From 1a1b331a41cf84b91021d581d542a54c46f86a3a Mon Sep 17 00:00:00 2001 From: Serge Shkurko Date: Sat, 23 Oct 2021 08:58:00 +0300 Subject: [PATCH 1/7] Added multiplatform support (pub.dev badges) --- lib/flutter_html.dart | 3 ++- lib/html_parser.dart | 2 +- lib/src/navigation_delegate.dart | 35 +++++++++++++++++++++++++ lib/src/replaced_element.dart | 2 +- lib/src/widgets/iframe_mobile.dart | 21 +++++++++++---- lib/src/widgets/iframe_unsupported.dart | 4 +-- lib/src/widgets/iframe_web.dart | 2 +- 7 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 lib/src/navigation_delegate.dart diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 3091f24303..653d862ae0 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -7,7 +7,7 @@ import 'package:flutter_html/image_render.dart'; import 'package:flutter_html/src/html_elements.dart'; import 'package:flutter_html/style.dart'; import 'package:html/dom.dart' as dom; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:flutter_html/src/navigation_delegate.dart'; //export render context api export 'package:flutter_html/html_parser.dart'; @@ -18,6 +18,7 @@ export 'package:flutter_html/src/interactable_element.dart'; export 'package:flutter_html/src/layout_element.dart'; export 'package:flutter_html/src/replaced_element.dart'; export 'package:flutter_html/src/styled_element.dart'; +export 'package:flutter_html/src/navigation_delegate.dart'; //export style api export 'package:flutter_html/style.dart'; diff --git a/lib/html_parser.dart b/lib/html_parser.dart index d41922a479..011197b827 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -13,12 +13,12 @@ import 'package:flutter_html/src/anchor.dart'; import 'package:flutter_html/src/css_parser.dart'; import 'package:flutter_html/src/html_elements.dart'; import 'package:flutter_html/src/layout_element.dart'; +import 'package:flutter_html/src/navigation_delegate.dart'; import 'package:flutter_html/src/utils.dart'; import 'package:flutter_html/style.dart'; import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' as htmlparser; import 'package:numerus/numerus.dart'; -import 'package:webview_flutter/webview_flutter.dart'; typedef OnTap = void Function( String? url, diff --git a/lib/src/navigation_delegate.dart b/lib/src/navigation_delegate.dart new file mode 100644 index 0000000000..d45bb9ed32 --- /dev/null +++ b/lib/src/navigation_delegate.dart @@ -0,0 +1,35 @@ +import 'dart:async'; + +/// Information about a navigation action that is about to be executed. +class NavigationRequest { + NavigationRequest({required this.url, required this.isForMainFrame}); + + /// The URL that will be loaded if the navigation is executed. + final String url; + + /// Whether the navigation request is to be loaded as the main frame. + final bool isForMainFrame; + + @override + String toString() { + return '$runtimeType(url: $url, isForMainFrame: $isForMainFrame)'; + } +} + +/// A decision on how to handle a navigation request. +enum NavigationDecision { + /// Prevent the navigation from taking place. + prevent, + + /// Allow the navigation to take place. + navigate, +} + +/// Decides how to handle a specific navigation request. +/// +/// The returned [NavigationDecision] determines how the navigation described by +/// `navigation` should be handled. +/// +/// See also: [WebView.navigationDelegate]. +typedef FutureOr NavigationDelegate( + NavigationRequest navigation); diff --git a/lib/src/replaced_element.dart b/lib/src/replaced_element.dart index 6c3002dc97..a8527d4eeb 100644 --- a/lib/src/replaced_element.dart +++ b/lib/src/replaced_element.dart @@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_html/html_parser.dart'; import 'package:flutter_html/src/anchor.dart'; import 'package:flutter_html/src/html_elements.dart'; +import 'package:flutter_html/src/navigation_delegate.dart'; import 'package:flutter_html/src/utils.dart'; import 'package:flutter_html/src/widgets/iframe_unsupported.dart' if (dart.library.io) 'package:flutter_html/src/widgets/iframe_mobile.dart' @@ -16,7 +17,6 @@ import 'package:flutter_math_fork/flutter_math.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:html/dom.dart' as dom; import 'package:video_player/video_player.dart'; -import 'package:webview_flutter/webview_flutter.dart'; /// A [ReplacedElement] is a type of [StyledElement] that does not require its [children] to be rendered. /// diff --git a/lib/src/widgets/iframe_mobile.dart b/lib/src/widgets/iframe_mobile.dart index 55223b3478..b571472516 100644 --- a/lib/src/widgets/iframe_mobile.dart +++ b/lib/src/widgets/iframe_mobile.dart @@ -2,9 +2,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/html_parser.dart'; +import 'package:flutter_html/src/navigation_delegate.dart'; import 'package:flutter_html/src/replaced_element.dart'; import 'package:flutter_html/style.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter/webview_flutter.dart' as webview; import 'package:html/dom.dart' as dom; /// [IframeContentElement is a [ReplacedElement] with web content. @@ -30,13 +31,23 @@ class IframeContentElement extends ReplacedElement { return Container( width: width ?? (height ?? 150) * 2, height: height ?? (width ?? 300) / 2, - child: WebView( + child: webview.WebView( initialUrl: src, key: key, javascriptMode: sandboxMode == null || sandboxMode == "allow-scripts" - ? JavascriptMode.unrestricted - : JavascriptMode.disabled, - navigationDelegate: navigationDelegate, + ? webview.JavascriptMode.unrestricted + : webview.JavascriptMode.disabled, + navigationDelegate: (request) async { + final result = await navigationDelegate!(NavigationRequest( + url: request.url, + isForMainFrame: request.isForMainFrame, + )); + if (result == NavigationDecision.prevent) { + return webview.NavigationDecision.prevent; + } else { + return webview.NavigationDecision.navigate; + } + }, gestureRecognizers: { Factory(() => VerticalDragGestureRecognizer()) }, diff --git a/lib/src/widgets/iframe_unsupported.dart b/lib/src/widgets/iframe_unsupported.dart index 4adae1a5d2..38c96eb0ab 100644 --- a/lib/src/widgets/iframe_unsupported.dart +++ b/lib/src/widgets/iframe_unsupported.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_html/html_parser.dart'; +import 'package:flutter_html/src/navigation_delegate.dart'; import 'package:flutter_html/src/replaced_element.dart'; import 'package:flutter_html/style.dart'; -import 'package:webview_flutter/webview_flutter.dart'; import 'package:html/dom.dart' as dom; /// [IframeContentElement is a [ReplacedElement] with web content. @@ -30,4 +30,4 @@ class IframeContentElement extends ReplacedElement { child: Text("Iframes are currently not supported in this environment"), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/iframe_web.dart b/lib/src/widgets/iframe_web.dart index 2ca3c79148..1c9f71f3df 100644 --- a/lib/src/widgets/iframe_web.dart +++ b/lib/src/widgets/iframe_web.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_html/html_parser.dart'; import 'package:flutter_html/shims/dart_ui.dart' as ui; +import 'package:flutter_html/src/navigation_delegate.dart'; import 'package:flutter_html/src/replaced_element.dart'; import 'package:flutter_html/src/utils.dart'; import 'package:flutter_html/style.dart'; -import 'package:webview_flutter/webview_flutter.dart'; import 'package:html/dom.dart' as dom; // ignore: avoid_web_libraries_in_flutter import 'dart:html' as html; From 9cc8a56e91acca56eadcb446181a33c6daa13fc5 Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 26 Nov 2021 10:01:38 +0100 Subject: [PATCH 2/7] Prevent crash on empty tags; fixes #893 --- lib/src/layout_element.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/layout_element.dart b/lib/src/layout_element.dart index b677e9d872..fce0362952 100644 --- a/lib/src/layout_element.dart +++ b/lib/src/layout_element.dart @@ -155,6 +155,11 @@ class TableLayoutElement extends LayoutElement { max(0, columnMax - finalColumnSizes.length), (_) => IntrinsicContentTrackSize()); + if (finalColumnSizes.isEmpty || rowSizes.isEmpty) { + // No actual cells to show + return SizedBox(); + } + return LayoutGrid( gridFit: GridFit.loose, columnSizes: finalColumnSizes, From a81ba3141791bea138993b3eb53f578a764216db Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 26 Nov 2021 10:31:36 +0100 Subject: [PATCH 3/7] Fix merge conflict in list markers --- lib/html_parser.dart | 1 - lib/src/css_parser.dart | 1 + lib/style.dart | 15 +-------------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 64852e348f..72e125014e 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -734,7 +734,6 @@ class HtmlParser extends StatelessWidget { String marker = ""; switch (tree.style.listStyleType!) { case ListStyleType.NONE: - tree.style.markerContent = ''; break; case ListStyleType.CIRCLE: marker = '○'; diff --git a/lib/src/css_parser.dart b/lib/src/css_parser.dart index 1370621b51..3107ec8526 100644 --- a/lib/src/css_parser.dart +++ b/lib/src/css_parser.dart @@ -230,6 +230,7 @@ Style declarationsToStyle(Map> declarations) { break; } } + break; case 'height': style.height = ExpressionMapping.expressionToPaddingLength(value.first) ?? style.height; break; diff --git a/lib/style.dart b/lib/style.dart index 503c6b5de6..50931d532e 100644 --- a/lib/style.dart +++ b/lib/style.dart @@ -540,20 +540,7 @@ class ListStyleType { static const LOWER_ROMAN = ListStyleType("LOWER_ROMAN"); static const UPPER_ROMAN = ListStyleType("UPPER_ROMAN"); static const SQUARE = ListStyleType("SQUARE"); -} - -enum ListStyleType { - LOWER_ALPHA, - UPPER_ALPHA, - LOWER_LATIN, - UPPER_LATIN, - CIRCLE, - DISC, - DECIMAL, - LOWER_ROMAN, - UPPER_ROMAN, - SQUARE, - NONE, + static const NONE = ListStyleType("NONE"); } enum ListStylePosition { From 3da05e5d516be4b328048c1388dd3c9543045a71 Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 26 Nov 2021 10:57:41 +0100 Subject: [PATCH 4/7] Fix whitespace rendering between list items; fixes #878 --- lib/html_parser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index d41922a479..66bf20c805 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -949,7 +949,7 @@ class HtmlParser extends StatelessWidget { if (child is EmptyContentElement || child is EmptyLayoutElement) { toRemove.add(child); } else if (child is TextContentElement - && tree.name == "body" + && (tree.name == "body" || tree.name == "ul") && child.text!.replaceAll(' ', '').isEmpty) { toRemove.add(child); } else if (child is TextContentElement From f2b80b1910c15e2135d3c96ebb334fcac4447ef9 Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 26 Nov 2021 14:34:31 +0100 Subject: [PATCH 5/7] Handle tables with both colspan and rowspan properly; fixes #889 --- lib/src/layout_element.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/src/layout_element.dart b/lib/src/layout_element.dart index fce0362952..43d79cbc2a 100644 --- a/lib/src/layout_element.dart +++ b/lib/src/layout_element.dart @@ -100,6 +100,7 @@ class TableLayoutElement extends LayoutElement { // Place the cells in the rows/columns final cells = []; final columnRowOffset = List.generate(columnMax, (_) => 0); + final columnColspanOffset = List.generate(columnMax, (_) => 0); int rowi = 0; for (var row in rows) { int columni = 0; @@ -107,11 +108,11 @@ class TableLayoutElement extends LayoutElement { if (columni > columnMax - 1 ) { break; } - while (columnRowOffset[columni] > 0) { - columnRowOffset[columni] = columnRowOffset[columni] - 1; - columni++; - } if (child is TableCellElement) { + while (columnRowOffset[columni] > 0) { + columnRowOffset[columni] = columnRowOffset[columni] - 1; + columni += columnColspanOffset[columni].clamp(1, columnMax - columni - 1); + } cells.add(GridPlacement( child: Container( width: double.infinity, @@ -139,6 +140,7 @@ class TableLayoutElement extends LayoutElement { rowSpan: min(child.rowspan, rows.length - rowi), )); columnRowOffset[columni] = child.rowspan - 1; + columnColspanOffset[columni] = child.colspan; columni += child.colspan; } } From 2523233547b37862ace9a955786acc9c070f85fc Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 26 Nov 2021 15:22:51 +0100 Subject: [PATCH 6/7] Fix crashing on negative margins (which we do not support); fixes #838 --- lib/html_parser.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/html_parser.dart b/lib/html_parser.dart index 66bf20c805..6c6f48dd59 100644 --- a/lib/html_parser.dart +++ b/lib/html_parser.dart @@ -1044,7 +1044,7 @@ class ContainerSpan extends StatelessWidget { height: style.height, width: style.width, padding: style.padding, - margin: style.margin, + margin: style.margin?.clamp(EdgeInsets.zero, const EdgeInsets.all(double.infinity)), alignment: shrinkWrap ? null : style.alignment, child: child ?? StyledText( From ab2266e026e6ba7f448393627207fa6ecce3fa3e Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Mon, 29 Nov 2021 09:51:51 +0100 Subject: [PATCH 7/7] Prepare 2.2.0 release --- CHANGELOG.md | 13 +++++++++++++ README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d1fd9070..2aecac6619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## [2.2.0] - November 29, 2021: +* Explicitly declare multiplatform support +* Extended and fixed list-style (marker) support +* Basic support for height/width css properties +* Support changing scroll physics of SelectableText.rich +* Support text transform css property +* Bumped minimum flutter_math_fork version for Flutter 2.5 compatibility +* Fix styling of iframes +* Fix nested font tag application +* Fix whitespace rendering between list items +* Prevent crash on empty
tag and tables with both colspan/rowspan +* Prevent crash on use of negative margins in css + ## [2.1.5] - October 7, 2021: * Ignore unsupported custom style selectors when using fromCss * Fix SVG tag usage inside tables diff --git a/README.md b/README.md index 66dff54e73..d1a04f11f6 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ A Flutter widget for rendering HTML and CSS as Flutter widgets. Add the following to your `pubspec.yaml` file: dependencies: - flutter_html: ^2.1.5 + flutter_html: ^2.2.0 ## Currently Supported HTML Tags: | | | | | | | | | | | | diff --git a/pubspec.yaml b/pubspec.yaml index 3b94c125e8..587187bcf9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_html description: A Flutter widget rendering static HTML and CSS as Flutter widgets. -version: 2.1.5 +version: 2.2.0 homepage: https://github.com/Sub6Resources/flutter_html environment: