diff --git a/lib/custom_render.dart b/lib/custom_render.dart index 1115c3ae78..a497010cf4 100644 --- a/lib/custom_render.dart +++ b/lib/custom_render.dart @@ -11,39 +11,44 @@ import 'package:flutter_html/src/utils.dart'; typedef CustomRenderMatcher = bool Function(RenderContext context); CustomRenderMatcher tagMatcher(String tag) => (context) { - return context.tree.element?.localName == tag; -}; + return context.tree.element?.localName == tag; + }; CustomRenderMatcher blockElementMatcher() => (context) { - return context.tree.style.display == Display.BLOCK && - (context.tree.children.isNotEmpty || context.tree.element?.localName == "hr"); -}; + return context.tree.style.display == Display.BLOCK && + (context.tree.children.isNotEmpty || + context.tree.element?.localName == "hr"); + }; CustomRenderMatcher listElementMatcher() => (context) { - return context.tree.style.display == Display.LIST_ITEM; -}; + return context.tree.style.display == Display.LIST_ITEM; + }; CustomRenderMatcher replacedElementMatcher() => (context) { - return context.tree is ReplacedElement; -}; + return context.tree is ReplacedElement; + }; -CustomRenderMatcher dataUriMatcher({String? encoding = 'base64', String? mime}) => (context) { - if (context.tree.element?.attributes == null - || _src(context.tree.element!.attributes.cast()) == null) return false; - final dataUri = _dataUriFormat.firstMatch(_src(context.tree.element!.attributes.cast())!); - return dataUri != null && dataUri.namedGroup('mime') != "image/svg+xml" && - (mime == null || dataUri.namedGroup('mime') == mime) && - (encoding == null || dataUri.namedGroup('encoding') == ';$encoding'); -}; +CustomRenderMatcher dataUriMatcher( + {String? encoding = 'base64', String? mime}) => + (context) { + if (context.tree.element?.attributes == null || + _src(context.tree.element!.attributes.cast()) == null) return false; + final dataUri = _dataUriFormat + .firstMatch(_src(context.tree.element!.attributes.cast())!); + return dataUri != null && + dataUri.namedGroup('mime') != "image/svg+xml" && + (mime == null || dataUri.namedGroup('mime') == mime) && + (encoding == null || dataUri.namedGroup('encoding') == ';$encoding'); + }; CustomRenderMatcher networkSourceMatcher({ List schemas: const ["https", "http"], List? domains, String? extension, }) => - (context) { - if (context.tree.element?.attributes.cast() == null - || _src(context.tree.element!.attributes.cast()) == null) return false; + (context) { + if (context.tree.element?.attributes.cast() == null || + _src(context.tree.element!.attributes.cast()) == null) return false; try { final src = Uri.parse(_src(context.tree.element!.attributes.cast())!); return schemas.contains(src.scheme) && @@ -55,34 +60,35 @@ CustomRenderMatcher networkSourceMatcher({ }; CustomRenderMatcher assetUriMatcher() => (context) => - context.tree.element?.attributes.cast() != null - && _src(context.tree.element!.attributes.cast()) != null - && _src(context.tree.element!.attributes.cast())!.startsWith("asset:") - && !_src(context.tree.element!.attributes.cast())!.endsWith(".svg"); + context.tree.element?.attributes.cast() != null && + _src(context.tree.element!.attributes.cast()) != null && + _src(context.tree.element!.attributes.cast())!.startsWith("asset:") && + !_src(context.tree.element!.attributes.cast())!.endsWith(".svg"); CustomRenderMatcher textContentElementMatcher() => (context) { - return context.tree is TextContentElement; -}; + return context.tree is TextContentElement; + }; CustomRenderMatcher interactableElementMatcher() => (context) { - return context.tree is InteractableElement; -}; + return context.tree is InteractableElement; + }; CustomRenderMatcher layoutElementMatcher() => (context) { - return context.tree is LayoutElement; -}; + return context.tree is LayoutElement; + }; CustomRenderMatcher verticalAlignMatcher() => (context) { - return context.tree.style.verticalAlign != null - && context.tree.style.verticalAlign != VerticalAlign.BASELINE; -}; + return context.tree.style.verticalAlign != null && + context.tree.style.verticalAlign != VerticalAlign.BASELINE; + }; CustomRenderMatcher fallbackMatcher() => (context) { - return true; -}; + return true; + }; class CustomRender { - final InlineSpan Function(RenderContext, List Function())? inlineSpan; + final InlineSpan Function(RenderContext, List Function())? + inlineSpan; final Widget Function(RenderContext, List Function())? widget; CustomRender.inlineSpan({ @@ -102,188 +108,242 @@ class SelectableCustomRender extends CustomRender { }) : super.inlineSpan(inlineSpan: null); } -CustomRender blockElementRender({ - Style? style, - List? children}) => +CustomRender blockElementRender({Style? style, List? children}) => CustomRender.inlineSpan(inlineSpan: (context, buildChildren) { - if (context.parser.selectable) { - return TextSpan( - style: context.style.generateTextStyle(), - children: (children as List?) ?? context.tree.children - .expandIndexed((i, childTree) => [ - if (childTree.style.display == Display.BLOCK && - i > 0 && - context.tree.children[i - 1] is ReplacedElement) - TextSpan(text: "\n"), - context.parser.parseTree(context, childTree), - if (i != context.tree.children.length - 1 && - childTree.style.display == Display.BLOCK && - childTree.element?.localName != "html" && - childTree.element?.localName != "body") - TextSpan(text: "\n"), - ]) - .toList(), - ); - } - return WidgetSpan( + if (context.parser.selectable) { + return TextSpan( + style: context.style.generateTextStyle(), + children: (children as List?) ?? + context.tree.children + .expandIndexed((i, childTree) => [ + if (childTree.style.display == Display.BLOCK && + i > 0 && + context.tree.children[i - 1] is ReplacedElement) + TextSpan(text: "\n"), + context.parser.parseTree(context, childTree), + if (i != context.tree.children.length - 1 && + childTree.style.display == Display.BLOCK && + childTree.element?.localName != "html" && + childTree.element?.localName != "body") + TextSpan(text: "\n"), + ]) + .toList(), + ); + } + return WidgetSpan( child: ContainerSpan( - key: context.key, - newContext: context, - style: style ?? context.tree.style, - shrinkWrap: context.parser.shrinkWrap, - children: children ?? context.tree.children + key: context.key, + newContext: context, + style: style ?? context.tree.style, + shrinkWrap: context.parser.shrinkWrap, + children: children ?? + context.tree.children .expandIndexed((i, childTree) => [ - if (context.parser.shrinkWrap && - childTree.style.display == Display.BLOCK && - i > 0 && - context.tree.children[i - 1] is ReplacedElement) - TextSpan(text: "\n"), - context.parser.parseTree(context, childTree), - if (context.parser.shrinkWrap && - i != context.tree.children.length - 1 && - childTree.style.display == Display.BLOCK && - childTree.element?.localName != "html" && - childTree.element?.localName != "body") - TextSpan(text: "\n"), - ]) + if (context.parser.shrinkWrap && + childTree.style.display == Display.BLOCK && + i > 0 && + context.tree.children[i - 1] is ReplacedElement) + TextSpan(text: "\n"), + context.parser.parseTree(context, childTree), + if (context.parser.shrinkWrap && + i != context.tree.children.length - 1 && + childTree.style.display == Display.BLOCK && + childTree.element?.localName != "html" && + childTree.element?.localName != "body") + TextSpan(text: "\n"), + ]) .toList(), - )); + )); }); -CustomRender listElementRender({ - Style? style, - Widget? child, - List? children}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => - WidgetSpan( - child: ContainerSpan( - key: context.key, - newContext: context, - style: style ?? context.tree.style, - shrinkWrap: context.parser.shrinkWrap, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - textDirection: style?.direction ?? context.tree.style.direction, - children: [ - (style?.listStylePosition ?? context.tree.style.listStylePosition) == ListStylePosition.OUTSIDE ? - Padding( - padding: style?.padding?.nonNegative ?? context.tree.style.padding?.nonNegative - ?? EdgeInsets.only(left: (style?.direction ?? context.tree.style.direction) != TextDirection.rtl ? 10.0 : 0.0, - right: (style?.direction ?? context.tree.style.direction) == TextDirection.rtl ? 10.0 : 0.0), - child: style?.markerContent ?? context.style.markerContent - ) : Container(height: 0, width: 0), - Text("\u0020", textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.w400)), - Expanded( - child: Padding( - padding: (style?.listStylePosition ?? context.tree.style.listStylePosition) == ListStylePosition.INSIDE ? - EdgeInsets.only(left: (style?.direction ?? context.tree.style.direction) != TextDirection.rtl ? 10.0 : 0.0, - right: (style?.direction ?? context.tree.style.direction) == TextDirection.rtl ? 10.0 : 0.0) : EdgeInsets.zero, - child: StyledText( - textSpan: TextSpan( - children: _getListElementChildren(style?.listStylePosition ?? context.tree.style.listStylePosition, buildChildren) - ..insertAll(0, context.tree.style.listStylePosition == ListStylePosition.INSIDE ? - [ - WidgetSpan(alignment: PlaceholderAlignment.middle, child: style?.markerContent ?? context.style.markerContent ?? Container(height: 0, width: 0)) - ] : []), - style: style?.generateTextStyle() ?? context.style.generateTextStyle(), - ), - style: style ?? context.style, - renderContext: context, - ) - ) - ) - ], - ), - ), -)); - -CustomRender replacedElementRender({PlaceholderAlignment? alignment, TextBaseline? baseline, Widget? child}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => WidgetSpan( - alignment: alignment ?? (context.tree as ReplacedElement).alignment, - baseline: baseline ?? TextBaseline.alphabetic, - child: child ?? (context.tree as ReplacedElement).toWidget(context)!, -)); +CustomRender listElementRender( + {Style? style, Widget? child, List? children}) => + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => WidgetSpan( + child: ContainerSpan( + key: context.key, + newContext: context, + style: style ?? context.tree.style, + shrinkWrap: context.parser.shrinkWrap, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + textDirection: + style?.direction ?? context.tree.style.direction, + children: [ + (style?.listStylePosition ?? + context.tree.style.listStylePosition) == + ListStylePosition.OUTSIDE + ? Padding( + padding: style?.padding?.nonNegative ?? + context.tree.style.padding?.nonNegative ?? + EdgeInsets.only( + left: (style?.direction ?? + context.tree.style.direction) != + TextDirection.rtl + ? 10.0 + : 0.0, + right: (style?.direction ?? + context.tree.style.direction) == + TextDirection.rtl + ? 10.0 + : 0.0), + child: style?.markerContent ?? + context.style.markerContent) + : Container(height: 0, width: 0), + Text("\u0020", + textAlign: TextAlign.right, + style: TextStyle(fontWeight: FontWeight.w400)), + Expanded( + child: Padding( + padding: (style?.listStylePosition ?? + context.tree.style.listStylePosition) == + ListStylePosition.INSIDE + ? EdgeInsets.only( + left: (style?.direction ?? + context.tree.style.direction) != + TextDirection.rtl + ? 10.0 + : 0.0, + right: (style?.direction ?? + context.tree.style.direction) == + TextDirection.rtl + ? 10.0 + : 0.0) + : EdgeInsets.zero, + child: StyledText( + textSpan: TextSpan( + children: _getListElementChildren( + style?.listStylePosition ?? + context.tree.style.listStylePosition, + buildChildren) + ..insertAll( + 0, + context.tree.style.listStylePosition == + ListStylePosition.INSIDE + ? [ + WidgetSpan( + alignment: + PlaceholderAlignment + .middle, + child: style?.markerContent ?? + context.style + .markerContent ?? + Container( + height: 0, width: 0)) + ] + : []), + style: style?.generateTextStyle() ?? + context.style.generateTextStyle(), + ), + style: style ?? context.style, + renderContext: context, + ))) + ], + ), + ), + )); + +CustomRender replacedElementRender( + {PlaceholderAlignment? alignment, + TextBaseline? baseline, + Widget? child}) => + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => WidgetSpan( + alignment: + alignment ?? (context.tree as ReplacedElement).alignment, + baseline: baseline ?? TextBaseline.alphabetic, + child: + child ?? (context.tree as ReplacedElement).toWidget(context)!, + )); CustomRender textContentElementRender({String? text}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => - TextSpan(text: (text ?? (context.tree as TextContentElement).text).transformed(context.tree.style.textTransform))); - -CustomRender base64ImageRender() => CustomRender.widget(widget: (context, buildChildren) { - final decodedImage = base64.decode(_src(context.tree.element!.attributes.cast())!.split("base64,")[1].trim()); - precacheImage( - MemoryImage(decodedImage), - context.buildContext, - onError: (exception, StackTrace? stackTrace) { - context.parser.onImageError?.call(exception, stackTrace); - }, - ); - final widget = Image.memory( - decodedImage, - frameBuilder: (ctx, child, frame, _) { - if (frame == null) { - return Text(_alt(context.tree.element!.attributes.cast()) ?? "", style: context.style.generateTextStyle()); - } - return child; - }, - ); - return Builder( - key: context.key, - builder: (buildContext) { - return GestureDetector( - child: widget, - onTap: () { - if (MultipleTapGestureDetector.of(buildContext) != null) { - MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); - } - context.parser.onImageTap?.call( - _src(context.tree.element!.attributes.cast())!.split("base64,")[1].trim(), - context, - context.tree.element!.attributes.cast(), - context.tree.element + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => TextSpan( + text: (text ?? (context.tree as TextContentElement).text) + .transformed(context.tree.style.textTransform))); + +CustomRender base64ImageRender() => + CustomRender.widget(widget: (context, buildChildren) { + final decodedImage = base64.decode( + _src(context.tree.element!.attributes.cast())! + .split("base64,")[1] + .trim()); + precacheImage( + MemoryImage(decodedImage), + context.buildContext, + onError: (exception, StackTrace? stackTrace) { + context.parser.onImageError?.call(exception, stackTrace); + }, + ); + final widget = Image.memory( + decodedImage, + frameBuilder: (ctx, child, frame, _) { + if (frame == null) { + return Text(_alt(context.tree.element!.attributes.cast()) ?? "", + style: context.style.generateTextStyle()); + } + return child; + }, + ); + return Builder( + key: context.key, + builder: (buildContext) { + return GestureDetector( + child: widget, + onTap: () { + if (MultipleTapGestureDetector.of(buildContext) != null) { + MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); + } + context.parser.onImageTap?.call( + _src(context.tree.element!.attributes.cast())! + .split("base64,")[1] + .trim(), + context, + context.tree.element!.attributes.cast(), + context.tree.element); + }, ); - }, - ); - } - ); -}); + }); + }); CustomRender assetImageRender({ double? width, double? height, -}) => CustomRender.widget(widget: (context, buildChildren) { - final assetPath = _src(context.tree.element!.attributes.cast())!.replaceFirst('asset:', ''); - final widget = Image.asset( - assetPath, - width: width ?? _width(context.tree.element!.attributes.cast()), - height: height ?? _height(context.tree.element!.attributes.cast()), - frameBuilder: (ctx, child, frame, _) { - if (frame == null) { - return Text(_alt(context.tree.element!.attributes.cast()) ?? "", style: context.style.generateTextStyle()); - } - return child; - }, - ); - return Builder( - key: context.key, - builder: (buildContext) { - return GestureDetector( - child: widget, - onTap: () { - if (MultipleTapGestureDetector.of(buildContext) != null) { - MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); - } - context.parser.onImageTap?.call( - assetPath, - context, - context.tree.element!.attributes.cast(), - context.tree.element +}) => + CustomRender.widget(widget: (context, buildChildren) { + final assetPath = _src(context.tree.element!.attributes.cast())! + .replaceFirst('asset:', ''); + final widget = Image.asset( + assetPath, + width: width ?? _width(context.tree.element!.attributes.cast()), + height: height ?? _height(context.tree.element!.attributes.cast()), + frameBuilder: (ctx, child, frame, _) { + if (frame == null) { + return Text(_alt(context.tree.element!.attributes.cast()) ?? "", + style: context.style.generateTextStyle()); + } + return child; + }, + ); + return Builder( + key: context.key, + builder: (buildContext) { + return GestureDetector( + child: widget, + onTap: () { + if (MultipleTapGestureDetector.of(buildContext) != null) { + MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); + } + context.parser.onImageTap?.call( + assetPath, + context, + context.tree.element!.attributes.cast(), + context.tree.element); + }, ); - }, - ); - } - ); -}); + }); + }); CustomRender networkImageRender({ Map? headers, @@ -292,151 +352,164 @@ CustomRender networkImageRender({ double? height, Widget Function(String?)? altWidget, Widget Function()? loadingWidget, -}) => CustomRender.widget(widget: (context, buildChildren) { - final src = mapUrl?.call(_src(context.tree.element!.attributes.cast())) - ?? _src(context.tree.element!.attributes.cast())!; - Completer completer = Completer(); - if (context.parser.cachedImageSizes[src] != null) { - completer.complete(context.parser.cachedImageSizes[src]); - } else { - Image image = Image.network(src, frameBuilder: (ctx, child, frame, _) { - if (frame == null) { - if (!completer.isCompleted) { - completer.completeError("error"); - } - return child; +}) => + CustomRender.widget(widget: (context, buildChildren) { + final src = mapUrl?.call(_src(context.tree.element!.attributes.cast())) ?? + _src(context.tree.element!.attributes.cast())!; + Completer completer = Completer(); + if (context.parser.cachedImageSizes[src] != null) { + completer.complete(context.parser.cachedImageSizes[src]); } else { - return child; - } - }); - - ImageStreamListener? listener; - listener = ImageStreamListener((ImageInfo imageInfo, bool synchronousCall) { - var myImage = imageInfo.image; - Size size = Size(myImage.width.toDouble(), myImage.height.toDouble()); - if (!completer.isCompleted) { - context.parser.cachedImageSizes[src] = size; - completer.complete(size); - image.image.resolve(ImageConfiguration()).removeListener(listener!); - } - }, onError: (object, stacktrace) { - if (!completer.isCompleted) { - completer.completeError(object); - image.image.resolve(ImageConfiguration()).removeListener(listener!); + Image image = Image.network(src, frameBuilder: (ctx, child, frame, _) { + if (frame == null) { + if (!completer.isCompleted) { + completer.completeError("error"); + } + return child; + } else { + return child; + } + }); + + ImageStreamListener? listener; + listener = + ImageStreamListener((ImageInfo imageInfo, bool synchronousCall) { + var myImage = imageInfo.image; + Size size = Size(myImage.width.toDouble(), myImage.height.toDouble()); + if (!completer.isCompleted) { + context.parser.cachedImageSizes[src] = size; + completer.complete(size); + image.image.resolve(ImageConfiguration()).removeListener(listener!); + } + }, onError: (object, stacktrace) { + if (!completer.isCompleted) { + completer.completeError(object); + image.image.resolve(ImageConfiguration()).removeListener(listener!); + } + }); + + image.image.resolve(ImageConfiguration()).addListener(listener); } - }); - - image.image.resolve(ImageConfiguration()).addListener(listener); - } - final attributes = context.tree.element!.attributes.cast(); - final widget = FutureBuilder( - future: completer.future, - initialData: context.parser.cachedImageSizes[src], - builder: (BuildContext buildContext, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - return Container( - constraints: BoxConstraints( - maxWidth: width ?? _width(attributes) ?? snapshot.data!.width, - maxHeight: - (width ?? _width(attributes) ?? snapshot.data!.width) / - _aspectRatio(attributes, snapshot)), - child: AspectRatio( - aspectRatio: _aspectRatio(attributes, snapshot), - child: Image.network( - src, - headers: headers, - width: width ?? _width(attributes) ?? snapshot.data!.width, - height: height ?? _height(attributes), - frameBuilder: (ctx, child, frame, _) { - if (frame == null) { - return altWidget?.call(_alt(attributes)) ?? - Text(_alt(attributes) ?? "", - style: context.style.generateTextStyle()); + final attributes = + context.tree.element!.attributes.cast(); + final widget = FutureBuilder( + future: completer.future, + initialData: context.parser.cachedImageSizes[src], + builder: (BuildContext buildContext, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return Container( + constraints: BoxConstraints( + maxWidth: width ?? _width(attributes) ?? snapshot.data!.width, + maxHeight: + (width ?? _width(attributes) ?? snapshot.data!.width) / + _aspectRatio(attributes, snapshot)), + child: AspectRatio( + aspectRatio: _aspectRatio(attributes, snapshot), + child: Image.network( + src, + headers: headers, + width: width ?? _width(attributes) ?? snapshot.data!.width, + height: height ?? _height(attributes), + frameBuilder: (ctx, child, frame, _) { + if (frame == null) { + return altWidget?.call(_alt(attributes)) ?? + Text(_alt(attributes) ?? "", + style: context.style.generateTextStyle()); + } + return child; + }, + ), + ), + ); + } else if (snapshot.hasError) { + return altWidget + ?.call(_alt(context.tree.element!.attributes.cast())) ?? + Text(_alt(context.tree.element!.attributes.cast()) ?? "", + style: context.style.generateTextStyle()); + } else { + return loadingWidget?.call() ?? const CircularProgressIndicator(); + } + }, + ); + return Builder( + key: context.key, + builder: (buildContext) { + return GestureDetector( + child: widget, + onTap: () { + if (MultipleTapGestureDetector.of(buildContext) != null) { + MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); } - return child; + context.parser.onImageTap?.call( + src, + context, + context.tree.element!.attributes.cast(), + context.tree.element); }, - ), - ), - ); - } else if (snapshot.hasError) { - return altWidget?.call(_alt(context.tree.element!.attributes.cast())) ?? - Text(_alt(context.tree.element!.attributes.cast()) - ?? "", style: context.style.generateTextStyle()); - } else { - return loadingWidget?.call() ?? const CircularProgressIndicator(); - } - }, - ); - return Builder( - key: context.key, - builder: (buildContext) { - return GestureDetector( - child: widget, - onTap: () { - if (MultipleTapGestureDetector.of(buildContext) != null) { - MultipleTapGestureDetector.of(buildContext)!.onTap?.call(); - } - context.parser.onImageTap?.call( - src, - context, - context.tree.element!.attributes.cast(), - context.tree.element ); - }, - ); - } - ); -}); + }); + }); CustomRender interactableElementRender({List? children}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan( - children: children ?? (context.tree as InteractableElement).children - .map((tree) => context.parser.parseTree(context, tree)) - .map((childSpan) { - return _getInteractableChildren(context, context.tree as InteractableElement, childSpan, - context.style.generateTextStyle().merge(childSpan.style)); - }).toList(), -)); - -CustomRender layoutElementRender({Widget? child}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => WidgetSpan( - child: child ?? (context.tree as LayoutElement).toWidget(context)!, -)); - -CustomRender verticalAlignRender({ - double? verticalOffset, - Style? style, - List? children}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => WidgetSpan( - child: Transform.translate( - key: context.key, - offset: Offset(0, verticalOffset ?? _getVerticalOffset(context.tree)), - child: StyledText( - textSpan: TextSpan( - style: style?.generateTextStyle() ?? context.style.generateTextStyle(), - children: children ?? buildChildren.call(), - ), - style: context.style, - renderContext: context, - ), - ), -)); + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => TextSpan( + children: children ?? + (context.tree as InteractableElement) + .children + .map((tree) => context.parser.parseTree(context, tree)) + .map((childSpan) { + return _getInteractableChildren( + context, + context.tree as InteractableElement, + childSpan, + context.style + .generateTextStyle() + .merge(childSpan.style)); + }).toList(), + )); + +CustomRender layoutElementRender({Widget? child}) => CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => WidgetSpan( + child: child ?? (context.tree as LayoutElement).toWidget(context)!, + )); + +CustomRender verticalAlignRender( + {double? verticalOffset, Style? style, List? children}) => + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => WidgetSpan( + child: Transform.translate( + key: context.key, + offset: Offset( + 0, verticalOffset ?? _getVerticalOffset(context.tree)), + child: StyledText( + textSpan: TextSpan( + style: style?.generateTextStyle() ?? + context.style.generateTextStyle(), + children: children ?? buildChildren.call(), + ), + style: context.style, + renderContext: context, + ), + ), + )); CustomRender fallbackRender({Style? style, List? children}) => - CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan( - style: style?.generateTextStyle() ?? context.style.generateTextStyle(), - children: context.tree.children - .expand((tree) => [ - context.parser.parseTree(context, tree), - if (tree.style.display == Display.BLOCK && - tree.element?.parent?.localName != "th" && - tree.element?.parent?.localName != "td" && - tree.element?.localName != "html" && - tree.element?.localName != "body") - TextSpan(text: "\n"), - ]) - .toList(), -)); + CustomRender.inlineSpan( + inlineSpan: (context, buildChildren) => TextSpan( + style: style?.generateTextStyle() ?? + context.style.generateTextStyle(), + children: context.tree.children + .expand((tree) => [ + context.parser.parseTree(context, tree), + if (tree.style.display == Display.BLOCK && + tree.element?.parent?.localName != "th" && + tree.element?.parent?.localName != "td" && + tree.element?.localName != "html" && + tree.element?.localName != "body") + TextSpan(text: "\n"), + ]) + .toList(), + )); final Map defaultRenders = { blockElementMatcher(): blockElementRender(), @@ -452,45 +525,51 @@ final Map defaultRenders = { fallbackMatcher(): fallbackRender(), }; -List _getListElementChildren(ListStylePosition? position, Function() buildChildren) { +List _getListElementChildren( + ListStylePosition? position, Function() buildChildren) { List children = buildChildren.call(); if (position == ListStylePosition.INSIDE) { final tabSpan = WidgetSpan( - child: Text("\t", textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.w400)), + child: Text("\t", + textAlign: TextAlign.right, + style: TextStyle(fontWeight: FontWeight.w400)), ); children.insert(0, tabSpan); } return children; } -InlineSpan _getInteractableChildren(RenderContext context, InteractableElement tree, InlineSpan childSpan, TextStyle childStyle) { +InlineSpan _getInteractableChildren(RenderContext context, + InteractableElement tree, InlineSpan childSpan, TextStyle childStyle) { if (childSpan is TextSpan) { return TextSpan( text: childSpan.text, children: childSpan.children - ?.map((e) => _getInteractableChildren(context, tree, e, childStyle.merge(childSpan.style))) + ?.map((e) => _getInteractableChildren( + context, tree, e, childStyle.merge(childSpan.style))) .toList(), - style: context.style.generateTextStyle().merge( - childSpan.style == null - ? childStyle - : childStyle.merge(childSpan.style)), + style: context.style.generateTextStyle().merge(childSpan.style == null + ? childStyle + : childStyle.merge(childSpan.style)), semanticsLabel: childSpan.semanticsLabel, recognizer: TapGestureRecognizer() - ..onTap = - context.parser.internalOnAnchorTap != null ? - () => context.parser.internalOnAnchorTap!(tree.href, context, tree.attributes, tree.element) - : null, + ..onTap = context.parser.internalOnAnchorTap != null + ? () => context.parser.internalOnAnchorTap!( + tree.href, context, tree.attributes, tree.element) + : null, ); } else { return WidgetSpan( child: MultipleTapGestureDetector( onTap: context.parser.internalOnAnchorTap != null - ? () => context.parser.internalOnAnchorTap!(tree.href, context, tree.attributes, tree.element) + ? () => context.parser.internalOnAnchorTap!( + tree.href, context, tree.attributes, tree.element) : null, child: GestureDetector( key: context.key, onTap: context.parser.internalOnAnchorTap != null - ? () => context.parser.internalOnAnchorTap!(tree.href, context, tree.attributes, tree.element) + ? () => context.parser.internalOnAnchorTap!( + tree.href, context, tree.attributes, tree.element) : null, child: (childSpan as WidgetSpan).child, ), @@ -499,7 +578,8 @@ InlineSpan _getInteractableChildren(RenderContext context, InteractableElement t } } -final _dataUriFormat = RegExp("^(?data):(?image\/[\\w\+\-\.]+)(?;base64)?\,(?.*)"); +final _dataUriFormat = RegExp( + "^(?data):(?image\/[\\w\+\-\.]+)(?;base64)?\,(?.*)"); double _getVerticalOffset(StyledElement tree) { switch (tree.style.verticalAlign) { @@ -522,12 +602,16 @@ String? _alt(Map attributes) { double? _height(Map attributes) { final heightString = attributes["height"]; - return heightString == null ? heightString as double? : double.tryParse(heightString); + return heightString == null + ? heightString as double? + : double.tryParse(heightString); } double? _width(Map attributes) { final widthString = attributes["width"]; - return widthString == null ? widthString as double? : double.tryParse(widthString); + return widthString == null + ? widthString as double? + : double.tryParse(widthString); } double _aspectRatio( @@ -545,5 +629,6 @@ double _aspectRatio( } extension ClampedEdgeInsets on EdgeInsetsGeometry { - EdgeInsetsGeometry get nonNegative => this.clamp(EdgeInsets.zero, const EdgeInsets.all(double.infinity)); + EdgeInsetsGeometry get nonNegative => + this.clamp(EdgeInsets.zero, const EdgeInsets.all(double.infinity)); } diff --git a/lib/flutter_html.dart b/lib/flutter_html.dart index 9496d9263a..3db68efb2b 100644 --- a/lib/flutter_html.dart +++ b/lib/flutter_html.dart @@ -159,14 +159,19 @@ class _HtmlState extends State { @override void initState() { super.initState(); - documentElement = widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.documentElement!; + documentElement = widget.data != null + ? HtmlParser.parseHTML(widget.data!) + : widget.documentElement!; } @override void didUpdateWidget(Html oldWidget) { super.didUpdateWidget(oldWidget); - if ((widget.data != null && oldWidget.data != widget.data) || oldWidget.documentElement != widget.documentElement) { - documentElement = widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.documentElement!; + if ((widget.data != null && oldWidget.data != widget.data) || + oldWidget.documentElement != widget.documentElement) { + documentElement = widget.data != null + ? HtmlParser.parseHTML(widget.data!) + : widget.documentElement!; } } @@ -333,7 +338,9 @@ class _SelectableHtmlState extends State { @override void initState() { super.initState(); - documentElement = widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.documentElement!; + documentElement = widget.data != null + ? HtmlParser.parseHTML(widget.data!) + : widget.documentElement!; } @override @@ -354,7 +361,8 @@ class _SelectableHtmlState extends State { customRenders: {} ..addAll(widget.customRenders) ..addAll(defaultRenders), - tagsList: widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList, + tagsList: + widget.tagsList.isEmpty ? SelectableHtml.tags : widget.tagsList, selectionControls: widget.selectionControls, scrollPhysics: widget.scrollPhysics, ), diff --git a/packages/flutter_html_all/example/example.md b/packages/flutter_html_all/example/example.md new file mode 100644 index 0000000000..aff9f75ad4 --- /dev/null +++ b/packages/flutter_html_all/example/example.md @@ -0,0 +1,3 @@ +# Example + +### Coming soon... \ No newline at end of file diff --git a/packages/flutter_html_all/lib/flutter_html_all.dart b/packages/flutter_html_all/lib/flutter_html_all.dart index 9fb8378795..2b34957522 100644 --- a/packages/flutter_html_all/lib/flutter_html_all.dart +++ b/packages/flutter_html_all/lib/flutter_html_all.dart @@ -1,3 +1,5 @@ +/// Package flutter_html_all is used to get access to all +/// of the extended features of the flutter_html package. library flutter_html_all; export 'package:flutter_html_audio/flutter_html_audio.dart'; diff --git a/packages/flutter_html_audio/example/example.md b/packages/flutter_html_audio/example/example.md new file mode 100644 index 0000000000..aff9f75ad4 --- /dev/null +++ b/packages/flutter_html_audio/example/example.md @@ -0,0 +1,3 @@ +# Example + +### Coming soon... \ No newline at end of file diff --git a/packages/flutter_html_audio/lib/flutter_html_audio.dart b/packages/flutter_html_audio/lib/flutter_html_audio.dart index 51fc3d70df..2484b8e45d 100644 --- a/packages/flutter_html_audio/lib/flutter_html_audio.dart +++ b/packages/flutter_html_audio/lib/flutter_html_audio.dart @@ -6,16 +6,24 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:video_player/video_player.dart'; import 'package:html/dom.dart' as dom; -typedef AudioControllerCallback = void Function(dom.Element?, ChewieAudioController, VideoPlayerController); +typedef AudioControllerCallback = void Function( + dom.Element?, ChewieAudioController, VideoPlayerController); -CustomRender audioRender({AudioControllerCallback? onControllerCreated}) - => CustomRender.widget(widget: (context, buildChildren) - => AudioWidget(context: context, callback: onControllerCreated)); +/// The CustomRender function for the `