[flutter_markdown] Pass parent TextStyle down to MarkdownElementBuilder.visitElementAfter (#4393)

The parent `TextStyle` should be passed down to the `MarkdownElementBuilder.visitElementAfter` method to allow custom markdown tags to override only part of the text style, e.g. the color, but keep all the rest of the styles the same.

This is especially useful when trying to color markdown headers in a certain color, as the parent font size, font family, etc. all are passed down and can be kept, while only the color is overridden.

This will unfortunately lead to a breaking change, due to the nature of how the class is typically used. As all usages of the class are sub-classes any change to the method schema will result in a breaking change!

Enables the following https://github.com/flutter/flutter/issues/105571

replaces https://github.com/flutter/packages/pull/3281
This commit is contained in:
Tarrin Neal
2023-07-07 11:43:07 -07:00
committed by GitHub
parent cd19bad56a
commit 8918e93756
5 changed files with 119 additions and 4 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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;
}

View File

@ -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"

View File

@ -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<Container>());
},
);
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: <md.InlineSyntax>[InlineTextColorSyntax()],
builders: <String, MarkdownElementBuilder>{
'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<String, Color> contentColors = <String, Color>{
'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) {