diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index a213b77ee52..3f64591880a 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.7.7 + +* Introduces `MarkdownImageConfig` for `sizedImageBuilder` builder. + ## 0.7.6+2 * Updates README to indicate that this package will be discontinued. diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index de27657609d..81478fda196 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -50,6 +50,33 @@ class _TableElement { final List rows = []; } +/// Holds configuration data for an image in a Markdown document. +class MarkdownImageConfig { + /// Creates a new [MarkdownImageConfig] instance. + MarkdownImageConfig({ + required this.uri, + this.title, + this.alt, + this.width, + this.height, + }); + + /// The URI of the image. + final Uri uri; + + /// The title of the image, displayed on hover. + final String? title; + + /// The alternative text for the image, displayed if the image cannot be loaded. + final String? alt; + + /// The desired width of the image. + final double? width; + + /// The desired height of the image. + final double? height; +} + /// A collection of widgets that should be placed adjacent to (inline with) /// other inline elements in the same parent block. /// @@ -105,7 +132,8 @@ class MarkdownBuilder implements md.NodeVisitor { required this.selectable, required this.styleSheet, required this.imageDirectory, - required this.imageBuilder, + @Deprecated('Use sizedImageBuilder instead') this.imageBuilder, + required this.sizedImageBuilder, required this.checkboxBuilder, required this.bulletBuilder, required this.builders, @@ -115,7 +143,8 @@ class MarkdownBuilder implements md.NodeVisitor { this.onSelectionChanged, this.onTapText, this.softLineBreak = false, - }); + }) : assert(imageBuilder == null || sizedImageBuilder == null, + 'Only one of imageBuilder or sizedImageBuilder may be specified.'); /// A delegate that controls how link and `pre` elements behave. final MarkdownBuilderDelegate delegate; @@ -131,9 +160,41 @@ class MarkdownBuilder implements md.NodeVisitor { /// The base directory holding images referenced by Img tags with local or network file paths. final String? imageDirectory; - /// Call when build an image widget. + /// {@template flutter_markdown.builder.MarkdownBuilder.imageBuilder} + /// Called to build an image widget. + /// + /// This builder allows for custom rendering of images within the Markdown content. + /// It provides the image `Uri`, `title`, and `alt` text. + /// + /// **Deprecated:** Use [sizedImageBuilder] instead, which offers more comprehensive + /// image information. + /// + /// Only one of [imageBuilder] or [sizedImageBuilder] may be specified. + /// + /// {@endtemplate} + @Deprecated('Use sizedImageBuilder instead') final MarkdownImageBuilder? imageBuilder; + /// {@template flutter_markdown.builder.MarkdownBuilder.sizedImageBuilder} + /// Called to build an image widget with size information. + /// + /// This builder allows for custom rendering of images within the Markdown content + /// when size information is available. It provides a [MarkdownImageConfig] + /// containing the `Uri`, `title`, `alt`, `width`, and `height` of the image. + /// + /// If both [imageBuilder] and [sizedImageBuilder] are `null`, a default image builder + /// will be used. + /// when size information is available. It provides a [MarkdownImageConfig] + /// containing the `Uri`, `title`, `alt`, `width`, and `height` of the image. + /// + /// If both [imageBuilder] and [sizedImageBuilder] are `null`, a default + /// image builder will be used. + /// + /// Only one of [imageBuilder] or [sizedImageBuilder] may be specified. + /// + /// {@endtemplate} + final MarkdownSizedImageBuilder? sizedImageBuilder; + /// Call when build a checkbox widget. final MarkdownCheckboxBuilder? checkboxBuilder; @@ -619,8 +680,12 @@ class MarkdownBuilder implements md.NodeVisitor { } Widget child; - if (imageBuilder != null) { - child = imageBuilder!(uri, title, alt); + if (sizedImageBuilder != null) { + final MarkdownImageConfig config = MarkdownImageConfig( + uri: uri, alt: alt, title: title, height: height, width: width); + child = sizedImageBuilder!(config); + } else if (imageBuilder != null) { + child = imageBuilder!(uri, alt, title); } else { child = kDefaultImageBuilder(uri, imageDirectory, width, height); } diff --git a/packages/flutter_markdown/lib/src/widget.dart b/packages/flutter_markdown/lib/src/widget.dart index 88540e41264..2faeedf5e91 100644 --- a/packages/flutter_markdown/lib/src/widget.dart +++ b/packages/flutter_markdown/lib/src/widget.dart @@ -32,6 +32,21 @@ typedef MarkdownOnSelectionChangedCallback = void Function( typedef MarkdownTapLinkCallback = void Function( String text, String? href, String title); +/// Signature for custom image builders used by [MarkdownWidget.sizedImageBuilder]. +/// +/// This signature allows for custom rendering of images within the Markdown +/// content when size information is available. It takes a +/// [MarkdownImageConfig] object as a parameter, which contains information +/// about the image, including: +/// - `uri`: The URI of the image. +/// - `title`: The title of the image. +/// - `alt`: The alternative text for the image. +/// - 'height': The height of the image. +/// - 'width': The width of the image. +/// +/// Used by [MarkdownWidget.sizedImageBuilder] +typedef MarkdownSizedImageBuilder = Widget Function(MarkdownImageConfig config); + /// Signature for custom image widget. /// /// Used by [MarkdownWidget.imageBuilder] @@ -220,7 +235,8 @@ abstract class MarkdownWidget extends StatefulWidget { this.blockSyntaxes, this.inlineSyntaxes, this.extensionSet, - this.imageBuilder, + @Deprecated('Use sizedImageBuilder instead') this.imageBuilder, + this.sizedImageBuilder, this.checkboxBuilder, this.bulletBuilder, this.builders = const {}, @@ -277,9 +293,13 @@ abstract class MarkdownWidget extends StatefulWidget { /// Defaults to [md.ExtensionSet.gitHubFlavored] final md.ExtensionSet? extensionSet; - /// Call when build an image widget. + /// {@macro flutter_markdown.builder.MarkdownBuilder.imageBuilder} + @Deprecated('Use sizedImageBuilder instead') final MarkdownImageBuilder? imageBuilder; + /// {@macro flutter_markdown.builder.MarkdownBuilder.sizedImageBuilder} + final MarkdownSizedImageBuilder? sizedImageBuilder; + /// Call when build a checkbox widget. final MarkdownCheckboxBuilder? checkboxBuilder; @@ -391,6 +411,7 @@ class _MarkdownWidgetState extends State styleSheet: styleSheet, imageDirectory: widget.imageDirectory, imageBuilder: widget.imageBuilder, + sizedImageBuilder: widget.sizedImageBuilder, checkboxBuilder: widget.checkboxBuilder, bulletBuilder: widget.bulletBuilder, builders: widget.builders, @@ -467,7 +488,8 @@ class MarkdownBody extends MarkdownWidget { super.blockSyntaxes, super.inlineSyntaxes, super.extensionSet, - super.imageBuilder, + @Deprecated('Use sizedImageBuilder instead.') super.imageBuilder, + super.sizedImageBuilder, super.checkboxBuilder, super.bulletBuilder, super.builders, @@ -522,7 +544,8 @@ class Markdown extends MarkdownWidget { super.blockSyntaxes, super.inlineSyntaxes, super.extensionSet, - super.imageBuilder, + @Deprecated('Use sizedImageBuilder instead.') super.imageBuilder, + super.sizedImageBuilder, super.checkboxBuilder, super.bulletBuilder, super.builders, diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index 1aebfd3aab4..0a1a922ade3 100644 --- a/packages/flutter_markdown/pubspec.yaml +++ b/packages/flutter_markdown/pubspec.yaml @@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output, formatted with simple Markdown tags. repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22 -version: 0.7.6+2 +version: 0.7.7 environment: sdk: ^3.4.0 diff --git a/packages/flutter_markdown/test/assets/images/golden/image_test/custom_image_builder_test.png b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_image_builder_test.png new file mode 100644 index 00000000000..9e6bad8162b Binary files /dev/null and b/packages/flutter_markdown/test/assets/images/golden/image_test/custom_image_builder_test.png differ diff --git a/packages/flutter_markdown/test/image_test.dart b/packages/flutter_markdown/test/image_test.dart index 77cebc10934..deef70dcc40 100644 --- a/packages/flutter_markdown/test/image_test.dart +++ b/packages/flutter_markdown/test/image_test.dart @@ -459,5 +459,60 @@ void defineTests() { }, skip: kIsWeb, // Goldens are platform-specific. ); + + testWidgets( + 'custom image builder test width and height', + (WidgetTester tester) async { + const double height = 200; + const double width = 100; + const String data = '![alt](https://img.png#${width}x$height)'; + Widget builder(MarkdownImageConfig config) => + Image.asset('assets/logo.png', + width: config.width, height: config.height); + + await tester.pumpWidget( + boilerplate( + MaterialApp( + home: DefaultAssetBundle( + bundle: TestAssetBundle(), + child: Center( + child: Container( + color: Colors.white, + width: 500, + child: Markdown( + data: data, + sizedImageBuilder: builder, + ), + ), + ), + ), + ), + ), + ); + + final Iterable widgets = tester.allWidgets; + final Image image = + widgets.firstWhere((Widget widget) => widget is Image) as Image; + + expect(image.image.runtimeType, AssetImage); + expect((image.image as AssetImage).assetName, 'assets/logo.png'); + expect(image.width, width); + expect(image.height, height); + + await tester.runAsync(() async { + final Element element = tester.element(find.byType(Markdown)); + await precacheImage(image.image, element); + }); + + await tester.pumpAndSettle(); + + await expectLater( + find.byType(Container), + matchesGoldenFile( + 'assets/images/golden/image_test/custom_image_builder_test.png')); + imageCache.clear(); + }, + skip: kIsWeb, + ); }); }