|
3 | 3 | // found in the LICENSE file. |
4 | 4 |
|
5 | 5 | import 'package:flutter/gestures.dart'; |
6 | | -import 'package:flutter/widgets.dart'; |
| 6 | +import 'package:flutter/material.dart'; |
7 | 7 | import 'package:flutter_markdown/flutter_markdown.dart'; |
8 | 8 | import 'package:flutter_test/flutter_test.dart'; |
9 | 9 | import 'package:markdown/markdown.dart' as md; |
@@ -83,6 +83,34 @@ void defineTests() { |
83 | 83 | expect(widgetSpan.child, isInstanceOf<Container>()); |
84 | 84 | }, |
85 | 85 | ); |
| 86 | + |
| 87 | + testWidgets( |
| 88 | + 'visitElementAfterWithContext is handled correctly', |
| 89 | + (WidgetTester tester) async { |
| 90 | + await tester.pumpWidget( |
| 91 | + boilerplate( |
| 92 | + Markdown( |
| 93 | + data: r'# This is a header with some \color1{color} in it', |
| 94 | + extensionSet: md.ExtensionSet.none, |
| 95 | + inlineSyntaxes: <md.InlineSyntax>[InlineTextColorSyntax()], |
| 96 | + builders: <String, MarkdownElementBuilder>{ |
| 97 | + 'inlineTextColor': InlineTextColorElementBuilder(), |
| 98 | + }, |
| 99 | + ), |
| 100 | + ), |
| 101 | + ); |
| 102 | + |
| 103 | + final RichText textWidget = tester.widget(find.byType(RichText)); |
| 104 | + final TextSpan rootSpan = textWidget.text as TextSpan; |
| 105 | + final TextSpan firstSpan = rootSpan.children![0] as TextSpan; |
| 106 | + final TextSpan secondSpan = rootSpan.children![1] as TextSpan; |
| 107 | + final TextSpan thirdSpan = rootSpan.children![2] as TextSpan; |
| 108 | + |
| 109 | + expect(secondSpan.style!.color, Colors.red); |
| 110 | + expect(secondSpan.style!.fontSize, firstSpan.style!.fontSize); |
| 111 | + expect(secondSpan.style!.fontSize, thirdSpan.style!.fontSize); |
| 112 | + }, |
| 113 | + ); |
86 | 114 | }); |
87 | 115 |
|
88 | 116 | testWidgets( |
@@ -250,6 +278,57 @@ class ContainerBuilder2 extends MarkdownElementBuilder { |
250 | 278 | } |
251 | 279 | } |
252 | 280 |
|
| 281 | +// Note: The implementation of inline span is incomplete, it does not handle |
| 282 | +// bold, italic, ... text with a colored block. |
| 283 | +// This would not work: `\color1{Text with *bold* text}` |
| 284 | +class InlineTextColorSyntax extends md.InlineSyntax { |
| 285 | + InlineTextColorSyntax() : super(r'\\color([1-9]){(.*?)}'); |
| 286 | + |
| 287 | + @override |
| 288 | + bool onMatch(md.InlineParser parser, Match match) { |
| 289 | + final String colorId = match.group(1)!; |
| 290 | + final String textContent = match.group(2)!; |
| 291 | + final md.Element node = md.Element.text( |
| 292 | + 'inlineTextColor', |
| 293 | + textContent, |
| 294 | + )..attributes['color'] = colorId; |
| 295 | + |
| 296 | + parser.addNode(node); |
| 297 | + |
| 298 | + parser.addNode( |
| 299 | + md.Text(''), |
| 300 | + ); |
| 301 | + return true; |
| 302 | + } |
| 303 | +} |
| 304 | + |
| 305 | +class InlineTextColorElementBuilder extends MarkdownElementBuilder { |
| 306 | + @override |
| 307 | + Widget visitElementAfterWithContext( |
| 308 | + BuildContext context, |
| 309 | + md.Element element, |
| 310 | + TextStyle? preferredStyle, |
| 311 | + TextStyle? parentStyle, |
| 312 | + ) { |
| 313 | + final String innerText = element.textContent; |
| 314 | + final String color = element.attributes['color'] ?? ''; |
| 315 | + |
| 316 | + final Map<String, Color> contentColors = <String, Color>{ |
| 317 | + '1': Colors.red, |
| 318 | + '2': Colors.green, |
| 319 | + '3': Colors.blue, |
| 320 | + }; |
| 321 | + final Color? contentColor = contentColors[color]; |
| 322 | + |
| 323 | + return RichText( |
| 324 | + text: TextSpan( |
| 325 | + text: innerText, |
| 326 | + style: parentStyle?.copyWith(color: contentColor), |
| 327 | + ), |
| 328 | + ); |
| 329 | + } |
| 330 | +} |
| 331 | + |
253 | 332 | class ImgBuilder extends MarkdownElementBuilder { |
254 | 333 | @override |
255 | 334 | Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { |
|
0 commit comments