diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 7f039470..39ba369f 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -47,7 +47,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} build_apk: - if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'android') || startsWith(github.ref, 'refs/heads/renovate/flutter-') }} + if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'android') || startsWith(github.ref, 'refs/heads/renovate/flutter-') || contains(github.ref, 'fwfh') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -111,7 +111,7 @@ jobs: fingerprint: build_ipa: - if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'ios') }} + if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'ios') || contains(github.ref, 'fwfh') }} runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -243,7 +243,7 @@ jobs: fingerprint: build_macos: - if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'macos') || startsWith(github.ref, 'refs/heads/renovate/flutter-') }} + if: ${{ github.ref == 'refs/heads/master' || contains(github.ref, 'macos') || startsWith(github.ref, 'refs/heads/renovate/flutter-') || contains(github.ref, 'fwfh') }} runs-on: macos-latest steps: - uses: actions/checkout@v4 diff --git a/lib/src/widgets/html.dart b/lib/src/widgets/html.dart index cc095822..027ab2b2 100644 --- a/lib/src/widgets/html.dart +++ b/lib/src/widgets/html.dart @@ -1,8 +1,13 @@ +// ignore_for_file: avoid_renaming_method_parameters, deprecated_member_use + import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +// ignore: implementation_imports +import 'package:flutter_widget_from_html_core/src/internal/core_ops.dart' + show Priority; import 'package:fwfh_webview/fwfh_webview.dart'; import 'package:html_unescape/html_unescape.dart'; import 'package:http/http.dart'; @@ -141,18 +146,13 @@ class TinhteWidgetFactory extends WidgetFactory { BuildOp get smilieOp { return _smilieOp ??= BuildOp( - onTree: (meta, tree) { - final a = meta.element.attributes; + onParsed: (tree) { + final a = tree.element.attributes; final title = a['data-title']; - if (title == null) return; + if (title == null) return tree; final smilie = _kSmilies[title]; - if (smilie == null) return; - final parentTree = tree.parent; - if (parentTree == null) return; - - // TODO: use `replaceWith` when it comes back in v0.9 - TextBit(parentTree, smilie).insertBefore(tree); - tree.detach(); + if (smilie == null) return tree; + return tree.parent.sub()..addText(smilie); }, ); } diff --git a/lib/src/widgets/html/chr.dart b/lib/src/widgets/html/chr.dart index c66bbbba..b618d59a 100644 --- a/lib/src/widgets/html/chr.dart +++ b/lib/src/widgets/html/chr.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + part of '../html.dart'; class Chr { diff --git a/lib/src/widgets/html/galleria.dart b/lib/src/widgets/html/galleria.dart index b1a8ad28..998dc877 100644 --- a/lib/src/widgets/html/galleria.dart +++ b/lib/src/widgets/html/galleria.dart @@ -4,108 +4,110 @@ const kColumns = 3; const kSpacing = 3.0; class Galleria { - final BuildMetadata galleryMeta; + final BuildTree galleryTree; final TinhteWidgetFactory wf; + final _items = []; final _lb = LbTrigger(); - Galleria(this.wf, this.galleryMeta); + Galleria(this.wf, this.galleryTree); BuildOp? _galleriaOp; BuildOp get op { return _galleriaOp ??= BuildOp( - onChild: onChild, - onWidgets: onWidgets, + onVisitChild: onVisitChild, + onRenderBlock: onRenderBlock, ); } - void onChild(BuildMetadata childMeta) { - final e = childMeta.element; - if (e.parent != galleryMeta.element) return; + void onVisitChild(BuildTree _, BuildTree subTree) { + final e = subTree.element; + if (e.parent != galleryTree.element) return; if (e.localName != 'li') return; - - childMeta.register(_GalleriaItem(wf, this, childMeta).op); + subTree.register(_GalleriaItem(wf, this, subTree).op); } - Iterable onWidgets( - BuildMetadata _, Iterable widgets) => - [ - WidgetPlaceholder(this, - child: _GalleriaGrid(widgets.toList(growable: false))) - ]; + Widget onRenderBlock(BuildTree _, WidgetPlaceholder placeholder) => + _items.isNotEmpty ? _GalleriaGrid(_items) : placeholder; } class _GalleriaItem { - final BuildMetadata itemMeta; + final BuildTree itemTree; final Galleria galleria; final WidgetFactory wf; Widget? _description; BuildOp? _descriptionOp; String? _source; - WidgetPlaceholder? _trigger; + Widget? _trigger; BuildOp? _triggerOp; - _GalleriaItem(this.wf, this.galleria, this.itemMeta); + _GalleriaItem(this.wf, this.galleria, this.itemTree); BuildOp? _itemOp; BuildOp get op { return _itemOp ??= BuildOp( - onChild: onChild, - onWidgets: onWidgets, + onVisitChild: onVisitChild, + onRenderBlock: onRenderBlock, ); } - void onChild(BuildMetadata childMeta) { - final e = childMeta.element; - if (e.parent != itemMeta.element) return; + void onVisitChild(BuildTree _, BuildTree subSubTree) { + final e = subSubTree.element; + if (e.parent != itemTree.element) return; switch (e.className) { case 'LbTrigger': _source ??= wf.urlFull(e.attributes['href'] ?? ''); final triggerOp = _triggerOp ??= BuildOp( - onWidgets: (meta, widgets) { - // bypass built-in A tag handling with 0 priority - // and NOT returning anything in `onWidgets` - _trigger = wf.buildColumnPlaceholder(meta, widgets); - return []; - }, - priority: 0, + alwaysRenderBlock: true, + onRenderedBlock: (_, block) => _trigger = block, ); - childMeta.register(triggerOp); + subSubTree.register(triggerOp); break; case 'Tinhte_Gallery_Description': final descriptionOp = _descriptionOp ??= BuildOp( - onWidgets: (meta, widgets) { - meta.tsb.enqueue((p, dynamic _) => p.copyWith( - style: p - .getDependency() - .textTheme - .bodySmall - ?.copyWith(color: kCaptionColor))); - _description = wf.buildColumnPlaceholder(meta, widgets); - return []; + alwaysRenderBlock: true, + onParsed: (descriptionTree) { + return descriptionTree + ..inheritanceResolvers.enqueue( + (style, context) => style.copyWith( + style: Theme.of(context!) + .textTheme + .bodySmall + ?.copyWith(color: kCaptionColor), + ), + null, + ); }, + onRenderedBlock: (_, block) => _description = block, ); - childMeta.register(descriptionOp); + subSubTree.register(descriptionOp); break; } } - Iterable onWidgets( - BuildMetadata _, Iterable widgets) { + Widget onRenderBlock(BuildTree _, WidgetPlaceholder placeholder) { final scopedSource = _source; final scopedTrigger = _trigger; - if (scopedSource == null || scopedTrigger == null) return widgets; + if (scopedSource == null || scopedTrigger == null) return placeholder; final index = galleria._lb.addSource( LbTriggerSource.image(scopedSource), caption: _description, ); - scopedTrigger.wrapWith((context, child) => - galleria._lb.buildGestureDetector(context, child, index)); - return [scopedTrigger]; + galleria._items.add( + WidgetPlaceholder.lazy( + scopedTrigger, + debugLabel: '${itemTree.element.localName}--galleriaItem', + ).wrapWith( + (context, child) => + galleria._lb.buildGestureDetector(context, child, index), + ), + ); + + return widget0; } } diff --git a/lib/src/widgets/html/lb_trigger.dart b/lib/src/widgets/html/lb_trigger.dart index 21277b76..13303993 100644 --- a/lib/src/widgets/html/lb_trigger.dart +++ b/lib/src/widgets/html/lb_trigger.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + part of '../html.dart'; const kCaptionColor = Colors.white70; diff --git a/lib/src/widgets/html/link_expander.dart b/lib/src/widgets/html/link_expander.dart index 7c154c73..dc3de257 100644 --- a/lib/src/widgets/html/link_expander.dart +++ b/lib/src/widgets/html/link_expander.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use + part of '../html.dart'; const kLinkExpanderSquareThumbnailSize = 120.0; @@ -45,8 +47,11 @@ class LinkExpander { break; case 'span': if (e.classes.contains('host')) { - childMeta.tsb.enqueue((p, dynamic _) => - p.copyWith(style: p.style.copyWith(color: Colors.grey))); + childMeta.tsb.enqueue( + (p, dynamic _) => + p.copyWith(style: p.style.copyWith(color: Colors.grey)), + null, + ); } break; } @@ -68,9 +73,10 @@ class LinkExpander { final a = meta.element.attributes; final fullUrl = wf.urlFull(a['href'] ?? ''); final onTap = fullUrl != null ? wf.gestureTapCallback(fullUrl) : null; + final recognizer = wf.buildGestureRecognizer(meta, onTap: onTap); - return WidgetPlaceholder(this) - ..wrapWith((context, previous) { + return WidgetPlaceholder( + builder: (context, previous) { final decoBox = wf.buildDecoration(meta, child, color: Theme.of(context).cardColor); if (decoBox == null) return previous; @@ -84,12 +90,13 @@ class LinkExpander { ); } - if (onTap != null) { - built = wf.buildGestureDetector(meta, built, onTap) ?? built; + if (recognizer != null) { + built = wf.buildGestureDetector(meta, built, recognizer) ?? built; } return built; - }); + }, + ); } Widget _buildSquare(Widget left, Widget right) => _buildBox( @@ -196,8 +203,7 @@ class _LinkExpanderInfo { Iterable onWidgets( BuildMetadata _, Iterable widgets) { final scopedDescription = _description; - final widget = le._info = WidgetPlaceholder<_LinkExpanderInfo>( - this, + final widget = le._info = WidgetPlaceholder( child: Padding( padding: const EdgeInsets.symmetric(vertical: kPostBodyPadding), child: LayoutBuilder( diff --git a/lib/src/widgets/html/photo_compare.dart b/lib/src/widgets/html/photo_compare.dart index fb94fb22..9f42ea82 100644 --- a/lib/src/widgets/html/photo_compare.dart +++ b/lib/src/widgets/html/photo_compare.dart @@ -10,41 +10,58 @@ class PhotoCompare { BuildOp get buildOp => BuildOp( defaultStyles: (_) => {'margin': '0.5em 0'}, - onChild: (childMeta) { - if (childMeta.element.localName == 'img') { - childMeta['display'] = 'block'; + onVisitChild: (tree, subTree) { + if (subTree.element.localName == 'img') { + subTree.register( + BuildOp( + onRenderBlock: (_, placeholder) { + final value = tree.getNonInherited<_Images>(); + if (value == null) { + tree.setNonInherited(_Images([placeholder])); + } else { + value.widgets.add(placeholder); + } + return widget0; + }, + priority: Priority.tagImg + 1, + ), + ); } }, - onWidgets: (meta, widgets) { - final images = []; - for (final widget in widgets) { - if (widget is WidgetPlaceholder) { - images.add(widget); - } - } + onParsed: (tree) { + final replacement = tree.parent.sub(); + final images = tree.getNonInherited<_Images>()?.widgets; + if (images == null || images.length != 2) return tree; - if (images.length != 2) return widgets; - - final a = meta.element.attributes; + final a = tree.element.attributes; final configJson = a['data-config'] ?? ''; - if (configJson.isEmpty) return widgets; + if (configJson.isEmpty) return tree; final Map config = json.decode(configJson); final width = (config['width'] as num?)?.toDouble(); final height = (config['height'] as num?)?.toDouble(); - if (width == null || height == null) return widgets; - - return [ - _PhotoCompareWidget( - aspectRatio: width / height, - image0: images[0], - image1: images[1], - ), - ]; + if (width == null || height == null) return tree; + + return replacement + ..append( + WidgetBit.block( + tree.parent, + _PhotoCompareWidget( + aspectRatio: width / height, + image0: images[0], + image1: images[1], + ), + ), + ); }, ); } +class _Images { + final List widgets; + const _Images(this.widgets); +} + class _PhotoCompareWidget extends StatefulWidget { final double aspectRatio; final Widget image0; diff --git a/pubspec.lock b/pubspec.lock index f8cd062b..ab89b2cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,18 +433,18 @@ packages: dependency: "direct main" description: name: flutter_widget_from_html - sha256: "8d2a9a7979a9c1a5d866d1f4134d2ec2cca78716c112c76803d6a552281405cc" + sha256: "22c911b6ccf82b83e0c457d987bac4e703440fea0fc88dab24f4dfe995a5f33f" url: "https://pub.dev" source: hosted - version: "0.10.6" + version: "0.14.11" flutter_widget_from_html_core: - dependency: transitive + dependency: "direct main" description: name: flutter_widget_from_html_core - sha256: "22140caa191cb4bba0fe4d5e4ad875c7e8a9ba47d61517f56d733019cf76396d" + sha256: "028f4989b9ff4907466af233d50146d807772600d98a3e895662fbdb09c39225" url: "https://pub.dev" source: hosted - version: "0.10.6" + version: "0.14.11" font_awesome_flutter: dependency: "direct main" description: @@ -473,26 +473,26 @@ packages: dependency: transitive description: name: fwfh_cached_network_image - sha256: "3de22aa3a6943c968e0d9fbcba4463b3dbbf7103171d62c84b6c672fb83eebdf" + sha256: "952aea958a5fda7d616cc297ba4bc08427e381459e75526fa375d6d8345630d3" url: "https://pub.dev" source: hosted - version: "0.7.0+7" + version: "0.14.2" fwfh_chewie: dependency: transitive description: name: fwfh_chewie - sha256: "0b51a1c976bb74da5e8e45d545c74cb54a7168ad3938dd77103a7aee485f55fa" + sha256: bbb036cd322ab77dc0edd34cbbf76181681f5e414987ece38745dc4f3d7408ed url: "https://pub.dev" source: hosted - version: "0.7.1+4" + version: "0.14.7" fwfh_just_audio: dependency: transitive description: name: fwfh_just_audio - sha256: "237b93a4cb9f0495a4b51940f361adda2a5abd57231dd44f07459db00144a6cd" + sha256: "4962bc59cf8bbb0a77a55ff56a7b925612b0d8263bc2ede3636b9c86113cb493" url: "https://pub.dev" source: hosted - version: "0.9.0+3" + version: "0.14.2" fwfh_svg: dependency: transitive description: @@ -513,10 +513,10 @@ packages: dependency: "direct main" description: name: fwfh_webview - sha256: "90a8dda0695403cf57abd7e8b83f6fb1f1a12933930a0bf9cac7cafb06e06a18" + sha256: b828bb5ddd4361a866cdb8f1b0de4f3348f332915ecf2f4215ba17e46c656adc url: "https://pub.dev" source: hosted - version: "0.9.0+2" + version: "0.14.8" get_it: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f92bb936..c0b71844 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,8 @@ dependencies: firebase_messaging: ^14.6.5 # ours - flutter_widget_from_html: ^0.10.6 + flutter_widget_from_html: ^0.14.11 + flutter_widget_from_html_core: any fwfh_webview: any the_api: path: ./packages/api