Skip to content

Commit b549768

Browse files
authored
Add support for HtmlWidget.renderMode (daohoangson#484)
WidgetFactory breaking changes: - `buildColumnPlaceholder` removed `trimMarginVertical` named param - `buildColumnWidget` removed `tsh` param - `onTapAnchor` replaced `anchorContext` param with `scrollTo`
1 parent b68fef3 commit b549768

File tree

21 files changed

+967
-115
lines changed

21 files changed

+967
-115
lines changed

.github/workflows/flutter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
- name: Setup Flutter
4242
uses: subosito/flutter-action@v1
4343
- run: ./tool/test.sh --coverage
44-
- run: bash <(curl -s https://codecov.io/bash)
44+
- uses: codecov/codecov-action@v1
4545

4646
ios:
4747
name: iOS Test

demo_app/lib/screens/huge_html.dart

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5193,11 +5193,70 @@ class HugeHtmlScreen extends StatelessWidget {
51935193
ShowPerfIconButton(),
51945194
],
51955195
),
5196+
body: ListView(
5197+
children: [
5198+
ListTile(
5199+
title: Text('renderMode: Column'),
5200+
onTap: () => Navigator.push(
5201+
context, MaterialPageRoute(builder: (_) => _ColumnScreen())),
5202+
),
5203+
ListTile(
5204+
title: Text('renderMode: ListView'),
5205+
onTap: () => Navigator.push(context,
5206+
MaterialPageRoute(builder: (_) => _ListViewScreen())),
5207+
),
5208+
ListTile(
5209+
title: Text('renderMode: SliverList'),
5210+
onTap: () => Navigator.push(context,
5211+
MaterialPageRoute(builder: (_) => _SliverListScreen())),
5212+
),
5213+
],
5214+
),
5215+
);
5216+
}
5217+
5218+
class _ColumnScreen extends StatelessWidget {
5219+
@override
5220+
Widget build(BuildContext context) => Scaffold(
5221+
appBar: AppBar(title: Text('renderMode: Column')),
51965222
body: SingleChildScrollView(
51975223
child: Padding(
51985224
padding: const EdgeInsets.all(8.0),
5199-
child: HtmlWidget(kHtml),
5225+
child: RepaintBoundary(
5226+
child: HtmlWidget(kHtml, renderMode: RenderMode.Column),
5227+
),
52005228
),
52015229
),
52025230
);
52035231
}
5232+
5233+
class _ListViewScreen extends StatelessWidget {
5234+
@override
5235+
Widget build(BuildContext context) => Scaffold(
5236+
appBar: AppBar(title: Text('renderMode: ListView')),
5237+
body: Padding(
5238+
padding: const EdgeInsets.all(8.0),
5239+
child: HtmlWidget(kHtml, renderMode: RenderMode.ListView),
5240+
),
5241+
);
5242+
}
5243+
5244+
class _SliverListScreen extends StatelessWidget {
5245+
@override
5246+
Widget build(BuildContext context) => Scaffold(
5247+
body: CustomScrollView(
5248+
slivers: [
5249+
SliverAppBar(
5250+
title: Text('renderMode: SliverList'),
5251+
floating: true,
5252+
expandedHeight: 200,
5253+
flexibleSpace: Placeholder(),
5254+
),
5255+
SliverPadding(
5256+
padding: const EdgeInsets.all(8.0),
5257+
sliver: HtmlWidget(kHtml, renderMode: RenderMode.SliverList),
5258+
),
5259+
],
5260+
),
5261+
);
5262+
}
4.41 KB
Loading
3.95 KB
Loading
3.58 KB
Loading
4.41 KB
Loading

packages/core/lib/src/core_helpers.dart

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart';
66
import 'package:html/dom.dart' as dom;
77

88
import 'core_html_widget.dart';
9+
import 'core_widget_factory.dart';
910

