Skip to content

Commit bf7be3d

Browse files
committed
Merge remote-tracking branch 'origin/master' into bugfix/audio-video-disposing
# Conflicts: # example/pubspec.yaml # lib/flutter_html.dart # lib/html_parser.dart
2 parents fa5265f + 2f3acc0 commit bf7be3d

File tree

8 files changed

+556
-328
lines changed

8 files changed

+556
-328
lines changed

README.md

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,13 @@ Inner links (such as `<a href="#top">Back to the top</a>` will work out of the b
285285

286286
A powerful API that allows you to customize everything when rendering a specific HTML tag. This means you can change the default behaviour or add support for HTML elements that aren't supported natively. You can also make up your own custom tags in your HTML!
287287

288-
`customRender` accepts a `Map<String, CustomRender>`. The `CustomRender` type is a function that requires a `Widget` or `InlineSpan` to be returned. It exposes `RenderContext` and the `Widget` that would have been rendered by `Html` without a `customRender` defined. The `RenderContext` contains the build context, styling and the HTML element, with attrributes and its subtree,.
288+
`customRender` accepts a `Map<CustomRenderMatcher, CustomRender>`.
289289

290-
To use this API, set the key as the tag of the HTML element you wish to provide a custom implementation for, and create a function with the above parameters that returns a `Widget` or `InlineSpan`.
290+
`CustomRenderMatcher` is a function that requires a `bool` to be returned. It exposes the `RenderContext` which provides `BuildContext` and access to the HTML tree.
291+
292+
The `CustomRender` class has two constructors: `CustomRender.widget()` and `CustomRender.inlineSpan()`. Both require a `<Widget/InlineSpan> Function(RenderContext, Function())`. The `Function()` argument is a function that will provide you with the element's children when needed.
293+
294+
To use this API, create a matching function and an instance of `CustomRender`.
291295

292296
Note: If you add any custom tags, you must add these tags to the [`tagsList`](#tagslist) parameter, otherwise they will not be rendered. See below for an example.
293297

@@ -302,21 +306,21 @@ Widget html = Html(
302306
<flutter horizontal></flutter>
303307
""",
304308
customRender: {
305-
"bird": (RenderContext context, Widget child) {
306-
return TextSpan(text: "🐦");
307-
},
308-
"flutter": (RenderContext context, Widget child) {
309-
return FlutterLogo(
310-
style: (context.tree.element!.attributes['horizontal'] != null)
311-
? FlutterLogoStyle.horizontal
312-
: FlutterLogoStyle.markOnly,
313-
textColor: context.style.color!,
314-
size: context.style.fontSize!.size! * 5,
315-
);
316-
},
309+
birdMatcher(): CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan(text: "🐦")),
310+
flutterMatcher(): CustomRender.widget(widget: (context, buildChildren) => FlutterLogo(
311+
style: (context.tree.element!.attributes['horizontal'] != null)
312+
? FlutterLogoStyle.horizontal
313+
: FlutterLogoStyle.markOnly,
314+
textColor: context.style.color!,
315+
size: context.style.fontSize!.size! * 5,
316+
)),
317317
},
318318
tagsList: Html.tags..addAll(["bird", "flutter"]),
319319
);
320+
321+
CustomRenderMatcher birdMatcher() => (context) => context.tree.element?.localName == 'bird';
322+
323+
CustomRenderMatcher flutterMatcher() => (context) => context.tree.element?.localName == 'flutter';
320324
```
321325

322326
2. Complex example - wrapping the default widget with your own, in this case placing a horizontal scroll around a (potentially too wide) table.
@@ -334,14 +338,16 @@ Widget html = Html(
334338
</table>
335339
""",
336340
customRender: {
337-
"table": (context, child) {
341+
tableMatcher(): CustomRender.widget(widget: (context, child) {
338342
return SingleChildScrollView(
339343
scrollDirection: Axis.horizontal,
340344
child: (context.tree as TableLayoutElement).toWidget(context),
341345
);
342-
}
346+
}),
343347
},
344348
);
349+
350+
CustomRenderMatcher tableMatcher() => (context) => context.tree.element?.localName == "table" ?? false;
345351
```
346352

347353
</details>
@@ -359,43 +365,52 @@ Widget html = Html(
359365
<iframe src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>
360366
""",
361367
customRender: {
362-
"iframe": (RenderContext context, Widget child) {
363-
final attrs = context.tree.element?.attributes;
364-
if (attrs != null) {
365-
double? width = double.tryParse(attrs['width'] ?? "");
366-
double? height = double.tryParse(attrs['height'] ?? "");
367-
return Container(
368-
width: width ?? (height ?? 150) * 2,
369-
height: height ?? (width ?? 300) / 2,
370-
child: WebView(
371-
initialUrl: attrs['src'] ?? "about:blank",
372-
javascriptMode: JavascriptMode.unrestricted,
373-
//no need for scrolling gesture recognizers on embedded youtube, so set gestureRecognizers null
374-
//on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer
375-
gestureRecognizers: attrs['src'] != null && attrs['src']!.contains("youtube.com/embed") ? null : [
376-
Factory(() => VerticalDragGestureRecognizer())
377-
].toSet(),
378-
navigationDelegate: (NavigationRequest request) async {
379-
//no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading
380-
//on other iframe content allow all url loading
381-
if (attrs['src'] != null && attrs['src']!.contains("youtube.com/embed")) {
382-
if (!request.url.contains("youtube.com/embed")) {
383-
return NavigationDecision.prevent;
384-
} else {
385-
return NavigationDecision.navigate;
386-
}
387-
} else {
388-
return NavigationDecision.navigate;
389-
}
390-
},
391-
),
392-
);
393-
} else {
394-
return Container(height: 0);
395-
}
396-
}
397-
}
368+
iframeYT(): CustomRender.widget(widget: (context, buildChildren) {
369+
double? width = double.tryParse(context.tree.attributes['width'] ?? "");
370+
double? height = double.tryParse(context.tree.attributes['height'] ?? "");
371+
return Container(
372+
width: width ?? (height ?? 150) * 2,
373+
height: height ?? (width ?? 300) / 2,
374+
child: WebView(
375+
initialUrl: context.tree.attributes['src']!,
376+
javascriptMode: JavascriptMode.unrestricted,
377+
navigationDelegate: (NavigationRequest request) async {
378+
//no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading
379+
if (!request.url.contains("youtube.com/embed")) {
380+
return NavigationDecision.prevent;
381+
} else {
382+
return NavigationDecision.navigate;
383+
}
384+
},
385+
),
386+
);
387+
}),
388+
iframeOther(): CustomRender.widget(widget: (context, buildChildren) {
389+
double? width = double.tryParse(context.tree.attributes['width'] ?? "");
390+
double? height = double.tryParse(context.tree.attributes['height'] ?? "");
391+
return Container(
392+
width: width ?? (height ?? 150) * 2,
393+
height: height ?? (width ?? 300) / 2,
394+
child: WebView(
395+
initialUrl: context.tree.attributes['src'],
396+
javascriptMode: JavascriptMode.unrestricted,
397+
//on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer
398+
gestureRecognizers: [
399+
Factory(() => VerticalDragGestureRecognizer())
400+
].toSet(),
401+
),
402+
);
403+
}),
404+
iframeNull(): CustomRender.widget(widget: (context, buildChildren) => Container(height: 0, width: 0)),
405+
}
398406
);
407+
408+
CustomRenderMatcher iframeYT() => (context) => context.tree.element?.attributes['src']?.contains("youtube.com/embed") ?? false;
409+
410+
CustomRenderMatcher iframeOther() => (context) => !(context.tree.element?.attributes['src']?.contains("youtube.com/embed")
411+
?? context.tree.element?.attributes['src'] == null);
412+
413+
CustomRenderMatcher iframeNull() => (context) => context.tree.element?.attributes['src'] == null;
399414
```
400415
</details>
401416

@@ -820,16 +835,23 @@ Then, use the `customRender` parameter to add the widget to render Tex. It could
820835
Widget htmlWidget = Html(
821836
data: r"""<tex>i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)</tex>""",
822837
customRender: {
823-
"tex": (RenderContext context, _) => Math.tex(
824-
context.tree.element!.text,
838+
texMatcher(): CustomRender.widget(widget: (context, buildChildren) => Math.tex(
839+
context.tree.element?.innerHtml ?? '',
840+
mathStyle: MathStyle.display,
841+
textStyle: context.style.generateTextStyle(),
825842
onErrorFallback: (FlutterMathException e) {
826-
//return your error widget here e.g.
827-
return Text(e.message);
843+
if (context.parser.onMathError != null) {
844+
return context.parser.onMathError!.call(context.tree.element?.innerHtml ?? '', e.message, e.messageWithType);
845+
} else {
846+
return Text(e.message);
847+
}
828848
},
829-
),
849+
)),
830850
},
831851
tagsList: Html.tags..add('tex'),
832852
);
853+
854+
CustomRenderMatcher texMatcher() => (context) => context.tree.element?.localName == 'tex';
833855
```
834856

835857
### Table

example/lib/main.dart

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_html/flutter_html.dart';
3+
import 'package:flutter_math_fork/flutter_math.dart';
34

45
void main() => runApp(new MyApp());
56

@@ -250,7 +251,6 @@ class _MyHomePageState extends State<MyHomePage> {
250251
body: SingleChildScrollView(
251252
child: Html(
252253
data: htmlData,
253-
tagsList: Html.tags..addAll(["bird", "flutter"]),
254254
style: {
255255
"table": Style(
256256
backgroundColor: Color.fromARGB(0x50, 0xee, 0xee, 0xee),
@@ -268,26 +268,32 @@ class _MyHomePageState extends State<MyHomePage> {
268268
),
269269
'h5': Style(maxLines: 2, textOverflow: TextOverflow.ellipsis),
270270
},
271-
customRender: {
272-
"table": (context, child) {
273-
return SingleChildScrollView(
274-
scrollDirection: Axis.horizontal,
275-
child:
276-
(context.tree as TableLayoutElement).toWidget(context),
277-
);
278-
},
279-
"bird": (RenderContext context, Widget child) {
280-
return TextSpan(text: "🐦");
281-
},
282-
"flutter": (RenderContext context, Widget child) {
283-
return FlutterLogo(
284-
style: (context.tree.element!.attributes['horizontal'] != null)
285-
? FlutterLogoStyle.horizontal
286-
: FlutterLogoStyle.markOnly,
287-
textColor: context.style.color!,
288-
size: context.style.fontSize!.size! * 5,
289-
);
290-
},
271+
tagsList: Html.tags..addAll(["tex", "bird", "flutter"]),
272+
customRenders: {
273+
tagMatcher("tex"): CustomRender.widget(widget: (context, buildChildren) => Math.tex(
274+
context.tree.element?.innerHtml ?? '',
275+
mathStyle: MathStyle.display,
276+
textStyle: context.style.generateTextStyle(),
277+
onErrorFallback: (FlutterMathException e) {
278+
if (context.parser.onMathError != null) {
279+
return context.parser.onMathError!.call(context.tree.element?.innerHtml ?? '', e.message, e.messageWithType);
280+
} else {
281+
return Text(e.message);
282+
}
283+
},
284+
)),
285+
tagMatcher("bird"): CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan(text: "🐦")),
286+
tagMatcher("flutter"): CustomRender.widget(widget: (context, buildChildren) => FlutterLogo(
287+
style: (context.tree.element!.attributes['horizontal'] != null)
288+
? FlutterLogoStyle.horizontal
289+
: FlutterLogoStyle.markOnly,
290+
textColor: context.style.color!,
291+
size: context.style.fontSize!.size! * 5,
292+
)),
293+
tagMatcher("table"): CustomRender.widget(widget: (context, buildChildren) => SingleChildScrollView(
294+
scrollDirection: Axis.horizontal,
295+
child: (context.tree as TableLayoutElement).toWidget(context),
296+
)),
291297
},
292298
customImageRenders: {
293299
networkSourceMatcher(domains: ["flutter.dev"]):

example/pubspec.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
name: example
22
description: flutter_html example app.
33
publish_to: none
4-
54
version: 1.0.0+1
65

76
environment:

0 commit comments

Comments
 (0)