diff --git a/packages/flutter_markdown/CHANGELOG.md b/packages/flutter_markdown/CHANGELOG.md index 82d0964646f4..fa273dbf1152 100644 --- a/packages/flutter_markdown/CHANGELOG.md +++ b/packages/flutter_markdown/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.17 + +* Introduces a new `MarkdownElementBuilder.visitElementAfterWithContext()` method passing the widget `BuildContext` and + the parent text's `TextStyle`. + ## 0.6.16 * Adds `tableVerticalAlignment` property to allow aligning table cells vertically. diff --git a/packages/flutter_markdown/lib/src/builder.dart b/packages/flutter_markdown/lib/src/builder.dart index f43852453c24..fbdcb31c3a69 100644 --- a/packages/flutter_markdown/lib/src/builder.dart +++ b/packages/flutter_markdown/lib/src/builder.dart @@ -74,6 +74,13 @@ class _InlineElement { /// A delegate used by [MarkdownBuilder] to control the widgets it creates. abstract class MarkdownBuilderDelegate { + /// Returns the [BuildContext] of the [MarkdownWidget]. + /// + /// The context will be passed down to the + /// [MarkdownElementBuilder.visitElementBefore] method and allows elements to + /// get information from the context. + BuildContext get context; + /// Returns a gesture recognizer to use for an `a` element with the given /// text, `href` attribute, and title. GestureRecognizer createLink(String text, String? href, String title); @@ -454,8 +461,12 @@ class MarkdownBuilder implements md.NodeVisitor { } if (builders.containsKey(tag)) { - final Widget? child = - builders[tag]!.visitElementAfter(element, styleSheet.styles[tag]); + final Widget? child = builders[tag]!.visitElementAfterWithContext( + delegate.context, + element, + styleSheet.styles[tag], + parent.style, + ); if (child != null) { if (current.children.isEmpty) { current.children.add(child); diff --git a/packages/flutter_markdown/lib/src/widget.dart b/packages/flutter_markdown/lib/src/widget.dart index 690f5b89572a..65dc983d10ed 100644 --- a/packages/flutter_markdown/lib/src/widget.dart +++ b/packages/flutter_markdown/lib/src/widget.dart @@ -70,6 +70,25 @@ abstract class MarkdownElementBuilder { /// If you needn't build a widget, return null. Widget? visitText(md.Text text, TextStyle? preferredStyle) => null; + /// Called when an Element has been reached, after its children have been + /// visited. + /// + /// If [MarkdownWidget.styleSheet] has a style with this tag, it will be + /// passed as [preferredStyle]. + /// + /// If parent element has [TextStyle] set, it will be passed as + /// [parentStyle]. + /// + /// If a widget build isn't needed, return null. + Widget? visitElementAfterWithContext( + BuildContext context, + md.Element element, + TextStyle? preferredStyle, + TextStyle? parentStyle, + ) { + return visitElementAfter(element, preferredStyle); + } + /// Called when an Element has been reached, after its children have been /// visited. /// @@ -77,6 +96,7 @@ abstract class MarkdownElementBuilder { /// to [preferredStyle]. /// /// If you needn't build a widget, return null. + @Deprecated('Use visitElementAfterWithContext() instead.') Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) => null; } diff --git a/packages/flutter_markdown/pubspec.yaml b/packages/flutter_markdown/pubspec.yaml index 8ff74e8411b0..e7485b032f27 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.6.16 +version: 0.6.17 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/flutter_markdown/test/custom_syntax_test.dart b/packages/flutter_markdown/test/custom_syntax_test.dart index 46ed3ed2990b..fd81ba7e1fc5 100644 --- a/packages/flutter_markdown/test/custom_syntax_test.dart +++ b/packages/flutter_markdown/test/custom_syntax_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:markdown/markdown.dart' as md; @@ -83,6 +83,34 @@ void defineTests() { expect(widgetSpan.child, isInstanceOf()); }, ); + + testWidgets( + 'visitElementAfterWithContext is handled correctly', + (WidgetTester tester) async { + await tester.pumpWidget( + boilerplate( + Markdown( + data: r'# This is a header with some \color1{color} in it', + extensionSet: md.ExtensionSet.none, + inlineSyntaxes: [InlineTextColorSyntax()], + builders: { + 'inlineTextColor': InlineTextColorElementBuilder(), + }, + ), + ), + ); + + final RichText textWidget = tester.widget(find.byType(RichText)); + final TextSpan rootSpan = textWidget.text as TextSpan; + final TextSpan firstSpan = rootSpan.children![0] as TextSpan; + final TextSpan secondSpan = rootSpan.children![1] as TextSpan; + final TextSpan thirdSpan = rootSpan.children![2] as TextSpan; + + expect(secondSpan.style!.color, Colors.red); + expect(secondSpan.style!.fontSize, firstSpan.style!.fontSize); + expect(secondSpan.style!.fontSize, thirdSpan.style!.fontSize); + }, + ); }); testWidgets( @@ -250,6 +278,57 @@ class ContainerBuilder2 extends MarkdownElementBuilder { } } +// Note: The implementation of inline span is incomplete, it does not handle +// bold, italic, ... text with a colored block. +// This would not work: `\color1{Text with *bold* text}` +class InlineTextColorSyntax extends md.InlineSyntax { + InlineTextColorSyntax() : super(r'\\color([1-9]){(.*?)}'); + + @override + bool onMatch(md.InlineParser parser, Match match) { + final String colorId = match.group(1)!; + final String textContent = match.group(2)!; + final md.Element node = md.Element.text( + 'inlineTextColor', + textContent, + )..attributes['color'] = colorId; + + parser.addNode(node); + + parser.addNode( + md.Text(''), + ); + return true; + } +} + +class InlineTextColorElementBuilder extends MarkdownElementBuilder { + @override + Widget visitElementAfterWithContext( + BuildContext context, + md.Element element, + TextStyle? preferredStyle, + TextStyle? parentStyle, + ) { + final String innerText = element.textContent; + final String color = element.attributes['color'] ?? ''; + + final Map contentColors = { + '1': Colors.red, + '2': Colors.green, + '3': Colors.blue, + }; + final Color? contentColor = contentColors[color]; + + return RichText( + text: TextSpan( + text: innerText, + style: parentStyle?.copyWith(color: contentColor), + ), + ); + } +} + class ImgBuilder extends MarkdownElementBuilder { @override Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) {