Skip to content

Commit

Permalink
Merge pull request #548 from tneotia/misc/nullsafety
Browse files Browse the repository at this point in the history
Nullsafety
  • Loading branch information
erickok authored Mar 6, 2021
2 parents 328ccff + 6cbbdba commit e421c47
Show file tree
Hide file tree
Showing 19 changed files with 618 additions and 636 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## [2.0.0-nullsafety.0] - March 5, 2021:
* Nullsafety support
* Official Flutter Web support
* New features & fixes for lists:
* Support start attribute (e.g. `start="5";`)
* Support RTL direction
* Support setting padding - you can remove the starting padding if you choose
* Fixed unknown character box on iOS when font-weight is below w400
* Upgraded link functions to provide more granular control
* Fixed errors in text-decoration parsing
* Fixed `<audio>` on iOS ("_duration called on null" exception)
* Updated dependencies

## [1.3.0] - February 16, 2021:
* New image loading API
* Image loading with request headers, from relative paths and custom loading widget
Expand Down
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Widget html = Html(
data: """<p>
Linking to <a href='https://github.com'>websites</a> has never been easier.
</p>""",
onLinkTap: (String url) {
onLinkTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
//open URL in webview, or launch URL in browser, or any other logic here
}
);
Expand All @@ -197,10 +197,10 @@ Widget html = Html(
<flutter horizontal></flutter>
""",
customRender: {
"bird": (RenderContext context, Widget child, Map<String, String> attributes, _) {
"bird": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
return TextSpan(text: "🐦");
},
"flutter": (RenderContext context, Widget child, Map<String, String> attributes, _) {
"flutter": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
return FlutterLogo(
style: (attributes['horizontal'] != null)
? FlutterLogoStyle.horizontal
Expand All @@ -227,26 +227,25 @@ Widget html = Html(
<iframe src="https://www.youtube.com/embed/tgbNymZ7vqY"></iframe>
""",
customRender: {
"iframe": (RenderContext context, Widget child, Map<String, String> attributes, _) {
"iframe": (RenderContext context, Widget child, Map<String, String> attributes, dom.Element? element) {
if (attributes != null) {
double width = double.tryParse(attributes['width'] ?? "");
double height = double.tryParse(attributes['height'] ?? "");
print(attributes['src']);
return Container(
width: width ?? (height ?? 150) * 2,
height: height ?? (width ?? 300) / 2,
child: WebView(
initialUrl: attributes['src'],
initialUrl: attributes['src'] ?? "about:blank",
javascriptMode: JavascriptMode.unrestricted,
//no need for scrolling gesture recognizers on embedded youtube, so set gestureRecognizers null
//on other iframe content scrolling might be necessary, so use VerticalDragGestureRecognizer
gestureRecognizers: attributes['src'].contains("youtube.com/embed") ? null : [
gestureRecognizers: attributes['src'] != null && attributes['src']!.contains("youtube.com/embed") ? null : [
Factory(() => VerticalDragGestureRecognizer())
].toSet(),
navigationDelegate: (NavigationRequest request) async {
//no need to load any url besides the embedded youtube url when displaying embedded youtube, so prevent url loading
//on other iframe content allow all url loading
if (attributes['src'].contains("youtube.com/embed")) {
if (attributes['src'] != null && attributes['src']!.contains("youtube.com/embed")) {
if (!request.url.contains("youtube.com/embed")) {
return NavigationDecision.prevent;
} else {
Expand Down Expand Up @@ -293,7 +292,7 @@ A function that defines what the widget should do when an image is tapped.
```dart
Widget html = Html(
data: """<img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />""",
onImageTap: (String url) {
onImageTap: (String? url, RenderContext context, Map<String, String> attributes, dom.Element? element) {
//open image in webview, or launch image in browser, or any other logic here
}
);
Expand Down Expand Up @@ -432,8 +431,9 @@ A typical usage would look something like this:

```dart
ImageSourceMatcher base64UriMatcher() => (attributes, element) =>
attributes["src"].startsWith("data:image") &&
attributes["src"].contains("base64,");
attributes["src"] != null &&
attributes["src"]!.startsWith("data:image") &&
attributes["src"]!.contains("base64,");
```

In the above example, the matcher checks whether the image's `src` either starts with "data:image" or contains "base64,", since these indicate an image in base64 format.
Expand All @@ -448,10 +448,10 @@ ImageSourceMatcher networkSourceMatcher({
String extension: "your extension",
}) =>
(attributes, element) {
final src = Uri.parse(attributes["src"]);
final src = Uri.parse(attributes["src"] ?? "about:blank");
return schemas.contains(src.scheme) &&
(domains == null || domains.contains(src.host)) &&
(extension == null || src.path.endsWith(".$extension"));
domains.contains(src.host) &&
src.path.endsWith(".$extension");
};
```

Expand All @@ -465,7 +465,8 @@ A typical usage might look like this:

```dart
ImageRender base64ImageRender() => (context, attributes, element) {
final decodedImage = base64.decode(attributes["src"].split("base64,")[1].trim());
final decodedImage = base64.decode(attributes["src"] != null ?
attributes["src"].split("base64,")[1].trim() : "about:blank");
return Image.memory(
decodedImage,
);
Expand All @@ -485,7 +486,7 @@ ImageRender networkImageRender({
}) =>
(context, attributes, element) {
return Image.network(
attributes["src"],
attributes["src"] ?? "about:blank",
headers: headers,
width: width,
height: height,
Expand Down Expand Up @@ -521,10 +522,10 @@ Widget html = Html(
},
networkSourceMatcher(): networkImageRender(
headers: {"Custom-Header": "some-value"},
altWidget: (alt) => Text(alt),
altWidget: (alt) => Text(alt ?? ""),
loadingWidget: () => Text("Loading..."),
),
(attr, _) => attr["src"] != null && attr["src"].startsWith("/wiki"):
(attr, _) => attr["src"] != null && attr["src"]!.startsWith("/wiki"):
networkImageRender(
mapUrl: (url) => "https://upload.wikimedia.org" + url),
},
Expand All @@ -538,16 +539,17 @@ When an image with URL `flutter.dev` is detected, rather than displaying the ima
2. Creating your own renders:
```dart
ImageSourceMatcher classAndIdMatcher({String classToMatch, String idToMatch}) => (attributes, element) =>
attributes["class"].contains(classToMatch) ||
attributes["id"].contains(idToMatch);
attributes["class"] != null && attributes["id"] != null &&
(attributes["class"]!.contains(classToMatch) ||
attributes["id"]!.contains(idToMatch));
ImageRender classAndIdRender({String classToMatch, String idToMatch}) => (context, attributes, element) {
if (attributes["class"].contains(classToMatch)) {
return Image.asset(attributes["src"]);
if (attributes["class"] != null && attributes["class"]!.contains(classToMatch)) {
return Image.asset(attributes["src"] ?? "about:blank");
} else {
return Image.network(
attributes["src"],
semanticLabel: attributes["longdesc"],
attributes["src"] ?? "about:blank",
semanticLabel: attributes["longdesc"] ?? "",
width: attributes["width"],
height: attributes["height"],
color: context.style.color,
Expand Down Expand Up @@ -609,9 +611,7 @@ You can set the `navigationDelegate` of the webview with the `navigationDelegate

### Audio

This package renders audio elements using the [`chewie_audio`](https://pub.dev/packages/chewie_audio) plugin.

Note: Audio elements currently do not work on iOS due to a bug with `chewie_audio`. Once [#509](https://github.com/Sub6Resources/flutter_html/pull/509) is merged, it will work again.
This package renders audio elements using the [`chewie_audio`](https://pub.dev/packages/chewie_audio) plugin.

The package considers the attributes `controls`, `loop`, `src`, `autoplay`, `width`, and `muted` when rendering the audio widget.

Expand Down
8 changes: 4 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class MyApp extends StatelessWidget {
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

Expand Down Expand Up @@ -157,13 +157,13 @@ class _MyHomePageState extends State<MyHomePage> {
},
networkSourceMatcher(domains: ["mydomain.com"]): networkImageRender(
headers: {"Custom-Header": "some-value"},
altWidget: (alt) => Text(alt),
altWidget: (alt) => Text(alt ?? ""),
loadingWidget: () => Text("Loading..."),
),
// On relative paths starting with /wiki, prefix with a base url
(attr, _) => attr["src"] != null && attr["src"].startsWith("/wiki"):
(attr, _) => attr["src"] != null && attr["src"]!.startsWith("/wiki"):
networkImageRender(
mapUrl: (url) => "https://upload.wikimedia.org" + url),
mapUrl: (url) => "https://upload.wikimedia.org" + url!),
// Custom placeholder image for broken links
networkSourceMatcher(): networkImageRender(altWidget: (_) => FlutterLogo()),
},
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: flutter_html example app.
version: 1.0.0+1

environment:
sdk: ">=2.1.0 <3.0.0"
sdk: '>=2.12.0 <3.0.0'

dependencies:
flutter_html:
Expand Down
18 changes: 9 additions & 9 deletions lib/flutter_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,27 @@ class Html extends StatelessWidget {
/// **style** Pass in the style information for the Html here.
/// See [its wiki page](https://github.com/Sub6Resources/flutter_html/wiki/Style) for more info.
Html({
Key key,
@required this.data,
Key? key,
required this.data,
this.onLinkTap,
this.customRender,
this.customRender = const {},
this.customImageRenders = const {},
this.onImageError,
this.shrinkWrap = false,
this.onImageTap,
this.blacklistedElements = const [],
this.style,
this.style = const {},
this.navigationDelegateForIframe,
}) : super(key: key);

final String data;
final OnTap onLinkTap;
final OnTap? onLinkTap;
final Map<ImageSourceMatcher, ImageRender> customImageRenders;
final ImageErrorListener onImageError;
final ImageErrorListener? onImageError;
final bool shrinkWrap;

/// Properties for the Image widget that gets rendered by the rich text parser
final OnTap onImageTap;
final OnTap? onImageTap;

final List<String> blacklistedElements;

Expand All @@ -67,11 +67,11 @@ class Html extends StatelessWidget {
/// Decides how to handle a specific navigation request in the WebView of an
/// Iframe. It's necessary to use the webview_flutter package inside the app
/// to use NavigationDelegate.
final NavigationDelegate navigationDelegateForIframe;
final NavigationDelegate? navigationDelegateForIframe;

@override
Widget build(BuildContext context) {
final double width = shrinkWrap ? null : MediaQuery.of(context).size.width;
final double? width = shrinkWrap ? null : MediaQuery.of(context).size.width;

return Container(
width: width,
Expand Down
Loading

0 comments on commit e421c47

Please sign in to comment.