Skip to content

Commit bba066f

Browse files
authored
[flutter_markdown] Added sizedImageBuilder to Markdown widget (#6739)
- Adds `sizedImageBuilder` to `Markdown` widget.
1 parent 799a590 commit bba066f

File tree

6 files changed

+157
-10
lines changed

6 files changed

+157
-10
lines changed

packages/flutter_markdown/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.7.7
2+
3+
* Introduces `MarkdownImageConfig` for `sizedImageBuilder` builder.
4+
15
## 0.7.6+2
26

37
* Updates README to indicate that this package will be discontinued.

packages/flutter_markdown/lib/src/builder.dart

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,33 @@ class _TableElement {
5050
final List<TableRow> rows = <TableRow>[];
5151
}
5252

53+
/// Holds configuration data for an image in a Markdown document.
54+
class MarkdownImageConfig {
55+
/// Creates a new [MarkdownImageConfig] instance.
56+
MarkdownImageConfig({
57+
required this.uri,
58+
this.title,
59+
this.alt,
60+
this.width,
61+
this.height,
62+
});
63+
64+
/// The URI of the image.
65+
final Uri uri;
66+
67+
/// The title of the image, displayed on hover.
68+
final String? title;
69+
70+
/// The alternative text for the image, displayed if the image cannot be loaded.
71+
final String? alt;
72+
73+
/// The desired width of the image.
74+
final double? width;
75+
76+
/// The desired height of the image.
77+
final double? height;
78+
}
79+
5380
/// A collection of widgets that should be placed adjacent to (inline with)
5481
/// other inline elements in the same parent block.
5582
///
@@ -105,7 +132,8 @@ class MarkdownBuilder implements md.NodeVisitor {
105132
required this.selectable,
106133
required this.styleSheet,
107134
required this.imageDirectory,
108-
required this.imageBuilder,
135+
@Deprecated('Use sizedImageBuilder instead') this.imageBuilder,
136+
required this.sizedImageBuilder,
109137
required this.checkboxBuilder,
110138
required this.bulletBuilder,
111139
required this.builders,
@@ -115,7 +143,8 @@ class MarkdownBuilder implements md.NodeVisitor {
115143
this.onSelectionChanged,
116144
this.onTapText,
117145
this.softLineBreak = false,
118-
});
146+
}) : assert(imageBuilder == null || sizedImageBuilder == null,
147+
'Only one of imageBuilder or sizedImageBuilder may be specified.');
119148

