From fdaca87061eac6182508136a0d8197b3ba53387f Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 18 May 2019 01:15:20 +0200 Subject: [PATCH 1/7] Added dto for image properties --- lib/image_properties.dart | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/image_properties.dart diff --git a/lib/image_properties.dart b/lib/image_properties.dart new file mode 100644 index 0000000000..6affd428f9 --- /dev/null +++ b/lib/image_properties.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +@immutable +class ImageProperties { + final String semanticLabel; + final bool excludeFromSemantics; + final double width; + final double height; + final Color color; + final BlendMode colorBlendMode; + final BoxFit fit; + final AlignmentGeometry alignment; + final ImageRepeat repeat; + final Rect centerSlice; + final bool matchTextDirection; + final bool gaplessPlayback; + final FilterQuality filterQuality; + final double scale; + + const ImageProperties( + {this.scale = 1, + this.semanticLabel, + this.excludeFromSemantics = false, + this.width, + this.height, + this.color, + this.colorBlendMode, + this.fit, + this.alignment = Alignment.center, + this.repeat = ImageRepeat.noRepeat, + this.centerSlice, + this.matchTextDirection = false, + this.gaplessPlayback = false, + this.filterQuality = FilterQuality.low}); +} From 7289b182ec7bbe92f4c105aae9df2d383afacbed Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 18 May 2019 01:19:29 +0200 Subject: [PATCH 2/7] Added onTap support, ImageProperties and 'alt' element fix - Added onTap support for Image widgets that are parsed by the richTextParser - Added ImageProperties to the richTextParser - Fixed 'alt' HTML element - it was not working before due to the if..else if.. confusion --- lib/html_parser.dart | 73 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 2f9e6f05f3..57783e6fa4 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -1,5 +1,5 @@ import 'dart:convert'; - +import 'image_properties.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:html/dom.dart' as dom; @@ -12,7 +12,7 @@ typedef CustomTextStyle = TextStyle Function( ); typedef CustomEdgeInsets = EdgeInsets Function(dom.Node node); typedef OnLinkTap = void Function(String url); - +typedef OnImageTap = void Function(); const OFFSET_TAGS_FONT_SIZE_FACTOR = 0.7; //The ratio of the parent font for each of the offset tags: sup or sub @@ -157,6 +157,8 @@ class HtmlRichTextParser extends StatelessWidget { color: Colors.blueAccent, decorationColor: Colors.blueAccent, ), + this.imageProperties, + this.onImageTap, }); final double indentSize = 10.0; @@ -169,6 +171,8 @@ class HtmlRichTextParser extends StatelessWidget { final CustomEdgeInsets customEdgeInsets; final ImageErrorListener onImageError; final TextStyle linkStyle; + final ImageProperties imageProperties; + final OnImageTap onImageTap; // style elements set a default style // for all child nodes @@ -654,26 +658,65 @@ class HtmlRichTextParser extends StatelessWidget { buildContext, onError: onImageError, ); - parseContext.rootWidgetList.add(Image.memory(base64.decode( - node.attributes['src'].split("base64,")[1].trim()))); + parseContext.rootWidgetList.add(InkWell( + child: Image.memory( + base64.decode(node.attributes['src'].split("base64,")[1].trim()), + width: imageProperties?.width, + height: imageProperties?.height, + scale: imageProperties?.scale ?? 1.0, + matchTextDirection: imageProperties?.matchTextDirection ?? false, + centerSlice: imageProperties?.centerSlice, + gaplessPlayback: imageProperties?.gaplessPlayback ?? false, + filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, + alignment: imageProperties?.alignment ?? Alignment.center, + colorBlendMode: imageProperties?.colorBlendMode, + fit: imageProperties?.fit, + color: imageProperties?.color, + repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, + semanticLabel: imageProperties?.semanticLabel, + excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, + ), + onTap: onImageTap, + )); } else { precacheImage( NetworkImage(node.attributes['src']), buildContext, onError: onImageError, ); - parseContext.rootWidgetList - .add(Image.network(node.attributes['src'])); + parseContext.rootWidgetList.add(InkWell( + child: Image.network( + node.attributes['src'], + width: imageProperties?.width, + height: imageProperties?.height, + scale: imageProperties?.scale ?? 1.0, + matchTextDirection: imageProperties?.matchTextDirection ?? false, + centerSlice: imageProperties?.centerSlice, + gaplessPlayback: imageProperties?.gaplessPlayback ?? false, + filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, + alignment: imageProperties?.alignment ?? Alignment.center, + colorBlendMode: imageProperties?.colorBlendMode, + fit: imageProperties?.fit, + color: imageProperties?.color, + repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, + semanticLabel: imageProperties?.semanticLabel, + excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, + ), + onTap: onImageTap, + )); + } + if (node.attributes['alt'] != null) { + parseContext.rootWidgetList.add(BlockText( + margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), + padding: EdgeInsets.all(0.0), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: node.attributes['alt'], + style: nextContext.childStyle, + children: [], + )))); } - } else if (node.attributes['alt'] != null) { - parseContext.rootWidgetList.add(BlockText( - margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), - padding: EdgeInsets.all(0.0), - child: RichText( - text: TextSpan( - text: node.attributes['alt'], - children: [], - )))); } break; case "li": From e85915ae10ad372b42af38401249803d3185212d Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 18 May 2019 01:20:17 +0200 Subject: [PATCH 3/7] Added ImageProperties to the Html widget --- lib/flutter_html.dart | 47 ++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 1d5b827f96..b0c2fd68f2 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -2,27 +2,30 @@ library flutter_html; import 'package:flutter/material.dart'; import 'package:flutter_html/html_parser.dart'; +import 'image_properties.dart'; class Html extends StatelessWidget { - Html({ - Key key, - @required this.data, - this.padding, - this.backgroundColor, - this.defaultTextStyle, - this.onLinkTap, - this.renderNewlines = false, - this.customRender, - this.customEdgeInsets, - this.customTextStyle, - this.blockSpacing = 14.0, - this.useRichText = false, - this.onImageError, - this.linkStyle = const TextStyle( - decoration: TextDecoration.underline, - color: Colors.blueAccent, - decorationColor: Colors.blueAccent), - }) : super(key: key); + Html( + {Key key, + @required this.data, + this.padding, + this.backgroundColor, + this.defaultTextStyle, + this.onLinkTap, + this.renderNewlines = false, + this.customRender, + this.customEdgeInsets, + this.customTextStyle, + this.blockSpacing = 14.0, + this.useRichText = false, + this.onImageError, + this.linkStyle = const TextStyle( + decoration: TextDecoration.underline, + color: Colors.blueAccent, + decorationColor: Colors.blueAccent), + this.imageProperties, + this.onImageTap}) + : super(key: key); final String data; final EdgeInsetsGeometry padding; @@ -35,6 +38,10 @@ class Html extends StatelessWidget { final ImageErrorListener onImageError; final TextStyle linkStyle; + /// Properties for the Image widget that gets rendered by the rich text parser + final ImageProperties imageProperties; + final OnImageTap onImageTap; + /// Either return a custom widget for specific node types or return null to /// fallback to the default rendering. final CustomRender customRender; @@ -61,6 +68,8 @@ class Html extends StatelessWidget { html: data, onImageError: onImageError, linkStyle: linkStyle, + imageProperties: imageProperties, + onImageTap: onImageTap, ) : HtmlOldParser( width: width, From 86ccb5c68ea7973d052440bce7c115a6c1870192 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 18 May 2019 12:39:27 +0200 Subject: [PATCH 4/7] Removed gaplessPlayback attribute --- lib/html_parser.dart | 126 ++++++++++++-------------------------- lib/image_properties.dart | 31 +++++----- 2 files changed, 55 insertions(+), 102 deletions(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 57783e6fa4..6bef69e987 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -32,11 +32,7 @@ class LinkTextSpan extends TextSpan { final String url; LinkTextSpan( - {TextStyle style, - this.url, - String text, - OnLinkTap onLinkTap, - List children}) + {TextStyle style, this.url, String text, OnLinkTap onLinkTap, List children}) : super( style: style, text: text, @@ -80,11 +76,7 @@ class BlockText extends StatelessWidget { final Decoration decoration; BlockText( - {@required this.child, - this.padding, - this.margin, - this.leadingChar = '', - this.decoration}); + {@required this.child, this.padding, this.margin, this.leadingChar = '', this.decoration}); @override Widget build(BuildContext context) { @@ -255,8 +247,7 @@ class HtmlRichTextParser extends StatelessWidget { bool _hasBlockChild(dom.Node node, {bool ignoreSelf = true}) { bool retval = false; if (node is dom.Element) { - if (_supportedBlockElements.contains(node.localName) && !ignoreSelf) - return true; + if (_supportedBlockElements.contains(node.localName) && !ignoreSelf) return true; node.nodes.forEach((dom.Node node) { if (_hasBlockChild(node, ignoreSelf: false)) retval = true; }); @@ -291,8 +282,7 @@ class HtmlRichTextParser extends StatelessWidget { if (w is BlockText) { if (w.child.text == null) return; if ((w.child.text.text == null || w.child.text.text.isEmpty) && - (w.child.text.children == null || w.child.text.children.isEmpty)) - return; + (w.child.text.children == null || w.child.text.children.isEmpty)) return; } else if (w is LinkBlock) { if (w.children.isEmpty) return; } else if (w is LinkTextSpan) { @@ -319,8 +309,7 @@ class HtmlRichTextParser extends StatelessWidget { // function can add child nodes to the parent if it should // // each iteration creates a new parseContext as a copy of the previous one if it needs to - void _parseNode( - dom.Node node, ParseContext parseContext, BuildContext buildContext) { + void _parseNode(dom.Node node, ParseContext parseContext, BuildContext buildContext) { // TEXT ONLY NODES // a text only node is a child of a tag with no inner html if (node is dom.Text) { @@ -359,18 +348,15 @@ class HtmlRichTextParser extends StatelessWidget { // debugPrint("Plain Text Node: '$finalText'"); // create a span by default - TextSpan span = TextSpan( - text: finalText, - children: [], - style: parseContext.childStyle); + TextSpan span = + TextSpan(text: finalText, children: [], style: parseContext.childStyle); // in this class, a ParentElement must be a BlockText, LinkTextSpan, Row, Column, TextSpan // the parseContext might actually be a block level style element, so we // need to honor the indent and styling specified by that block style. // e.g. ol, ul, blockquote - bool treatLikeBlock = - ['blockquote', 'ul', 'ol'].indexOf(parseContext.blockType) != -1; + bool treatLikeBlock = ['blockquote', 'ul', 'ol'].indexOf(parseContext.blockType) != -1; // if there is no parentElement, contain the span in a BlockText if (parseContext.parentElement == null) { @@ -381,18 +367,15 @@ class HtmlRichTextParser extends StatelessWidget { Decoration decoration; if (parseContext.blockType == 'blockquote') { decoration = BoxDecoration( - border: - Border(left: BorderSide(color: Colors.black38, width: 2.0)), + border: Border(left: BorderSide(color: Colors.black38, width: 2.0)), ); parseContext.childStyle = parseContext.childStyle.merge(TextStyle( fontStyle: FontStyle.italic, )); } BlockText blockText = BlockText( - margin: EdgeInsets.only( - top: 8.0, - bottom: 8.0, - left: parseContext.indentLevel * indentSize), + margin: + EdgeInsets.only(top: 8.0, bottom: 8.0, left: parseContext.indentLevel * indentSize), padding: EdgeInsets.all(2.0), decoration: decoration, child: RichText( @@ -402,8 +385,7 @@ class HtmlRichTextParser extends StatelessWidget { ); parseContext.rootWidgetList.add(blockText); } else { - parseContext.rootWidgetList - .add(BlockText(child: RichText(text: span))); + parseContext.rootWidgetList.add(BlockText(child: RichText(text: span))); } // this allows future items to be added as children of this item @@ -413,8 +395,7 @@ class HtmlRichTextParser extends StatelessWidget { } else if (parseContext.parentElement is LinkTextSpan) { // add this node to the parent as another LinkTextSpan parseContext.parentElement.children.add(LinkTextSpan( - style: - parseContext.parentElement.style.merge(parseContext.childStyle), + style: parseContext.parentElement.style.merge(parseContext.childStyle), url: parseContext.parentElement.url, text: finalText, onLinkTap: onLinkTap, @@ -447,21 +428,18 @@ class HtmlRichTextParser extends StatelessWidget { //"b","i","em","strong","code","u","small","abbr","acronym" case "b": case "strong": - childStyle = - childStyle.merge(TextStyle(fontWeight: FontWeight.bold)); + childStyle = childStyle.merge(TextStyle(fontWeight: FontWeight.bold)); break; case "i": case "address": case "em": - childStyle = - childStyle.merge(TextStyle(fontStyle: FontStyle.italic)); + childStyle = childStyle.merge(TextStyle(fontStyle: FontStyle.italic)); break; case "code": childStyle = childStyle.merge(TextStyle(fontFamily: 'monospace')); break; case "u": - childStyle = childStyle - .merge(TextStyle(decoration: TextDecoration.underline)); + childStyle = childStyle.merge(TextStyle(decoration: TextDecoration.underline)); break; case "abbr": case "acronym": @@ -518,8 +496,7 @@ class HtmlRichTextParser extends StatelessWidget { if (_hasBlockChild(node)) { LinkBlock linkContainer = LinkBlock( url: url, - margin: EdgeInsets.only( - left: parseContext.indentLevel * indentSize), + margin: EdgeInsets.only(left: parseContext.indentLevel * indentSize), onLinkTap: onLinkTap, children: [], ); @@ -538,8 +515,7 @@ class HtmlRichTextParser extends StatelessWidget { } else { // start a new block element for this link and its text BlockText blockElement = BlockText( - margin: EdgeInsets.only( - left: parseContext.indentLevel * indentSize, top: 10.0), + margin: EdgeInsets.only(left: parseContext.indentLevel * indentSize, top: 10.0), child: RichText(text: span), ); parseContext.rootWidgetList.add(blockElement); @@ -551,10 +527,8 @@ class HtmlRichTextParser extends StatelessWidget { break; case "br": - if (parseContext.parentElement != null && - parseContext.parentElement is TextSpan) { - parseContext.parentElement.children - .add(TextSpan(text: '\n', children: [])); + if (parseContext.parentElement != null && parseContext.parentElement is TextSpan) { + parseContext.parentElement.children.add(TextSpan(text: '\n', children: [])); } break; @@ -566,8 +540,7 @@ class HtmlRichTextParser extends StatelessWidget { children: [], ); nextContext.rootWidgetList.add(Container( - margin: EdgeInsets.symmetric(vertical: 12.0), - child: nextContext.parentElement)); + margin: EdgeInsets.symmetric(vertical: 12.0), child: nextContext.parentElement)); break; // we don't handle tbody, thead, or tfoot elements separately for now @@ -583,11 +556,8 @@ class HtmlRichTextParser extends StatelessWidget { colspan = int.tryParse(node.attributes['colspan']); } nextContext.childStyle = nextContext.childStyle.merge(TextStyle( - fontWeight: (node.localName == 'th') - ? FontWeight.bold - : FontWeight.normal)); - RichText text = - RichText(text: TextSpan(text: '', children: [])); + fontWeight: (node.localName == 'th') ? FontWeight.bold : FontWeight.normal)); + RichText text = RichText(text: TextSpan(text: '', children: [])); Expanded cell = Expanded( flex: colspan, child: Container(padding: EdgeInsets.all(1.0), child: text), @@ -642,8 +612,7 @@ class HtmlRichTextParser extends StatelessWidget { switch (node.localName) { case "hr": - parseContext.rootWidgetList - .add(Divider(height: 1.0, color: Colors.black38)); + parseContext.rootWidgetList.add(Divider(height: 1.0, color: Colors.black38)); break; case "img": if (node.attributes['src'] != null) { @@ -666,7 +635,6 @@ class HtmlRichTextParser extends StatelessWidget { scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, centerSlice: imageProperties?.centerSlice, - gaplessPlayback: imageProperties?.gaplessPlayback ?? false, filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, alignment: imageProperties?.alignment ?? Alignment.center, colorBlendMode: imageProperties?.colorBlendMode, @@ -692,7 +660,6 @@ class HtmlRichTextParser extends StatelessWidget { scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, centerSlice: imageProperties?.centerSlice, - gaplessPlayback: imageProperties?.gaplessPlayback ?? false, filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, alignment: imageProperties?.alignment ?? Alignment.center, colorBlendMode: imageProperties?.colorBlendMode, @@ -728,8 +695,7 @@ class HtmlRichTextParser extends StatelessWidget { leadingChar = parseContext.listCount.toString() + '.'; } BlockText blockText = BlockText( - margin: EdgeInsets.only( - left: parseContext.indentLevel * indentSize, top: 3.0), + margin: EdgeInsets.only(left: parseContext.indentLevel * indentSize, top: 3.0), child: RichText( text: TextSpan( text: '', @@ -790,8 +756,7 @@ class HtmlRichTextParser extends StatelessWidget { Decoration decoration; if (parseContext.blockType == 'blockquote') { decoration = BoxDecoration( - border: - Border(left: BorderSide(color: Colors.black38, width: 2.0)), + border: Border(left: BorderSide(color: Colors.black38, width: 2.0)), ); nextContext.childStyle = nextContext.childStyle.merge(TextStyle( fontStyle: FontStyle.italic, @@ -800,9 +765,7 @@ class HtmlRichTextParser extends StatelessWidget { BlockText blockText = BlockText( margin: _customEdgeInsets ?? EdgeInsets.only( - top: 8.0, - bottom: 8.0, - left: parseContext.indentLevel * indentSize), + top: 8.0, bottom: 8.0, left: parseContext.indentLevel * indentSize), padding: EdgeInsets.all(2.0), decoration: decoration, child: RichText( @@ -1012,8 +975,7 @@ class HtmlOldParser extends StatelessWidget { Widget _parseNode(dom.Node node) { if (customRender != null) { - final Widget customWidget = - customRender(node, _parseNodeList(node.nodes)); + final Widget customWidget = customRender(node, _parseNodeList(node.nodes)); if (customWidget != null) { return customWidget; } @@ -1103,9 +1065,8 @@ class HtmlOldParser extends StatelessWidget { child: Wrap( children: _parseNodeList(node.nodes), ), - textDirection: node.attributes["dir"] == "rtl" - ? TextDirection.rtl - : TextDirection.ltr, + textDirection: + node.attributes["dir"] == "rtl" ? TextDirection.rtl : TextDirection.ltr, ); } //Direction attribute is required, just render the text normally now. @@ -1123,8 +1084,7 @@ class HtmlOldParser extends StatelessWidget { ); case "blockquote": return Padding( - padding: - EdgeInsets.fromLTRB(40.0, blockSpacing, 40.0, blockSpacing), + padding: EdgeInsets.fromLTRB(40.0, blockSpacing, 40.0, blockSpacing), child: Container( width: width, child: Wrap( @@ -1247,8 +1207,7 @@ class HtmlOldParser extends StatelessWidget { ); case "figure": return Padding( - padding: - EdgeInsets.fromLTRB(40.0, blockSpacing, 40.0, blockSpacing), + padding: EdgeInsets.fromLTRB(40.0, blockSpacing, 40.0, blockSpacing), child: Column( children: _parseNodeList(node.nodes), crossAxisAlignment: CrossAxisAlignment.center, @@ -1378,13 +1337,12 @@ class HtmlOldParser extends StatelessWidget { if (node.attributes['src'].startsWith("data:image") && node.attributes['src'].contains("base64,")) { precacheImage( - MemoryImage(base64.decode( - node.attributes['src'].split("base64,")[1].trim())), + MemoryImage(base64.decode(node.attributes['src'].split("base64,")[1].trim())), context, onError: onImageError, ); - return Image.memory(base64.decode( - node.attributes['src'].split("base64,")[1].trim())); + return Image.memory( + base64.decode(node.attributes['src'].split("base64,")[1].trim())); } precacheImage( NetworkImage(node.attributes['src']), @@ -1396,8 +1354,7 @@ class HtmlOldParser extends StatelessWidget { //Temp fix for https://github.com/flutter/flutter/issues/736 if (node.attributes['alt'].endsWith(" ")) { return Container( - padding: EdgeInsets.only(right: 2.0), - child: Text(node.attributes['alt'])); + padding: EdgeInsets.only(right: 2.0), child: Text(node.attributes['alt'])); } else { return Text(node.attributes['alt']); } @@ -1618,9 +1575,8 @@ class HtmlOldParser extends StatelessWidget { painter = new TextPainter( text: new TextSpan( text: node.text, - style: parentStyle.merge(TextStyle( - fontSize: - parentStyle.fontSize * OFFSET_TAGS_FONT_SIZE_FACTOR)), + style: parentStyle.merge( + TextStyle(fontSize: parentStyle.fontSize * OFFSET_TAGS_FONT_SIZE_FACTOR)), ), textDirection: TextDirection.ltr); painter.layout(); @@ -1651,8 +1607,7 @@ class HtmlOldParser extends StatelessWidget { top: node.localName == "sub" ? null : 0, ), style: TextStyle( - fontSize: parentStyle.fontSize * - OFFSET_TAGS_FONT_SIZE_FACTOR), + fontSize: parentStyle.fontSize * OFFSET_TAGS_FONT_SIZE_FACTOR), ) ], ) @@ -1767,8 +1722,7 @@ class HtmlOldParser extends StatelessWidget { String finalText = trimStringHtml(node.text); //Temp fix for https://github.com/flutter/flutter/issues/736 if (finalText.endsWith(" ")) { - return Container( - padding: EdgeInsets.only(right: 2.0), child: Text(finalText)); + return Container(padding: EdgeInsets.only(right: 2.0), child: Text(finalText)); } else { return Text(finalText); } diff --git a/lib/image_properties.dart b/lib/image_properties.dart index 6affd428f9..62dfab5b63 100644 --- a/lib/image_properties.dart +++ b/lib/image_properties.dart @@ -13,23 +13,22 @@ class ImageProperties { final ImageRepeat repeat; final Rect centerSlice; final bool matchTextDirection; - final bool gaplessPlayback; final FilterQuality filterQuality; final double scale; - const ImageProperties( - {this.scale = 1, - this.semanticLabel, - this.excludeFromSemantics = false, - this.width, - this.height, - this.color, - this.colorBlendMode, - this.fit, - this.alignment = Alignment.center, - this.repeat = ImageRepeat.noRepeat, - this.centerSlice, - this.matchTextDirection = false, - this.gaplessPlayback = false, - this.filterQuality = FilterQuality.low}); + const ImageProperties({ + this.scale = 1, + this.semanticLabel, + this.excludeFromSemantics = false, + this.width, + this.height, + this.color, + this.colorBlendMode, + this.fit, + this.alignment = Alignment.center, + this.repeat = ImageRepeat.noRepeat, + this.centerSlice, + this.matchTextDirection = false, + this.filterQuality = FilterQuality.low, + }); } From af4ca1d44c64fb8a69ba30322d4079a7fae6ade9 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 18 May 2019 12:47:06 +0200 Subject: [PATCH 5/7] Changed InkWell to GestureDetector --- lib/html_parser.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 6bef69e987..aac9da1616 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -627,7 +627,7 @@ class HtmlRichTextParser extends StatelessWidget { buildContext, onError: onImageError, ); - parseContext.rootWidgetList.add(InkWell( + parseContext.rootWidgetList.add(GestureDetector( child: Image.memory( base64.decode(node.attributes['src'].split("base64,")[1].trim()), width: imageProperties?.width, @@ -652,7 +652,7 @@ class HtmlRichTextParser extends StatelessWidget { buildContext, onError: onImageError, ); - parseContext.rootWidgetList.add(InkWell( + parseContext.rootWidgetList.add(GestureDetector( child: Image.network( node.attributes['src'], width: imageProperties?.width, From 446cb74d0ddf0f1e6dc16543d983dc89e504ba88 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 19 May 2019 12:21:27 +0200 Subject: [PATCH 6/7] Added parser for 'width' and 'height' attributes for img --- lib/html_parser.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index aac9da1616..db60d42ad2 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -630,8 +630,14 @@ class HtmlRichTextParser extends StatelessWidget { parseContext.rootWidgetList.add(GestureDetector( child: Image.memory( base64.decode(node.attributes['src'].split("base64,")[1].trim()), - width: imageProperties?.width, - height: imageProperties?.height, + width: imageProperties?.width ?? + ((node.attributes['width'] != null) + ? double.parse(node.attributes['width']) + : null), + height: imageProperties?.height ?? + ((node.attributes['height'] != null) + ? double.parse(node.attributes['height']) + : null), scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, centerSlice: imageProperties?.centerSlice, @@ -655,8 +661,14 @@ class HtmlRichTextParser extends StatelessWidget { parseContext.rootWidgetList.add(GestureDetector( child: Image.network( node.attributes['src'], - width: imageProperties?.width, - height: imageProperties?.height, + width: imageProperties?.width ?? + ((node.attributes['width'] != null) + ? double.parse(node.attributes['width']) + : null), + height: imageProperties?.height ?? + ((node.attributes['height'] != null) + ? double.parse(node.attributes['height']) + : null), scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, centerSlice: imageProperties?.centerSlice, From 6962f11f9095a279c0c7b1055124b51a628ae65d Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 19 May 2019 12:56:53 +0200 Subject: [PATCH 7/7] Added support for 'width' and 'height' for img element, and showImages --- lib/flutter_html.dart | 46 +++++----- lib/html_parser.dart | 206 ++++++++++++++++++++++-------------------- 2 files changed, 132 insertions(+), 120 deletions(-) diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index b0c2fd68f2..259d594f66 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -5,27 +5,28 @@ import 'package:flutter_html/html_parser.dart'; import 'image_properties.dart'; class Html extends StatelessWidget { - Html( - {Key key, - @required this.data, - this.padding, - this.backgroundColor, - this.defaultTextStyle, - this.onLinkTap, - this.renderNewlines = false, - this.customRender, - this.customEdgeInsets, - this.customTextStyle, - this.blockSpacing = 14.0, - this.useRichText = false, - this.onImageError, - this.linkStyle = const TextStyle( - decoration: TextDecoration.underline, - color: Colors.blueAccent, - decorationColor: Colors.blueAccent), - this.imageProperties, - this.onImageTap}) - : super(key: key); + Html({ + Key key, + @required this.data, + this.padding, + this.backgroundColor, + this.defaultTextStyle, + this.onLinkTap, + this.renderNewlines = false, + this.customRender, + this.customEdgeInsets, + this.customTextStyle, + this.blockSpacing = 14.0, + this.useRichText = false, + this.onImageError, + this.linkStyle = const TextStyle( + decoration: TextDecoration.underline, + color: Colors.blueAccent, + decorationColor: Colors.blueAccent), + this.imageProperties, + this.onImageTap, + this.showImages = true, + }) : super(key: key); final String data; final EdgeInsetsGeometry padding; @@ -41,6 +42,7 @@ class Html extends StatelessWidget { /// Properties for the Image widget that gets rendered by the rich text parser final ImageProperties imageProperties; final OnImageTap onImageTap; + final bool showImages; /// Either return a custom widget for specific node types or return null to /// fallback to the default rendering. @@ -70,6 +72,7 @@ class Html extends StatelessWidget { linkStyle: linkStyle, imageProperties: imageProperties, onImageTap: onImageTap, + showImages: showImages, ) : HtmlOldParser( width: width, @@ -80,6 +83,7 @@ class Html extends StatelessWidget { blockSpacing: blockSpacing, onImageError: onImageError, linkStyle: linkStyle, + showImages: showImages, ), ), ); diff --git a/lib/html_parser.dart b/lib/html_parser.dart index db60d42ad2..e5c658afa1 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -151,6 +151,7 @@ class HtmlRichTextParser extends StatelessWidget { ), this.imageProperties, this.onImageTap, + this.showImages = true, }); final double indentSize = 10.0; @@ -165,6 +166,7 @@ class HtmlRichTextParser extends StatelessWidget { final TextStyle linkStyle; final ImageProperties imageProperties; final OnImageTap onImageTap; + final bool showImages; // style elements set a default style // for all child nodes @@ -615,86 +617,88 @@ class HtmlRichTextParser extends StatelessWidget { parseContext.rootWidgetList.add(Divider(height: 1.0, color: Colors.black38)); break; case "img": - if (node.attributes['src'] != null) { - if (node.attributes['src'].startsWith("data:image") && - node.attributes['src'].contains("base64,")) { - precacheImage( - MemoryImage( - base64.decode( - node.attributes['src'].split("base64,")[1].trim(), + if (showImages) { + if (node.attributes['src'] != null) { + if (node.attributes['src'].startsWith("data:image") && + node.attributes['src'].contains("base64,")) { + precacheImage( + MemoryImage( + base64.decode( + node.attributes['src'].split("base64,")[1].trim(), + ), ), - ), - buildContext, - onError: onImageError, - ); - parseContext.rootWidgetList.add(GestureDetector( - child: Image.memory( - base64.decode(node.attributes['src'].split("base64,")[1].trim()), - width: imageProperties?.width ?? - ((node.attributes['width'] != null) - ? double.parse(node.attributes['width']) - : null), - height: imageProperties?.height ?? - ((node.attributes['height'] != null) - ? double.parse(node.attributes['height']) - : null), - scale: imageProperties?.scale ?? 1.0, - matchTextDirection: imageProperties?.matchTextDirection ?? false, - centerSlice: imageProperties?.centerSlice, - filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, - alignment: imageProperties?.alignment ?? Alignment.center, - colorBlendMode: imageProperties?.colorBlendMode, - fit: imageProperties?.fit, - color: imageProperties?.color, - repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, - semanticLabel: imageProperties?.semanticLabel, - excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, - ), - onTap: onImageTap, - )); - } else { - precacheImage( - NetworkImage(node.attributes['src']), - buildContext, - onError: onImageError, - ); - parseContext.rootWidgetList.add(GestureDetector( - child: Image.network( - node.attributes['src'], - width: imageProperties?.width ?? - ((node.attributes['width'] != null) - ? double.parse(node.attributes['width']) - : null), - height: imageProperties?.height ?? - ((node.attributes['height'] != null) - ? double.parse(node.attributes['height']) - : null), - scale: imageProperties?.scale ?? 1.0, - matchTextDirection: imageProperties?.matchTextDirection ?? false, - centerSlice: imageProperties?.centerSlice, - filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, - alignment: imageProperties?.alignment ?? Alignment.center, - colorBlendMode: imageProperties?.colorBlendMode, - fit: imageProperties?.fit, - color: imageProperties?.color, - repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, - semanticLabel: imageProperties?.semanticLabel, - excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, - ), - onTap: onImageTap, - )); - } - if (node.attributes['alt'] != null) { - parseContext.rootWidgetList.add(BlockText( - margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), - padding: EdgeInsets.all(0.0), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: node.attributes['alt'], - style: nextContext.childStyle, - children: [], - )))); + buildContext, + onError: onImageError, + ); + parseContext.rootWidgetList.add(GestureDetector( + child: Image.memory( + base64.decode(node.attributes['src'].split("base64,")[1].trim()), + width: imageProperties?.width ?? + ((node.attributes['width'] != null) + ? double.parse(node.attributes['width']) + : null), + height: imageProperties?.height ?? + ((node.attributes['height'] != null) + ? double.parse(node.attributes['height']) + : null), + scale: imageProperties?.scale ?? 1.0, + matchTextDirection: imageProperties?.matchTextDirection ?? false, + centerSlice: imageProperties?.centerSlice, + filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, + alignment: imageProperties?.alignment ?? Alignment.center, + colorBlendMode: imageProperties?.colorBlendMode, + fit: imageProperties?.fit, + color: imageProperties?.color, + repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, + semanticLabel: imageProperties?.semanticLabel, + excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, + ), + onTap: onImageTap, + )); + } else { + precacheImage( + NetworkImage(node.attributes['src']), + buildContext, + onError: onImageError, + ); + parseContext.rootWidgetList.add(GestureDetector( + child: Image.network( + node.attributes['src'], + width: imageProperties?.width ?? + ((node.attributes['width'] != null) + ? double.parse(node.attributes['width']) + : null), + height: imageProperties?.height ?? + ((node.attributes['height'] != null) + ? double.parse(node.attributes['height']) + : null), + scale: imageProperties?.scale ?? 1.0, + matchTextDirection: imageProperties?.matchTextDirection ?? false, + centerSlice: imageProperties?.centerSlice, + filterQuality: imageProperties?.filterQuality ?? FilterQuality.low, + alignment: imageProperties?.alignment ?? Alignment.center, + colorBlendMode: imageProperties?.colorBlendMode, + fit: imageProperties?.fit, + color: imageProperties?.color, + repeat: imageProperties?.repeat ?? ImageRepeat.noRepeat, + semanticLabel: imageProperties?.semanticLabel, + excludeFromSemantics: (imageProperties?.semanticLabel == null) ? true : false, + ), + onTap: onImageTap, + )); + } + if (node.attributes['alt'] != null) { + parseContext.rootWidgetList.add(BlockText( + margin: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), + padding: EdgeInsets.all(0.0), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: node.attributes['alt'], + style: nextContext.childStyle, + children: [], + )))); + } } } break; @@ -876,6 +880,7 @@ class HtmlOldParser extends StatelessWidget { decoration: TextDecoration.underline, color: Colors.blueAccent, decorationColor: Colors.blueAccent), + this.showImages = true, }); final double width; @@ -886,6 +891,7 @@ class HtmlOldParser extends StatelessWidget { final String html; final ImageErrorListener onImageError; final TextStyle linkStyle; + final bool showImages; static const _supportedElements = [ "a", @@ -1345,30 +1351,32 @@ class HtmlOldParser extends StatelessWidget { case "img": return Builder( builder: (BuildContext context) { - if (node.attributes['src'] != null) { - if (node.attributes['src'].startsWith("data:image") && - node.attributes['src'].contains("base64,")) { + if (showImages) { + if (node.attributes['src'] != null) { + if (node.attributes['src'].startsWith("data:image") && + node.attributes['src'].contains("base64,")) { + precacheImage( + MemoryImage(base64.decode(node.attributes['src'].split("base64,")[1].trim())), + context, + onError: onImageError, + ); + return Image.memory( + base64.decode(node.attributes['src'].split("base64,")[1].trim())); + } precacheImage( - MemoryImage(base64.decode(node.attributes['src'].split("base64,")[1].trim())), + NetworkImage(node.attributes['src']), context, onError: onImageError, ); - return Image.memory( - base64.decode(node.attributes['src'].split("base64,")[1].trim())); - } - precacheImage( - NetworkImage(node.attributes['src']), - context, - onError: onImageError, - ); - return Image.network(node.attributes['src']); - } else if (node.attributes['alt'] != null) { - //Temp fix for https://github.com/flutter/flutter/issues/736 - if (node.attributes['alt'].endsWith(" ")) { - return Container( - padding: EdgeInsets.only(right: 2.0), child: Text(node.attributes['alt'])); - } else { - return Text(node.attributes['alt']); + return Image.network(node.attributes['src']); + } else if (node.attributes['alt'] != null) { + //Temp fix for https://github.com/flutter/flutter/issues/736 + if (node.attributes['alt'].endsWith(" ")) { + return Container( + padding: EdgeInsets.only(right: 2.0), child: Text(node.attributes['alt'])); + } else { + return Text(node.attributes['alt']); + } } } return Container();