diff --git a/CHANGELOG.md b/CHANGELOG.md index cda7b09bbd..2e3415bc40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.11.0] - September 10, 2019: + +* Make it so `width=100%` doesn't throw error. Fixes [#118](https://github.com/Sub6Resources/flutter_html/issues/118). +* You can now set width and/or height in `ImageProperties` to negative to ignore the `width` and/or `height` values from the html. Fixes [#97](https://github.com/Sub6Resources/flutter_html/issues/97) +* The `img` `alt` property now renders correctly when the image fails to load and with the correct style. Fixes [#96](https://github.com/Sub6Resources/flutter_html/issues/96) +* Add partial support for `sub` tag. +* Add new option: `shrinkToFit` ([#148](https://github.com/Sub6Resources/flutter_html/pull/148)). Fixes [#75](https://github.com/Sub6Resources/flutter_html/issues/75). + ## [0.10.4] - June 22, 2019: * Add support for `customTextStyle` to block and specialty HTML elements. diff --git a/README.md b/README.md index 372be9549d..570ac5822d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A Flutter widget for rendering static html tags as Flutter widgets. (Will render Add the following to your `pubspec.yaml` file: dependencies: - flutter_html: ^0.10.4 + flutter_html: ^0.11.0 ## Currently Supported HTML Tags: `a`, `abbr`, `acronym`, `address`, `article`, `aside`, `b`, `bdi`, `bdo`, `big`, `blockquote`, `body`, `br`, `caption`, `cite`, `code`, `data`, `dd`, `del`, `dfn`, `div`, `dl`, `dt`, `em`, `figcaption`, `figure`, `footer`, `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `header`, `hr`, `i`, `img`, `ins`, `kbd`, `li`, `main`, `mark`, `nav`, `noscript`, `ol`, `p`, `pre`, `q`, `rp`, `rt`, `ruby`, `s`, `samp`, `section`, `small`, `span`, `strike`, `strong`, `sub`, `sup`, `table`, `tbody`, `td`, `template`, `tfoot`, `th`, `thead`, `time`, `tr`, `tt`, `u`, `ul`, `var` diff --git a/example/main.dart b/example/main.dart index 9e91253002..4a5260c9e1 100644 --- a/example/main.dart +++ b/example/main.dart @@ -109,7 +109,7 @@ class _MyHomePageState extends State {
Second nested div
- +
Available on GitHub
diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index bfe3740595..a58ba80f20 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -3,6 +3,7 @@ library flutter_html; import 'package:flutter/material.dart'; import 'package:flutter_html/html_parser.dart'; import 'package:flutter_html/rich_text_parser.dart'; + import 'image_properties.dart'; class Html extends StatelessWidget { @@ -25,6 +26,7 @@ class Html extends StatelessWidget { decoration: TextDecoration.underline, color: Colors.blueAccent, decorationColor: Colors.blueAccent), + this.shrinkToFit = false, this.imageProperties, this.onImageTap, this.showImages = true, @@ -40,6 +42,7 @@ class Html extends StatelessWidget { final bool useRichText; final ImageErrorListener onImageError; final TextStyle linkStyle; + final bool shrinkToFit; /// Properties for the Image widget that gets rendered by the rich text parser final ImageProperties imageProperties; @@ -55,7 +58,7 @@ class Html extends StatelessWidget { @override Widget build(BuildContext context) { - final double width = MediaQuery.of(context).size.width; + final double width = shrinkToFit ? null : MediaQuery.of(context).size.width; return Container( padding: padding, @@ -65,7 +68,7 @@ class Html extends StatelessWidget { style: defaultTextStyle ?? DefaultTextStyle.of(context).style, child: (useRichText) ? HtmlRichTextParser( - width: width, + shrinkToFit: shrinkToFit, onLinkTap: onLinkTap, renderNewlines: renderNewlines, customEdgeInsets: customEdgeInsets, diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 303ad7e7ad..b89006af7f 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -1,9 +1,7 @@ import 'dart:convert'; -import 'package:flutter_html/rich_text_parser.dart'; -import 'image_properties.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_html/rich_text_parser.dart'; import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' as parser; diff --git a/lib/rich_text_parser.dart b/lib/rich_text_parser.dart index af39f02dea..6d62c536db 100644 --- a/lib/rich_text_parser.dart +++ b/lib/rich_text_parser.dart @@ -83,31 +83,25 @@ class BlockText extends StatelessWidget { final RichText child; final EdgeInsets padding; final EdgeInsets margin; - final String leadingChar; final Decoration decoration; + final bool shrinkToFit; BlockText({ @required this.child, + @required this.shrinkToFit, this.padding, this.margin, - this.leadingChar = '', this.decoration, }); @override Widget build(BuildContext context) { return Container( - width: double.infinity, + width: shrinkToFit ? null : double.infinity, padding: this.padding, margin: this.margin, decoration: this.decoration, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - leadingChar.isNotEmpty ? Text(leadingChar) : Container(), - Expanded(child: child), - ], - ), + child: child, ); } } @@ -155,7 +149,7 @@ class ParseContext { class HtmlRichTextParser extends StatelessWidget { HtmlRichTextParser({ - @required this.width, + this.shrinkToFit, this.onLinkTap, this.renderNewlines = false, this.html, @@ -175,7 +169,7 @@ class HtmlRichTextParser extends StatelessWidget { final double indentSize = 10.0; - final double width; + final bool shrinkToFit; final onLinkTap; final bool renderNewlines; final String html; @@ -223,6 +217,7 @@ class HtmlRichTextParser extends StatelessWidget { "time", "span", "big", + "sub", ]; // specialty elements require unique handling @@ -425,6 +420,7 @@ class HtmlRichTextParser extends StatelessWidget { )); } BlockText blockText = BlockText( + shrinkToFit: shrinkToFit, margin: EdgeInsets.only( top: 8.0, bottom: 8.0, @@ -438,8 +434,10 @@ 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), + shrinkToFit: shrinkToFit, + )); } // this allows future items to be added as children of this item @@ -522,6 +520,13 @@ class HtmlRichTextParser extends StatelessWidget { childStyle = childStyle.merge( TextStyle(backgroundColor: Colors.yellow, color: Colors.black)); break; + case "sub": + childStyle = childStyle.merge( + TextStyle( + fontSize: childStyle.fontSize * OFFSET_TAGS_FONT_SIZE_FACTOR, + ), + ); + break; case "del": case "s": case "strike": @@ -599,6 +604,7 @@ class HtmlRichTextParser extends StatelessWidget { } else { // start a new block element for this link and its text BlockText blockElement = BlockText( + shrinkToFit: shrinkToFit, margin: EdgeInsets.only( left: parseContext.indentLevel * indentSize, top: 10.0), child: RichText(text: span), @@ -732,6 +738,15 @@ class HtmlRichTextParser extends StatelessWidget { case "img": if (showImages) { if (node.attributes['src'] != null) { + final width = imageProperties?.width ?? + ((node.attributes['width'] != null) + ? double.tryParse(node.attributes['width']) + : null); + final height = imageProperties?.height ?? + ((node.attributes['height'] != null) + ? double.tryParse(node.attributes['height']) + : null); + if (node.attributes['src'].startsWith("data:image") && node.attributes['src'].contains("base64,")) { precacheImage( @@ -741,20 +756,14 @@ class HtmlRichTextParser extends StatelessWidget { ), ), buildContext, - onError: onImageError, + 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.tryParse(node.attributes['width']) - : null), - height: imageProperties?.height ?? - ((node.attributes['height'] != null) - ? double.tryParse(node.attributes['height']) - : null), + width: (width ?? -1) > 0 ? width : null, + height: (height ?? -1) > 0 ? width : null, scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, @@ -782,19 +791,31 @@ class HtmlRichTextParser extends StatelessWidget { precacheImage( NetworkImage(node.attributes['src']), buildContext, - onError: onImageError, + 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), + frameBuilder: (context, child, frame, _) { + if (node.attributes['alt'] != null && frame == null) { + return BlockText( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: node.attributes['alt'], + style: nextContext.childStyle, + ), + ), + shrinkToFit: shrinkToFit, + ); + } + if (frame != null) { + return child; + } + return Container(); + }, + width: (width ?? -1) > 0 ? width : null, + height: (height ?? -1) > 0 ? height : null, scale: imageProperties?.scale ?? 1.0, matchTextDirection: imageProperties?.matchTextDirection ?? false, @@ -819,19 +840,6 @@ class HtmlRichTextParser extends StatelessWidget { }, )); } - 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; @@ -844,16 +852,18 @@ class HtmlRichTextParser extends StatelessWidget { leadingChar = parseContext.listCount.toString() + '.'; } BlockText blockText = BlockText( + shrinkToFit: shrinkToFit, margin: EdgeInsets.only( left: parseContext.indentLevel * indentSize, top: 3.0), child: RichText( text: TextSpan( - text: '', - style: nextContext.childStyle, - children: [], + text: '$leadingChar ', + style: DefaultTextStyle.of(buildContext).style, + children: [ + TextSpan(text: '', style: nextContext.childStyle) + ], ), ), - leadingChar: '$leadingChar ', ); parseContext.rootWidgetList.add(blockText); nextContext.parentElement = blockText.child.text; @@ -914,6 +924,7 @@ class HtmlRichTextParser extends StatelessWidget { )); } BlockText blockText = BlockText( + shrinkToFit: shrinkToFit, margin: node.localName != 'body' ? _customEdgeInsets ?? EdgeInsets.only( @@ -953,12 +964,6 @@ class HtmlRichTextParser extends StatelessWidget { } } - Paint _getPaint(Color color) { - Paint paint = new Paint(); - paint.color = color; - return paint; - } - String condenseHtmlWhitespace(String stringToTrim) { stringToTrim = stringToTrim.replaceAll("\n", " "); while (stringToTrim.indexOf(" ") != -1) { @@ -966,26 +971,4 @@ class HtmlRichTextParser extends StatelessWidget { } return stringToTrim; } - - bool _isNotFirstBreakTag(dom.Node node) { - int index = node.parentNode.nodes.indexOf(node); - if (index == 0) { - if (node.parentNode == null) { - return false; - } - return _isNotFirstBreakTag(node.parentNode); - } else if (node.parentNode.nodes[index - 1] is dom.Element) { - if ((node.parentNode.nodes[index - 1] as dom.Element).localName == "br") { - return true; - } - return false; - } else if (node.parentNode.nodes[index - 1] is dom.Text) { - if ((node.parentNode.nodes[index - 1] as dom.Text).text.trim() == "") { - return _isNotFirstBreakTag(node.parentNode.nodes[index - 1]); - } else { - return false; - } - } - return false; - } } diff --git a/pubspec.lock b/pubspec.lock index 0ea08348ac..d45ef57534 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,5 +1,5 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: async: dependency: transitive @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.2.0" boolean_selector: dependency: transitive description: @@ -80,14 +80,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.7.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" sky_engine: dependency: transitive description: flutter @@ -134,7 +134,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "0.2.5" typed_data: dependency: transitive description: @@ -150,5 +150,5 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" flutter: ">=0.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0a21f25fdb..9da47f8b9b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_html description: A Flutter widget for rendering static html tags as Flutter widgets. (Will render over 70 different html tags!) -version: 0.10.4 +version: 0.11.0 author: Matthew Whitaker homepage: https://github.com/Sub6Resources/flutter_html