diff --git a/lib/custom_render.dart b/lib/custom_render.dart index abb1931418..73e016151e 100644 --- a/lib/custom_render.dart +++ b/lib/custom_render.dart @@ -10,6 +10,7 @@ import 'package:flutter_html/src/html_elements.dart'; import 'package:flutter_html/src/utils.dart'; typedef CustomRenderMatcher = bool Function(RenderContext context); +typedef ImageLoadingBuilder = Widget Function(); CustomRenderMatcher tagMatcher(String tag) => (context) { return context.tree.element?.localName == tag; @@ -437,14 +438,15 @@ CustomRender fallbackRender({Style? style, List? children}) => .toList(), )); -Map generateDefaultRenders() { +Map generateDefaultRenders( + {ImageLoadingBuilder? loadingWidget}) { return { blockElementMatcher(): blockElementRender(), listElementMatcher(): listElementRender(), textContentElementMatcher(): textContentElementRender(), dataUriMatcher(): base64ImageRender(), assetUriMatcher(): assetImageRender(), - networkSourceMatcher(): networkImageRender(), + networkSourceMatcher(): networkImageRender(loadingWidget: loadingWidget), replacedElementMatcher(): replacedElementRender(), interactableElementMatcher(): interactableElementRender(), layoutElementMatcher(): layoutElementRender(), diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 1b5d7fe55e..387b635b24 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -60,6 +60,7 @@ class Html extends StatefulWidget { this.onImageError, this.shrinkWrap = false, this.onImageTap, + this.loadingBuilder, this.tagsList = const [], this.style = const {}, }) : documentElement = null, @@ -76,6 +77,7 @@ class Html extends StatefulWidget { this.customRenders = const {}, this.onCssParseError, this.onImageError, + this.loadingBuilder, this.shrinkWrap = false, this.onImageTap, this.tagsList = const [], @@ -97,6 +99,7 @@ class Html extends StatefulWidget { this.onImageError, this.shrinkWrap = false, this.onImageTap, + this.loadingBuilder, this.tagsList = const [], this.style = const {}, }) : data = null, @@ -136,6 +139,12 @@ class Html extends StatefulWidget { /// A list of HTML tags that are the only tags that are rendered. By default, this list is empty and all supported HTML tags are rendered. final List tagsList; + /// A builder that specifies the widget to display to the user while an image + /// is still loading. + /// If this is null, By default to show + /// a [CircularProgressIndicator] while an image loads over the network. + final ImageLoadingBuilder? loadingBuilder; + /// Either return a custom widget for specific node types or return null to /// fallback to the default rendering. final Map customRenders; @@ -192,7 +201,7 @@ class _HtmlState extends State { style: widget.style, customRenders: {} ..addAll(widget.customRenders) - ..addAll(generateDefaultRenders()), + ..addAll(generateDefaultRenders(loadingWidget: widget.loadingBuilder)), tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList, ); } @@ -243,6 +252,7 @@ class SelectableHtml extends StatefulWidget { this.tagsList = const [], this.selectionControls, this.scrollPhysics, + this.loadingBuilder, }) : documentElement = null, assert(data != null), _anchorKey = anchorKey ?? GlobalKey(), @@ -261,6 +271,7 @@ class SelectableHtml extends StatefulWidget { this.tagsList = const [], this.selectionControls, this.scrollPhysics, + this.loadingBuilder, }) : data = null, assert(document != null), documentElement = document!.documentElement, @@ -280,6 +291,7 @@ class SelectableHtml extends StatefulWidget { this.tagsList = const [], this.selectionControls, this.scrollPhysics, + this.loadingBuilder, }) : data = null, assert(documentElement != null), _anchorKey = anchorKey ?? GlobalKey(), @@ -323,6 +335,12 @@ class SelectableHtml extends StatefulWidget { /// Allows you to override the default scrollPhysics for [SelectableText.rich] final ScrollPhysics? scrollPhysics; + /// A builder that specifies the widget to display to the user while an image + /// is still loading. + /// If this is null, By default to show + /// a [CircularProgressIndicator] while an image loads over the network. + final ImageLoadingBuilder? loadingBuilder; + /// Either return a custom widget for specific node types or return null to /// fallback to the default rendering. final Map customRenders; @@ -362,7 +380,8 @@ class _SelectableHtmlState extends State { style: widget.style, customRenders: {} ..addAll(widget.customRenders) - ..addAll(generateDefaultRenders()), + ..addAll( + generateDefaultRenders(loadingWidget: widget.loadingBuilder)), tagsList: widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList, selectionControls: widget.selectionControls, diff --git a/test/flutter_html_test.dart b/test/flutter_html_test.dart index 30acdbe449..4c0eddca39 100644 --- a/test/flutter_html_test.dart +++ b/test/flutter_html_test.dart @@ -10,6 +10,9 @@ void main() { MaterialApp( home: Html( data: "", + loadingBuilder: (){ + return Container(); + }, ), ), ); @@ -24,6 +27,9 @@ void main() { MaterialApp( home: SelectableHtml( data: '', + loadingBuilder: (){ + return Container(); + }, ), ), ); @@ -38,6 +44,9 @@ void main() { MaterialApp( home: Html( data: "Text", + loadingBuilder: (){ + return Container(); + }, ), ), ); @@ -101,6 +110,9 @@ void main() { home: Html( data: "Text", tagsList: Html.tags..add('custom'), + loadingBuilder: (){ + return Container(); + }, ), ), );