mirror of
https://github.com/flutter/packages.git
synced 2025-07-02 08:34:31 +08:00
Allow custom bullet points through a widget builder (#167)
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
* Added builder option bulletBuilder
|
||||||
|
|
||||||
## 0.6.0
|
## 0.6.0
|
||||||
|
|
||||||
* Null safety release
|
* Null safety release
|
||||||
|
@ -10,25 +10,7 @@ import '_functions_io.dart' if (dart.library.html) '_functions_web.dart';
|
|||||||
import 'style_sheet.dart';
|
import 'style_sheet.dart';
|
||||||
import 'widget.dart';
|
import 'widget.dart';
|
||||||
|
|
||||||
const List<String> _kBlockTags = const <String>[
|
const List<String> _kBlockTags = const <String>['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'blockquote', 'pre', 'ol', 'ul', 'hr', 'table', 'thead', 'tbody', 'tr'];
|
||||||
'p',
|
|
||||||
'h1',
|
|
||||||
'h2',
|
|
||||||
'h3',
|
|
||||||
'h4',
|
|
||||||
'h5',
|
|
||||||
'h6',
|
|
||||||
'li',
|
|
||||||
'blockquote',
|
|
||||||
'pre',
|
|
||||||
'ol',
|
|
||||||
'ul',
|
|
||||||
'hr',
|
|
||||||
'table',
|
|
||||||
'thead',
|
|
||||||
'tbody',
|
|
||||||
'tr'
|
|
||||||
];
|
|
||||||
|
|
||||||
const List<String> _kListTags = const <String>['ul', 'ol'];
|
const List<String> _kListTags = const <String>['ul', 'ol'];
|
||||||
|
|
||||||
@ -99,6 +81,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
required this.imageDirectory,
|
required this.imageDirectory,
|
||||||
required this.imageBuilder,
|
required this.imageBuilder,
|
||||||
required this.checkboxBuilder,
|
required this.checkboxBuilder,
|
||||||
|
required this.bulletBuilder,
|
||||||
required this.builders,
|
required this.builders,
|
||||||
required this.listItemCrossAxisAlignment,
|
required this.listItemCrossAxisAlignment,
|
||||||
this.fitContent = false,
|
this.fitContent = false,
|
||||||
@ -125,6 +108,9 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
/// Call when build a checkbox widget.
|
/// Call when build a checkbox widget.
|
||||||
final MarkdownCheckboxBuilder? checkboxBuilder;
|
final MarkdownCheckboxBuilder? checkboxBuilder;
|
||||||
|
|
||||||
|
/// Called when building a custom bullet.
|
||||||
|
final MarkdownBulletBuilder? bulletBuilder;
|
||||||
|
|
||||||
/// Call when build a custom widget.
|
/// Call when build a custom widget.
|
||||||
final Map<String, MarkdownElementBuilder> builders;
|
final Map<String, MarkdownElementBuilder> builders;
|
||||||
|
|
||||||
@ -188,16 +174,14 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
_addAnonymousBlockIfNeeded();
|
_addAnonymousBlockIfNeeded();
|
||||||
if (_isListTag(tag)) {
|
if (_isListTag(tag)) {
|
||||||
_listIndents.add(tag);
|
_listIndents.add(tag);
|
||||||
if (element.attributes["start"] != null)
|
if (element.attributes["start"] != null) start = int.parse(element.attributes["start"]!) - 1;
|
||||||
start = int.parse(element.attributes["start"]!) - 1;
|
|
||||||
} else if (tag == 'blockquote') {
|
} else if (tag == 'blockquote') {
|
||||||
_isInBlockquote = true;
|
_isInBlockquote = true;
|
||||||
} else if (tag == 'table') {
|
} else if (tag == 'table') {
|
||||||
_tables.add(_TableElement());
|
_tables.add(_TableElement());
|
||||||
} else if (tag == 'tr') {
|
} else if (tag == 'tr') {
|
||||||
final length = _tables.single.rows.length;
|
final length = _tables.single.rows.length;
|
||||||
BoxDecoration? decoration =
|
BoxDecoration? decoration = styleSheet.tableCellsDecoration as BoxDecoration?;
|
||||||
styleSheet.tableCellsDecoration as BoxDecoration?;
|
|
||||||
if (length == 0 || length % 2 == 1) decoration = null;
|
if (length == 0 || length % 2 == 1) decoration = null;
|
||||||
_tables.single.rows.add(TableRow(
|
_tables.single.rows.add(TableRow(
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
@ -227,9 +211,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
// The Markdown parser passes empty table data tags for blank
|
// The Markdown parser passes empty table data tags for blank
|
||||||
// table cells. Insert a text node with an empty string in this
|
// table cells. Insert a text node with an empty string in this
|
||||||
// case for the table cell to get properly created.
|
// case for the table cell to get properly created.
|
||||||
if (element.tag == 'td' &&
|
if (element.tag == 'td' && element.children != null && element.children!.isEmpty) {
|
||||||
element.children != null &&
|
|
||||||
element.children!.isEmpty) {
|
|
||||||
element.children!.add(md.Text(''));
|
element.children!.add(md.Text(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,13 +226,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String? extractTextFromElement(element) {
|
String? extractTextFromElement(element) {
|
||||||
return element is md.Element && (element.children?.isNotEmpty ?? false)
|
return element is md.Element && (element.children?.isNotEmpty ?? false) ? element.children!.map((e) => e is md.Text ? e.text : extractTextFromElement(e)).join("") : ((element.attributes?.isNotEmpty ?? false) ? element.attributes["alt"] : "");
|
||||||
? element.children!
|
|
||||||
.map((e) => e is md.Text ? e.text : extractTextFromElement(e))
|
|
||||||
.join("")
|
|
||||||
: ((element.attributes?.isNotEmpty ?? false)
|
|
||||||
? element.attributes["alt"]
|
|
||||||
: "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -286,8 +262,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
|
|
||||||
Widget? child;
|
Widget? child;
|
||||||
if (_blocks.isNotEmpty && builders.containsKey(_blocks.last.tag)) {
|
if (_blocks.isNotEmpty && builders.containsKey(_blocks.last.tag)) {
|
||||||
child = builders[_blocks.last.tag!]!
|
child = builders[_blocks.last.tag!]!.visitText(text, styleSheet.styles[_blocks.last.tag!]);
|
||||||
.visitText(text, styleSheet.styles[_blocks.last.tag!]);
|
|
||||||
} else if (_blocks.last.tag == 'pre') {
|
} else if (_blocks.last.tag == 'pre') {
|
||||||
child = Scrollbar(
|
child = Scrollbar(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -299,9 +274,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
} else {
|
} else {
|
||||||
child = _buildRichText(
|
child = _buildRichText(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
style: _isInBlockquote
|
style: _isInBlockquote ? styleSheet.blockquote!.merge(_inlines.last.style) : _inlines.last.style,
|
||||||
? styleSheet.blockquote!.merge(_inlines.last.style)
|
|
||||||
: _inlines.last.style,
|
|
||||||
text: _isInBlockquote ? text.text : trimText(text.text),
|
text: _isInBlockquote ? text.text : trimText(text.text),
|
||||||
recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
|
recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
|
||||||
),
|
),
|
||||||
@ -325,9 +298,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
|
|
||||||
if (current.children.isNotEmpty) {
|
if (current.children.isNotEmpty) {
|
||||||
child = Column(
|
child = Column(
|
||||||
crossAxisAlignment: fitContent
|
crossAxisAlignment: fitContent ? CrossAxisAlignment.start : CrossAxisAlignment.stretch,
|
||||||
? CrossAxisAlignment.start
|
|
||||||
: CrossAxisAlignment.stretch,
|
|
||||||
children: current.children,
|
children: current.children,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -351,19 +322,11 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
bullet = _buildBullet(_listIndents.last);
|
bullet = _buildBullet(_listIndents.last);
|
||||||
}
|
}
|
||||||
child = Row(
|
child = Row(
|
||||||
textBaseline: listItemCrossAxisAlignment ==
|
textBaseline: listItemCrossAxisAlignment == MarkdownListItemCrossAxisAlignment.start ? null : TextBaseline.alphabetic,
|
||||||
MarkdownListItemCrossAxisAlignment.start
|
crossAxisAlignment: listItemCrossAxisAlignment == MarkdownListItemCrossAxisAlignment.start ? CrossAxisAlignment.start : CrossAxisAlignment.baseline,
|
||||||
? null
|
|
||||||
: TextBaseline.alphabetic,
|
|
||||||
crossAxisAlignment: listItemCrossAxisAlignment ==
|
|
||||||
MarkdownListItemCrossAxisAlignment.start
|
|
||||||
? CrossAxisAlignment.start
|
|
||||||
: CrossAxisAlignment.baseline,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: styleSheet.listIndent! +
|
width: styleSheet.listIndent! + styleSheet.listBulletPadding!.left + styleSheet.listBulletPadding!.right,
|
||||||
styleSheet.listBulletPadding!.left +
|
|
||||||
styleSheet.listBulletPadding!.right,
|
|
||||||
child: bullet,
|
child: bullet,
|
||||||
),
|
),
|
||||||
Expanded(child: child)
|
Expanded(child: child)
|
||||||
@ -401,8 +364,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
final _InlineElement parent = _inlines.last;
|
final _InlineElement parent = _inlines.last;
|
||||||
|
|
||||||
if (builders.containsKey(tag)) {
|
if (builders.containsKey(tag)) {
|
||||||
final Widget? child =
|
final Widget? child = builders[tag]!.visitElementAfter(element, styleSheet.styles[tag]);
|
||||||
builders[tag]!.visitElementAfter(element, styleSheet.styles[tag]);
|
|
||||||
if (child != null) current.children[0] = child;
|
if (child != null) current.children[0] = child;
|
||||||
} else if (tag == 'img') {
|
} else if (tag == 'img') {
|
||||||
// create an image widget for this image
|
// create an image widget for this image
|
||||||
@ -474,8 +436,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_linkHandlers.isNotEmpty) {
|
if (_linkHandlers.isNotEmpty) {
|
||||||
TapGestureRecognizer recognizer =
|
TapGestureRecognizer recognizer = _linkHandlers.last as TapGestureRecognizer;
|
||||||
_linkHandlers.last as TapGestureRecognizer;
|
|
||||||
return GestureDetector(child: child, onTap: recognizer.onTap);
|
return GestureDetector(child: child, onTap: recognizer.onTap);
|
||||||
} else {
|
} else {
|
||||||
return child;
|
return child;
|
||||||
@ -497,7 +458,17 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBullet(String listTag) {
|
Widget _buildBullet(String listTag) {
|
||||||
if (listTag == 'ul') {
|
final int index = _blocks.last.nextListIndex;
|
||||||
|
final bool isUnordered = listTag == 'ul';
|
||||||
|
|
||||||
|
if (bulletBuilder != null) {
|
||||||
|
return Padding(
|
||||||
|
padding: styleSheet.listBulletPadding!,
|
||||||
|
child: bulletBuilder!(index, isUnordered ? BulletStyle.unorderedList : BulletStyle.orderedList),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUnordered) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: styleSheet.listBulletPadding!,
|
padding: styleSheet.listBulletPadding!,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -508,7 +479,6 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int index = _blocks.last.nextListIndex;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: styleSheet.listBulletPadding!,
|
padding: styleSheet.listBulletPadding!,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -583,28 +553,20 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
) {
|
) {
|
||||||
List<Widget> mergedTexts = <Widget>[];
|
List<Widget> mergedTexts = <Widget>[];
|
||||||
for (Widget child in children) {
|
for (Widget child in children) {
|
||||||
if (mergedTexts.isNotEmpty &&
|
if (mergedTexts.isNotEmpty && mergedTexts.last is RichText && child is RichText) {
|
||||||
mergedTexts.last is RichText &&
|
|
||||||
child is RichText) {
|
|
||||||
RichText previous = mergedTexts.removeLast() as RichText;
|
RichText previous = mergedTexts.removeLast() as RichText;
|
||||||
TextSpan previousTextSpan = previous.text as TextSpan;
|
TextSpan previousTextSpan = previous.text as TextSpan;
|
||||||
List<TextSpan> children = previousTextSpan.children != null
|
List<TextSpan> children = previousTextSpan.children != null ? List.from(previousTextSpan.children!) : [previousTextSpan];
|
||||||
? List.from(previousTextSpan.children!)
|
|
||||||
: [previousTextSpan];
|
|
||||||
children.add(child.text as TextSpan);
|
children.add(child.text as TextSpan);
|
||||||
TextSpan? mergedSpan = _mergeSimilarTextSpans(children);
|
TextSpan? mergedSpan = _mergeSimilarTextSpans(children);
|
||||||
mergedTexts.add(_buildRichText(
|
mergedTexts.add(_buildRichText(
|
||||||
mergedSpan,
|
mergedSpan,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
));
|
));
|
||||||
} else if (mergedTexts.isNotEmpty &&
|
} else if (mergedTexts.isNotEmpty && mergedTexts.last is SelectableText && child is SelectableText) {
|
||||||
mergedTexts.last is SelectableText &&
|
|
||||||
child is SelectableText) {
|
|
||||||
SelectableText previous = mergedTexts.removeLast() as SelectableText;
|
SelectableText previous = mergedTexts.removeLast() as SelectableText;
|
||||||
TextSpan previousTextSpan = previous.textSpan!;
|
TextSpan previousTextSpan = previous.textSpan!;
|
||||||
List<TextSpan> children = previousTextSpan.children != null
|
List<TextSpan> children = previousTextSpan.children != null ? List.from(previousTextSpan.children!) : [previousTextSpan];
|
||||||
? List.from(previousTextSpan.children!)
|
|
||||||
: [previousTextSpan];
|
|
||||||
if (child.textSpan != null) {
|
if (child.textSpan != null) {
|
||||||
children.add(child.textSpan!);
|
children.add(child.textSpan!);
|
||||||
}
|
}
|
||||||
@ -667,10 +629,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
|
|
||||||
for (int index = 1; index < textSpans.length; index++) {
|
for (int index = 1; index < textSpans.length; index++) {
|
||||||
TextSpan? nextChild = textSpans[index];
|
TextSpan? nextChild = textSpans[index];
|
||||||
if (nextChild is TextSpan &&
|
if (nextChild is TextSpan && nextChild.recognizer == mergedSpans.last.recognizer && nextChild.semanticsLabel == mergedSpans.last.semanticsLabel && nextChild.style == mergedSpans.last.style) {
|
||||||
nextChild.recognizer == mergedSpans.last.recognizer &&
|
|
||||||
nextChild.semanticsLabel == mergedSpans.last.semanticsLabel &&
|
|
||||||
nextChild.style == mergedSpans.last.style) {
|
|
||||||
TextSpan previous = mergedSpans.removeLast();
|
TextSpan previous = mergedSpans.removeLast();
|
||||||
mergedSpans.add(TextSpan(
|
mergedSpans.add(TextSpan(
|
||||||
text: previous.toPlainText() + nextChild.toPlainText(),
|
text: previous.toPlainText() + nextChild.toPlainText(),
|
||||||
@ -685,9 +644,7 @@ class MarkdownBuilder implements md.NodeVisitor {
|
|||||||
|
|
||||||
// When the mergered spans compress into a single TextSpan return just that
|
// When the mergered spans compress into a single TextSpan return just that
|
||||||
// TextSpan, otherwise bundle the set of TextSpans under a single parent.
|
// TextSpan, otherwise bundle the set of TextSpans under a single parent.
|
||||||
return mergedSpans.length == 1
|
return mergedSpans.length == 1 ? mergedSpans.first : TextSpan(children: mergedSpans);
|
||||||
? mergedSpans.first
|
|
||||||
: TextSpan(children: mergedSpans);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRichText(TextSpan? text, {TextAlign? textAlign}) {
|
Widget _buildRichText(TextSpan? text, {TextAlign? textAlign}) {
|
||||||
|
@ -32,6 +32,19 @@ typedef Widget MarkdownImageBuilder(Uri uri, String? title, String? alt);
|
|||||||
/// Used by [MarkdownWidget.checkboxBuilder]
|
/// Used by [MarkdownWidget.checkboxBuilder]
|
||||||
typedef Widget MarkdownCheckboxBuilder(bool value);
|
typedef Widget MarkdownCheckboxBuilder(bool value);
|
||||||
|
|
||||||
|
/// Signature for custom bullet widget.
|
||||||
|
///
|
||||||
|
/// Used by [MarkdownWidget.bulletBuilder]
|
||||||
|
typedef Widget MarkdownBulletBuilder(int index, BulletStyle style);
|
||||||
|
|
||||||
|
/// Enumeration sent to the user when calling [MarkdownBulletBuilder]
|
||||||
|
///
|
||||||
|
/// Use this to differentiate the bullet styling when building your own.
|
||||||
|
enum BulletStyle {
|
||||||
|
orderedList,
|
||||||
|
unorderedList,
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a format [TextSpan] given a string.
|
/// Creates a format [TextSpan] given a string.
|
||||||
///
|
///
|
||||||
/// Used by [MarkdownWidget] to highlight the contents of `pre` elements.
|
/// Used by [MarkdownWidget] to highlight the contents of `pre` elements.
|
||||||
@ -61,8 +74,7 @@ abstract class MarkdownElementBuilder {
|
|||||||
/// to [preferredStyle].
|
/// to [preferredStyle].
|
||||||
///
|
///
|
||||||
/// If you needn't build a widget, return null.
|
/// If you needn't build a widget, return null.
|
||||||
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) =>
|
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) => null;
|
||||||
null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum to specify which theme being used when creating [MarkdownStyleSheet]
|
/// Enum to specify which theme being used when creating [MarkdownStyleSheet]
|
||||||
@ -136,10 +148,10 @@ abstract class MarkdownWidget extends StatefulWidget {
|
|||||||
this.extensionSet,
|
this.extensionSet,
|
||||||
this.imageBuilder,
|
this.imageBuilder,
|
||||||
this.checkboxBuilder,
|
this.checkboxBuilder,
|
||||||
|
this.bulletBuilder,
|
||||||
this.builders = const {},
|
this.builders = const {},
|
||||||
this.fitContent = false,
|
this.fitContent = false,
|
||||||
this.listItemCrossAxisAlignment =
|
this.listItemCrossAxisAlignment = MarkdownListItemCrossAxisAlignment.baseline,
|
||||||
MarkdownListItemCrossAxisAlignment.baseline,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// The Markdown to display.
|
/// The Markdown to display.
|
||||||
@ -191,6 +203,9 @@ abstract class MarkdownWidget extends StatefulWidget {
|
|||||||
/// Call when build a checkbox widget.
|
/// Call when build a checkbox widget.
|
||||||
final MarkdownCheckboxBuilder? checkboxBuilder;
|
final MarkdownCheckboxBuilder? checkboxBuilder;
|
||||||
|
|
||||||
|
/// Called when building a bullet
|
||||||
|
final MarkdownBulletBuilder? bulletBuilder;
|
||||||
|
|
||||||
/// Render certain tags, usually used with [extensionSet]
|
/// Render certain tags, usually used with [extensionSet]
|
||||||
///
|
///
|
||||||
/// For example, we will add support for `sub` tag:
|
/// For example, we will add support for `sub` tag:
|
||||||
@ -223,8 +238,7 @@ abstract class MarkdownWidget extends StatefulWidget {
|
|||||||
_MarkdownWidgetState createState() => _MarkdownWidgetState();
|
_MarkdownWidgetState createState() => _MarkdownWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MarkdownWidgetState extends State<MarkdownWidget>
|
class _MarkdownWidgetState extends State<MarkdownWidget> implements MarkdownBuilderDelegate {
|
||||||
implements MarkdownBuilderDelegate {
|
|
||||||
List<Widget>? _children;
|
List<Widget>? _children;
|
||||||
final List<GestureRecognizer> _recognizers = <GestureRecognizer>[];
|
final List<GestureRecognizer> _recognizers = <GestureRecognizer>[];
|
||||||
|
|
||||||
@ -237,8 +251,7 @@ class _MarkdownWidgetState extends State<MarkdownWidget>
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(MarkdownWidget oldWidget) {
|
void didUpdateWidget(MarkdownWidget oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.data != oldWidget.data ||
|
if (widget.data != oldWidget.data || widget.styleSheet != oldWidget.styleSheet) {
|
||||||
widget.styleSheet != oldWidget.styleSheet) {
|
|
||||||
_parseMarkdown();
|
_parseMarkdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,10 +263,8 @@ class _MarkdownWidgetState extends State<MarkdownWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _parseMarkdown() {
|
void _parseMarkdown() {
|
||||||
final MarkdownStyleSheet fallbackStyleSheet =
|
final MarkdownStyleSheet fallbackStyleSheet = kFallbackStyle(context, widget.styleSheetTheme);
|
||||||
kFallbackStyle(context, widget.styleSheetTheme);
|
final MarkdownStyleSheet styleSheet = fallbackStyleSheet.merge(widget.styleSheet);
|
||||||
final MarkdownStyleSheet styleSheet =
|
|
||||||
fallbackStyleSheet.merge(widget.styleSheet);
|
|
||||||
|
|
||||||
_disposeRecognizers();
|
_disposeRecognizers();
|
||||||
|
|
||||||
@ -277,6 +288,7 @@ class _MarkdownWidgetState extends State<MarkdownWidget>
|
|||||||
imageDirectory: widget.imageDirectory,
|
imageDirectory: widget.imageDirectory,
|
||||||
imageBuilder: widget.imageBuilder,
|
imageBuilder: widget.imageBuilder,
|
||||||
checkboxBuilder: widget.checkboxBuilder,
|
checkboxBuilder: widget.checkboxBuilder,
|
||||||
|
bulletBuilder: widget.bulletBuilder,
|
||||||
builders: widget.builders,
|
builders: widget.builders,
|
||||||
fitContent: widget.fitContent,
|
fitContent: widget.fitContent,
|
||||||
listItemCrossAxisAlignment: widget.listItemCrossAxisAlignment,
|
listItemCrossAxisAlignment: widget.listItemCrossAxisAlignment,
|
||||||
@ -288,8 +300,7 @@ class _MarkdownWidgetState extends State<MarkdownWidget>
|
|||||||
|
|
||||||
void _disposeRecognizers() {
|
void _disposeRecognizers() {
|
||||||
if (_recognizers.isEmpty) return;
|
if (_recognizers.isEmpty) return;
|
||||||
final List<GestureRecognizer> localRecognizers =
|
final List<GestureRecognizer> localRecognizers = List<GestureRecognizer>.from(_recognizers);
|
||||||
List<GestureRecognizer>.from(_recognizers);
|
|
||||||
_recognizers.clear();
|
_recognizers.clear();
|
||||||
for (GestureRecognizer recognizer in localRecognizers) recognizer.dispose();
|
for (GestureRecognizer recognizer in localRecognizers) recognizer.dispose();
|
||||||
}
|
}
|
||||||
@ -345,9 +356,9 @@ class MarkdownBody extends MarkdownWidget {
|
|||||||
md.ExtensionSet? extensionSet,
|
md.ExtensionSet? extensionSet,
|
||||||
MarkdownImageBuilder? imageBuilder,
|
MarkdownImageBuilder? imageBuilder,
|
||||||
MarkdownCheckboxBuilder? checkboxBuilder,
|
MarkdownCheckboxBuilder? checkboxBuilder,
|
||||||
|
MarkdownBulletBuilder? bulletBuilder,
|
||||||
Map<String, MarkdownElementBuilder> builders = const {},
|
Map<String, MarkdownElementBuilder> builders = const {},
|
||||||
MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment =
|
MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment = MarkdownListItemCrossAxisAlignment.baseline,
|
||||||
MarkdownListItemCrossAxisAlignment.baseline,
|
|
||||||
this.shrinkWrap = true,
|
this.shrinkWrap = true,
|
||||||
this.fitContent = true,
|
this.fitContent = true,
|
||||||
}) : super(
|
}) : super(
|
||||||
@ -367,6 +378,7 @@ class MarkdownBody extends MarkdownWidget {
|
|||||||
checkboxBuilder: checkboxBuilder,
|
checkboxBuilder: checkboxBuilder,
|
||||||
builders: builders,
|
builders: builders,
|
||||||
listItemCrossAxisAlignment: listItemCrossAxisAlignment,
|
listItemCrossAxisAlignment: listItemCrossAxisAlignment,
|
||||||
|
bulletBuilder: bulletBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// See [ScrollView.shrinkWrap]
|
/// See [ScrollView.shrinkWrap]
|
||||||
@ -380,8 +392,7 @@ class MarkdownBody extends MarkdownWidget {
|
|||||||
if (children!.length == 1) return children.single;
|
if (children!.length == 1) return children.single;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: shrinkWrap ? MainAxisSize.min : MainAxisSize.max,
|
mainAxisSize: shrinkWrap ? MainAxisSize.min : MainAxisSize.max,
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: fitContent ? CrossAxisAlignment.start : CrossAxisAlignment.stretch,
|
||||||
fitContent ? CrossAxisAlignment.start : CrossAxisAlignment.stretch,
|
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -413,9 +424,9 @@ class Markdown extends MarkdownWidget {
|
|||||||
md.ExtensionSet? extensionSet,
|
md.ExtensionSet? extensionSet,
|
||||||
MarkdownImageBuilder? imageBuilder,
|
MarkdownImageBuilder? imageBuilder,
|
||||||
MarkdownCheckboxBuilder? checkboxBuilder,
|
MarkdownCheckboxBuilder? checkboxBuilder,
|
||||||
|
MarkdownBulletBuilder? bulletBuilder,
|
||||||
Map<String, MarkdownElementBuilder> builders = const {},
|
Map<String, MarkdownElementBuilder> builders = const {},
|
||||||
MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment =
|
MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment = MarkdownListItemCrossAxisAlignment.baseline,
|
||||||
MarkdownListItemCrossAxisAlignment.baseline,
|
|
||||||
this.padding = const EdgeInsets.all(16.0),
|
this.padding = const EdgeInsets.all(16.0),
|
||||||
this.controller,
|
this.controller,
|
||||||
this.physics,
|
this.physics,
|
||||||
@ -437,6 +448,7 @@ class Markdown extends MarkdownWidget {
|
|||||||
checkboxBuilder: checkboxBuilder,
|
checkboxBuilder: checkboxBuilder,
|
||||||
builders: builders,
|
builders: builders,
|
||||||
listItemCrossAxisAlignment: listItemCrossAxisAlignment,
|
listItemCrossAxisAlignment: listItemCrossAxisAlignment,
|
||||||
|
bulletBuilder: bulletBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The amount of space by which to inset the children.
|
/// The amount of space by which to inset the children.
|
||||||
|
@ -69,18 +69,7 @@ void defineTests() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final Iterable<Widget> widgets = tester.allWidgets;
|
final Iterable<Widget> widgets = tester.allWidgets;
|
||||||
expectTextStrings(widgets, <String>[
|
expectTextStrings(widgets, <String>['1.', 'Item 1', '2.', 'Item 2', '3.', 'Item 3', '10.', 'Item 10', '11.', 'Item 11']);
|
||||||
'1.',
|
|
||||||
'Item 1',
|
|
||||||
'2.',
|
|
||||||
'Item 2',
|
|
||||||
'3.',
|
|
||||||
'Item 3',
|
|
||||||
'10.',
|
|
||||||
'Item 10',
|
|
||||||
'11.',
|
|
||||||
'Item 11'
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -107,12 +96,35 @@ void defineTests() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('custom bullet builder', (WidgetTester tester) async {
|
||||||
|
final String data = '* Item 1\n* Item 2\n1) Item 3\n2) Item 4';
|
||||||
|
final MarkdownBulletBuilder builder = (int index, BulletStyle style) => Text('$index ${style == BulletStyle.orderedList ? 'ordered' : 'unordered'}');
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
Markdown(data: data, bulletBuilder: builder),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Iterable<Widget> widgets = tester.allWidgets;
|
||||||
|
|
||||||
|
expectTextStrings(widgets, <String>[
|
||||||
|
'0 unordered',
|
||||||
|
'Item 1',
|
||||||
|
'1 unordered',
|
||||||
|
'Item 2',
|
||||||
|
'0 ordered',
|
||||||
|
'Item 3',
|
||||||
|
'1 ordered',
|
||||||
|
'Item 4',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'custom checkbox builder',
|
'custom checkbox builder',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
const String data = '- [x] Item 1\n- [ ] Item 2';
|
const String data = '- [x] Item 1\n- [ ] Item 2';
|
||||||
final MarkdownCheckboxBuilder builder =
|
final MarkdownCheckboxBuilder builder = (bool checked) => Text('$checked');
|
||||||
(bool checked) => Text('$checked');
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
boilerplate(
|
boilerplate(
|
||||||
|
Reference in New Issue
Block a user