1011
export 'external/csslib.dart';
1112
export 'widgets/css_sizing.dart';
@@ -46,6 +47,23 @@ typedef CustomStylesBuilder = Map<String, String>? Function(
4647
/// For those needs, a custom [WidgetFactory] is the way to go.
4748
typedef CustomWidgetBuilder = Widget? Function(dom.Element element);
4849

50+
/// A callback to scroll the anchor identified by [id] into the viewport.
51+
///
52+
/// By default, an internal implementation is given to [WidgetFactory.onTapAnchor]
53+
/// when an anchor is tapped to handle the scrolling.
54+
/// A wf subclass can use this to change the [curve], the animation [duration]
55+
/// or even request scrolling to a different anchor.
56+
///
57+
/// The future is resolved after scrolling is completed.
58+
/// It will be `true` if scrolling succeed or `false` otherwise.
59+
typedef EnsureVisible = Future<bool> Function(
60+
String id, {
61+
Curve curve,
62+
Duration duration,
63+
Curve jumpCurve,
64+
Duration jumpDuration,
65+
});
66+
4967
/// A set of values that should trigger rebuild.
5068
class RebuildTriggers {
5169
final List _values;
@@ -77,6 +95,45 @@ class RebuildTriggers {
7795
}
7896
}
7997

98+
/// The HTML body render modes.
99+
enum RenderMode {
100+
/// The body will be rendered as a `Column` widget.
101+
///
102+
/// This is the default render mode.
103+
/// It's good enough for small / medium document and can be used easily.
104+
Column,
105+
106+
/// The body will be rendered as a `ListView` widget.
107+
///
108+
/// It's good for medium / large document in a dedicated page layout
109+
/// (e.g. the HTML document is the only thing on the screen).
110+
ListView,
111+
112+
/// The body will be rendered as a `SliverList` sliver.
113+
///
114+
/// It's good for large / huge document and can be put in the same scrolling
115+
/// context with other contents.
116+
/// A [CustomScrollView] or similar is required for this to work.
117+
SliverList,
118+
}
119+
120+
/// An extension on [Widget] to keep track of anchors.
121+
extension WidgetAnchors on Widget {
122+
static final _anchors = Expando<Iterable<Key>>();
123+
124+
/// Anchor keys of this widget and its children.
125+
Iterable<Key>? get anchors => _anchors[this];
126+
127+
/// Set anchor keys.
128+
bool setAnchorsIfUnset(Iterable<Key>? anchors) {
129+
if (anchors == null) return false;
130+
final existing = _anchors[this];
131+
if (existing != null) return false;
132+
_anchors[this] = anchors;
133+
return true;
134+
}
135+
}
136+
80137
/// A widget builder that supports builder callbacks.
81138
class WidgetPlaceholder<T> extends StatelessWidget {
82139
/// The origin of this widget.
@@ -99,6 +156,8 @@ class WidgetPlaceholder<T> extends StatelessWidget {
99156
built = builder(context, built) ?? widget0;
100157
}
101158

159+
built.setAnchorsIfUnset(anchors);
160+
102161
return built;
103162
}
104163

packages/core/lib/src/core_html_widget.dart

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class HtmlWidget extends StatefulWidget {
2929

3030
/// The callback to handle async build snapshot.
3131
///
32-
/// By default, a [CircularProgressIndicator] will be shown until
32+
/// By default, a platform-dependent indicator will be shown until
3333
/// the widget tree is ready.
3434
/// This default builder doesn't do any error handling
3535
/// (it will just ignore any errors).
@@ -92,6 +92,13 @@ class HtmlWidget extends StatefulWidget {
9292
]);
9393
final RebuildTriggers? _rebuildTriggers;
9494

95+
/// The render mode.
96+
///
97+
/// - [RenderMode.Column] is the default mode, suitable for small / medium document.
98+
/// - [RenderMode.ListView] has better performance as it renders contents lazily.
99+
/// - [RenderMode.SliverList] has similar performance as `ListView` and can be put inside a `CustomScrollView`.
100+
final RenderMode renderMode;
101+
95102
/// The default styling for text elements.
96103
final TextStyle? textStyle;
97104

@@ -112,6 +119,7 @@ class HtmlWidget extends StatefulWidget {
112119
this.onTapImage,
113120
this.onTapUrl,
114121
RebuildTriggers? rebuildTriggers,
122+
this.renderMode = RenderMode.Column,
115123
this.textStyle = const TextStyle(),
116124
}) : _rebuildTriggers = rebuildTriggers,
117125
super(key: key);
@@ -205,6 +213,30 @@ class _HtmlWidgetState extends State<HtmlWidget> {
205213
return built;
206214
}
207215

216+
Widget _buildAsyncBuilder(
217+
BuildContext context, AsyncSnapshot<Widget> snapshot) {
218+
final built = snapshot.data;
219+
if (built != null) return built;
220+
221+
final indicator = Theme.of(context).platform == TargetPlatform.iOS
222+
? const Center(
223+
child: Padding(
224+
padding: EdgeInsets.all(8),
225+
child: CupertinoActivityIndicator()))
226+
: const Center(
227+
child: Padding(
228+
padding: EdgeInsets.all(8),
229+
child: CircularProgressIndicator()));
230+
231+
switch (widget.renderMode) {
232+
case RenderMode.Column:
233+
case RenderMode.ListView:
234+
return indicator;
235+
case RenderMode.SliverList:
236+
return SliverToBoxAdapter(child: indicator);
237+
}
238+
}
239+
208240
Widget _buildSync() {
209241
Timeline.startSync('Build $widget (sync)');
210242

@@ -244,18 +276,6 @@ class _RootTsb extends TextStyleBuilder {
244276
void reset() => _output = null;
245277
}
246278

247-
Widget _buildAsyncBuilder(
248-
BuildContext context, AsyncSnapshot<Widget> snapshot) =>
249-
snapshot.data ??
250-
Center(
251-
child: Padding(
252-
padding: EdgeInsets.all(8),
253-
child: Theme.of(context).platform == TargetPlatform.iOS
254-
? CupertinoActivityIndicator()
255-
: CircularProgressIndicator(),
256-
),
257-
);
258-
259279
Widget _buildBody(_HtmlWidgetState state, dom.NodeList domNodes) {
260280
final rootMeta = state._rootMeta;
261281
final wf = state._wf;

0 commit comments

Comments
 (0)