120149
/// A delegate that controls how link and `pre` elements behave.
121150
final MarkdownBuilderDelegate delegate;
@@ -131,9 +160,41 @@ class MarkdownBuilder implements md.NodeVisitor {
131160
/// The base directory holding images referenced by Img tags with local or network file paths.
132161
final String? imageDirectory;
133162

134-
/// Call when build an image widget.
163+
/// {@template flutter_markdown.builder.MarkdownBuilder.imageBuilder}
164+
/// Called to build an image widget.
165+
///
166+
/// This builder allows for custom rendering of images within the Markdown content.
167+
/// It provides the image `Uri`, `title`, and `alt` text.
168+
///
169+
/// **Deprecated:** Use [sizedImageBuilder] instead, which offers more comprehensive
170+
/// image information.
171+
///
172+
/// Only one of [imageBuilder] or [sizedImageBuilder] may be specified.
173+
///
174+
/// {@endtemplate}
175+
@Deprecated('Use sizedImageBuilder instead')
135176
final MarkdownImageBuilder? imageBuilder;
136177

178+
/// {@template flutter_markdown.builder.MarkdownBuilder.sizedImageBuilder}
179+
/// Called to build an image widget with size information.
180+
///
181+
/// This builder allows for custom rendering of images within the Markdown content
182+
/// when size information is available. It provides a [MarkdownImageConfig]
183+
/// containing the `Uri`, `title`, `alt`, `width`, and `height` of the image.
184+
///
185+
/// If both [imageBuilder] and [sizedImageBuilder] are `null`, a default image builder
186+
/// will be used.
187+
/// when size information is available. It provides a [MarkdownImageConfig]
188+
/// containing the `Uri`, `title`, `alt`, `width`, and `height` of the image.
189+
///
190+
/// If both [imageBuilder] and [sizedImageBuilder] are `null`, a default
191+
/// image builder will be used.
192+
///
193+
/// Only one of [imageBuilder] or [sizedImageBuilder] may be specified.
194+
///
195+
/// {@endtemplate}
196+
final MarkdownSizedImageBuilder? sizedImageBuilder;
197+
137198
/// Call when build a checkbox widget.
138199
final MarkdownCheckboxBuilder? checkboxBuilder;
139200

@@ -619,8 +680,12 @@ class MarkdownBuilder implements md.NodeVisitor {
619680
}
620681

621682
Widget child;
622-
if (imageBuilder != null) {
623-
child = imageBuilder!(uri, title, alt);
683+
if (sizedImageBuilder != null) {
684+
final MarkdownImageConfig config = MarkdownImageConfig(
685+
uri: uri, alt: alt, title: title, height: height, width: width);
686+
child = sizedImageBuilder!(config);
687+
} else if (imageBuilder != null) {
688+
child = imageBuilder!(uri, alt, title);
624689
} else {
625690
child = kDefaultImageBuilder(uri, imageDirectory, width, height);
626691
}

packages/flutter_markdown/lib/src/widget.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ typedef MarkdownOnSelectionChangedCallback = void Function(
3232
typedef MarkdownTapLinkCallback = void Function(
3333
String text, String? href, String title);
3434

35+
/// Signature for custom image builders used by [MarkdownWidget.sizedImageBuilder].
36+
///
37+
/// This signature allows for custom rendering of images within the Markdown
38+
/// content when size information is available. It takes a
39+
/// [MarkdownImageConfig] object as a parameter, which contains information
40+
/// about the image, including:
41+
/// - `uri`: The URI of the image.
42+
/// - `title`: The title of the image.
43+
/// - `alt`: The alternative text for the image.
44+
/// - 'height': The height of the image.
45+
/// - 'width': The width of the image.
46+
///
47+
/// Used by [MarkdownWidget.sizedImageBuilder]
48+
typedef MarkdownSizedImageBuilder = Widget Function(MarkdownImageConfig config);
49+
3550
/// Signature for custom image widget.
3651
///
3752
/// Used by [MarkdownWidget.imageBuilder]
@@ -220,7 +235,8 @@ abstract class MarkdownWidget extends StatefulWidget {
220235
this.blockSyntaxes,
221236
this.inlineSyntaxes,
222237
this.extensionSet,
223-
this.imageBuilder,
238+
@Deprecated('Use sizedImageBuilder instead') this.imageBuilder,
239+
this.sizedImageBuilder,
224240
this.checkboxBuilder,
225241
this.bulletBuilder,
226242
this.builders = const <String, MarkdownElementBuilder>{},
@@ -277,9 +293,13 @@ abstract class MarkdownWidget extends StatefulWidget {
277293
/// Defaults to [md.ExtensionSet.gitHubFlavored]
278294
final md.ExtensionSet? extensionSet;
279295

280-
/// Call when build an image widget.
296+
/// {@macro flutter_markdown.builder.MarkdownBuilder.imageBuilder}
297+
@Deprecated('Use sizedImageBuilder instead')
281298
final MarkdownImageBuilder? imageBuilder;
282299

300+
/// {@macro flutter_markdown.builder.MarkdownBuilder.sizedImageBuilder}
301+
final MarkdownSizedImageBuilder? sizedImageBuilder;
302+
283303
/// Call when build a checkbox widget.
284304
final MarkdownCheckboxBuilder? checkboxBuilder;
285305

@@ -391,6 +411,7 @@ class _MarkdownWidgetState extends State<MarkdownWidget>
391411
styleSheet: styleSheet,
392412
imageDirectory: widget.imageDirectory,
393413
imageBuilder: widget.imageBuilder,
414+
sizedImageBuilder: widget.sizedImageBuilder,
394415
checkboxBuilder: widget.checkboxBuilder,
395416
bulletBuilder: widget.bulletBuilder,
396417
builders: widget.builders,
@@ -467,7 +488,8 @@ class MarkdownBody extends MarkdownWidget {
467488
super.blockSyntaxes,
468489
super.inlineSyntaxes,
469490
super.extensionSet,
470-
super.imageBuilder,
491+
@Deprecated('Use sizedImageBuilder instead.') super.imageBuilder,
492+
super.sizedImageBuilder,
471493
super.checkboxBuilder,
472494
super.bulletBuilder,
473495
super.builders,
@@ -522,7 +544,8 @@ class Markdown extends MarkdownWidget {
522544
super.blockSyntaxes,
523545
super.inlineSyntaxes,
524546
super.extensionSet,
525-
super.imageBuilder,
547+
@Deprecated('Use sizedImageBuilder instead.') super.imageBuilder,
548+
super.sizedImageBuilder,
526549
super.checkboxBuilder,
527550
super.bulletBuilder,
528551
super.builders,

packages/flutter_markdown/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: A Markdown renderer for Flutter. Create rich text output,
44
formatted with simple Markdown tags.
55
repository: https://github.com/flutter/packages/tree/main/packages/flutter_markdown
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_markdown%22
7-
version: 0.7.6+2
7+
version: 0.7.7
88

99
environment:
1010
sdk: ^3.4.0
Loading

packages/flutter_markdown/test/image_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,5 +459,60 @@ void defineTests() {
459459
},
460460
skip: kIsWeb, // Goldens are platform-specific.
461461
);
462+
463+
testWidgets(
464+
'custom image builder test width and height',
465+
(WidgetTester tester) async {
466+
const double height = 200;
467+
const double width = 100;
468+
const String data = '![alt](https://img.png#${width}x$height)';
469+
Widget builder(MarkdownImageConfig config) =>
470+
Image.asset('assets/logo.png',
471+
width: config.width, height: config.height);
472+
473+
await tester.pumpWidget(
474+
boilerplate(
475+
MaterialApp(
476+
home: DefaultAssetBundle(
477+
bundle: TestAssetBundle(),
478+
child: Center(
479+
child: Container(
480+
color: Colors.white,
481+
width: 500,
482+
child: Markdown(
483+
data: data,
484+
sizedImageBuilder: builder,
485+
),
486+
),
487+
),
488+
),
489+
),
490+
),
491+
);
492+
493+
final Iterable<Widget> widgets = tester.allWidgets;
494+
final Image image =
495+
widgets.firstWhere((Widget widget) => widget is Image) as Image;
496+
497+
expect(image.image.runtimeType, AssetImage);
498+
expect((image.image as AssetImage).assetName, 'assets/logo.png');
499+
expect(image.width, width);
500+
expect(image.height, height);
501+
502+
await tester.runAsync(() async {
503+
final Element element = tester.element(find.byType(Markdown));
504+
await precacheImage(image.image, element);
505+
});
506+
507+
await tester.pumpAndSettle();
508+
509+
await expectLater(
510+
find.byType(Container),
511+
matchesGoldenFile(
512+
'assets/images/golden/image_test/custom_image_builder_test.png'));
513+
imageCache.clear();
514+
},
515+
skip: kIsWeb,
516+
);
462517
});
463518
}

0 commit comments

Comments
 (0)