From a9aedf96d378dbf7423f1abd7e20f4cbfcbc66db Mon Sep 17 00:00:00 2001 From: "mowen@126.com" <mowen@126.com> Date: Sun, 30 May 2021 02:56:14 +0800 Subject: [PATCH 1/5] Fixed a crash when load local HTML resources on IOS when localUrlScope attribute is null Fixed a crash when load local HTML resources on IOS when localUrlScope attribute is null --- ios/Classes/FlutterWebviewPlugin.m | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index fc266094..bfd14991 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -196,16 +196,10 @@ - (void)navigate:(FlutterMethodCall*)call { NSString *url = call.arguments[@"url"]; NSNumber *withLocalUrl = call.arguments[@"withLocalUrl"]; if ( [withLocalUrl boolValue]) { - NSURL *htmlUrl = [NSURL fileURLWithPath:url isDirectory:false]; - NSString *localUrlScope = call.arguments[@"localUrlScope"]; if (@available(iOS 9.0, *)) { - if(localUrlScope == nil) { - [self.webview loadFileURL:htmlUrl allowingReadAccessToURL:htmlUrl]; - } - else { - NSURL *scopeUrl = [NSURL fileURLWithPath:localUrlScope]; - [self.webview loadFileURL:htmlUrl allowingReadAccessToURL:scopeUrl]; - } + NSURL *scopeUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; + NSURL *fileUrl = [NSURL fileURLWithPaht:[[NSBundle mainBundle] pathForResource:url ofType:nil]]; + [self.webview loadFileURL:fileUrl allowingReadAccessToURL:scopeUrl]; } else { @throw @"not available on version earlier than ios 9.0"; } From 28fd69e873dfdea14531383cda3df541334328e1 Mon Sep 17 00:00:00 2001 From: "mowen@126.com" <mowen@126.com> Date: Tue, 1 Jun 2021 16:28:39 +0800 Subject: [PATCH 2/5] Add support for sliding / scaling on webview. by passing and calculating the relative offset of the reference component's render box --- lib/src/base.dart | 64 ++++++++++++++++-------------- lib/src/webview_scaffold.dart | 74 +++++++++++++++++++++++++++++++---- 2 files changed, 101 insertions(+), 37 deletions(-) diff --git a/lib/src/base.dart b/lib/src/base.dart index 2fdc0384..cc006f28 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -12,6 +12,12 @@ const _kChannel = 'flutter_webview_plugin'; // TODO: more general state for iOS/android enum WebViewState { shouldStart, startLoad, finishLoad, abortLoad } +/// the transition animation type of page on/off screen +enum TransitionType{ + Non,Slide,Scale +} + + // TODO: use an id by webview to be able to manage multiple webview /// Singleton class that communicate with a Webview Instance @@ -44,7 +50,7 @@ class FlutterWebviewPlugin { final _onPostMessage = StreamController<JavascriptMessage>.broadcast(); final Map<String, JavascriptChannel> _javascriptChannels = - <String, JavascriptChannel>{}; + <String, JavascriptChannel>{}; Future<Null> _handleMessages(MethodCall call) async { switch (call.method) { @@ -140,33 +146,33 @@ class FlutterWebviewPlugin { /// - [useWideViewPort]: use wide viewport for Android webview ( setUseWideViewPort ) /// - [ignoreSSLErrors]: use to bypass Android/iOS SSL checks e.g. for self-signed certificates Future<void> launch( - String url, { - Map<String, String>? headers, - Set<JavascriptChannel> javascriptChannels = const <JavascriptChannel>{}, - bool withJavascript = true, - bool clearCache = false, - bool clearCookies = false, - bool mediaPlaybackRequiresUserGesture = true, - bool hidden = false, - bool enableAppScheme = true, - Rect? rect, - String? userAgent, - bool withZoom = false, - bool displayZoomControls = false, - bool withLocalStorage = true, - bool withLocalUrl = false, - String? localUrlScope, - bool withOverviewMode = false, - bool scrollBar = true, - bool supportMultipleWindows = false, - bool appCacheEnabled = false, - bool allowFileURLs = false, - bool useWideViewPort = false, - String? invalidUrlRegex, - bool geolocationEnabled = false, - bool debuggingEnabled = false, - bool ignoreSSLErrors = false, - }) async { + String url, { + Map<String, String>? headers, + Set<JavascriptChannel> javascriptChannels = const <JavascriptChannel>{}, + bool withJavascript = true, + bool clearCache = false, + bool clearCookies = false, + bool mediaPlaybackRequiresUserGesture = true, + bool hidden = false, + bool enableAppScheme = true, + Rect? rect, + String? userAgent, + bool withZoom = false, + bool displayZoomControls = false, + bool withLocalStorage = true, + bool withLocalUrl = false, + String? localUrlScope, + bool withOverviewMode = false, + bool scrollBar = true, + bool supportMultipleWindows = false, + bool appCacheEnabled = false, + bool allowFileURLs = false, + bool useWideViewPort = false, + String? invalidUrlRegex, + bool geolocationEnabled = false, + bool debuggingEnabled = false, + bool ignoreSSLErrors = false, + }) async { final args = <String, dynamic>{ 'url': url, 'withJavascript': withJavascript, @@ -317,7 +323,7 @@ class FlutterWebviewPlugin { Set<String> _extractJavascriptChannelNames(Set<JavascriptChannel> channels) { final Set<String> channelNames = - channels.map((JavascriptChannel channel) => channel.name).toSet(); + channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } diff --git a/lib/src/webview_scaffold.dart b/lib/src/webview_scaffold.dart index 389add90..881d54b8 100644 --- a/lib/src/webview_scaffold.dart +++ b/lib/src/webview_scaffold.dart @@ -10,6 +10,8 @@ import 'base.dart'; class WebviewScaffold extends StatefulWidget { const WebviewScaffold({ Key? key, + required this.rrok, + required this.margin, this.appBar, required this.url, this.headers, @@ -41,7 +43,22 @@ class WebviewScaffold extends StatefulWidget { this.geolocationEnabled = false, this.debuggingEnabled = false, this.ignoreSSLErrors = false, - }) : super(key: key); + this.transitionType = TransitionType.Non + }) + : + assert(transitionType != null, 'TransitionType must not null !'), + super(key: key); + + // The global key of the referenced render object + final GlobalKey rrok; + + final EdgeInsets margin; + + /// in Debug mode,the first init webview page at slide/scale transition,will display misalignment + /// after that will be display properly. + /// + /// in Profile/Release mode, will always display properly. + final TransitionType transitionType; final PreferredSizeWidget? appBar; final String url; @@ -92,6 +109,8 @@ class _WebviewScaffoldState extends State<WebviewScaffold> { super.initState(); webviewReference.close(); + perceptionPageTransition(); + _onBack = webviewReference.onBack.listen((_) async { if (!mounted) { return; @@ -113,13 +132,52 @@ class _WebviewScaffoldState extends State<WebviewScaffold> { if (widget.hidden) { _onStateChanged = webviewReference.onStateChanged.listen((WebViewStateChanged state) { - if (state.type == WebViewState.finishLoad) { - webviewReference.show(); - } + if (state.type == WebViewState.finishLoad) { + webviewReference.show(); + } + }); + } + } + + /// coordinate the webview rect whit page's transition + void perceptionPageTransition() { + if (widget.transitionType != TransitionType.Non) { + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { + //avoid to concurrent modification exception + WidgetsBinding.instance?.addPersistentFrameCallback((timeStamp) { + if (mounted) { + driveWebView(); + } + }); }); } } + void driveWebView() { + final RenderBox box = widget.rrok.currentContext?.findRenderObject()! as RenderBox; + final Offset offset = box.localToGlobal(Offset.zero) - Offset(widget.margin.left != null ? widget.margin.left : 0.0, widget.margin.top != null ? widget.margin.top : 0.0); + final Size size = box.size; + final Rect rect = box.paintBounds; + + final double value = offset.dx / size.width; + + // print('value=$value , offset=$offset , rect=$rect , size=$size , margin=${widget.margin.left}/${widget.margin.top}/${widget.margin.right}/${widget.margin.bottom}'); + + switch (widget.transitionType) { + case TransitionType.Slide: + webviewReference.resize(_rect!.shift(offset)); + break; + case TransitionType.Scale: + final double www = size.width * (value * 2); + final double hhh = size.height * (value * 2); + webviewReference.resize(Rect.fromLTWH(offset.dx, offset.dy, size.width - www, size.height - hhh)); + break; + case TransitionType.Non: + // TODO: Handle this case. + break; + } + } + /// Equivalent to [Navigator.of(context)._history.last]. Route<dynamic> get _topMostRoute { Route? topMost; @@ -161,7 +219,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> { clearCache: widget.clearCache, clearCookies: widget.clearCookies, mediaPlaybackRequiresUserGesture: - widget.mediaPlaybackRequiresUserGesture, + widget.mediaPlaybackRequiresUserGesture, hidden: widget.hidden, enableAppScheme: widget.enableAppScheme, userAgent: widget.userAgent, @@ -217,8 +275,7 @@ class _WebviewPlaceholder extends SingleChildRenderObjectWidget { } @override - void updateRenderObject( - BuildContext context, _WebviewPlaceholderRender renderObject) { + void updateRenderObject(BuildContext context, _WebviewPlaceholderRender renderObject) { renderObject..onRectChanged = onRectChanged; } } @@ -227,7 +284,8 @@ class _WebviewPlaceholderRender extends RenderProxyBox { _WebviewPlaceholderRender({ RenderBox? child, ValueChanged<Rect>? onRectChanged, - }) : _callback = onRectChanged, + }) + : _callback = onRectChanged, super(child); ValueChanged<Rect>? _callback; From fee208858c917360620e3756c69bbe57b3ec8097 Mon Sep 17 00:00:00 2001 From: "mowen@126.com" <mowen@126.com> Date: Sun, 6 Jun 2021 20:45:37 +0800 Subject: [PATCH 3/5] Update FlutterWebviewPlugin.m --- ios/Classes/FlutterWebviewPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Classes/FlutterWebviewPlugin.m b/ios/Classes/FlutterWebviewPlugin.m index bfd14991..dd7a3d1b 100644 --- a/ios/Classes/FlutterWebviewPlugin.m +++ b/ios/Classes/FlutterWebviewPlugin.m @@ -198,7 +198,7 @@ - (void)navigate:(FlutterMethodCall*)call { if ( [withLocalUrl boolValue]) { if (@available(iOS 9.0, *)) { NSURL *scopeUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; - NSURL *fileUrl = [NSURL fileURLWithPaht:[[NSBundle mainBundle] pathForResource:url ofType:nil]]; + NSURL *fileUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:url ofType:nil]]; [self.webview loadFileURL:fileUrl allowingReadAccessToURL:scopeUrl]; } else { @throw @"not available on version earlier than ios 9.0"; From b3d8bd280f83c77300ec984856f6281de35854cf Mon Sep 17 00:00:00 2001 From: "mowen@126.com" <mowen@126.com> Date: Mon, 7 Jun 2021 13:35:20 +0800 Subject: [PATCH 4/5] u --- lib/src/base.dart | 66 +++++++++++++++++------------------ lib/src/webview_scaffold.dart | 17 ++------- 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/lib/src/base.dart b/lib/src/base.dart index cc006f28..2814d811 100644 --- a/lib/src/base.dart +++ b/lib/src/base.dart @@ -13,8 +13,9 @@ const _kChannel = 'flutter_webview_plugin'; enum WebViewState { shouldStart, startLoad, finishLoad, abortLoad } /// the transition animation type of page on/off screen -enum TransitionType{ - Non,Slide,Scale +enum TransitionType { + Non, + Slide } @@ -145,34 +146,33 @@ class FlutterWebviewPlugin { /// - [withOverviewMode]: enable overview mode for Android webview ( setLoadWithOverviewMode ) /// - [useWideViewPort]: use wide viewport for Android webview ( setUseWideViewPort ) /// - [ignoreSSLErrors]: use to bypass Android/iOS SSL checks e.g. for self-signed certificates - Future<void> launch( - String url, { - Map<String, String>? headers, - Set<JavascriptChannel> javascriptChannels = const <JavascriptChannel>{}, - bool withJavascript = true, - bool clearCache = false, - bool clearCookies = false, - bool mediaPlaybackRequiresUserGesture = true, - bool hidden = false, - bool enableAppScheme = true, - Rect? rect, - String? userAgent, - bool withZoom = false, - bool displayZoomControls = false, - bool withLocalStorage = true, - bool withLocalUrl = false, - String? localUrlScope, - bool withOverviewMode = false, - bool scrollBar = true, - bool supportMultipleWindows = false, - bool appCacheEnabled = false, - bool allowFileURLs = false, - bool useWideViewPort = false, - String? invalidUrlRegex, - bool geolocationEnabled = false, - bool debuggingEnabled = false, - bool ignoreSSLErrors = false, - }) async { + Future<void> launch(String url, { + Map<String, String>? headers, + Set<JavascriptChannel> javascriptChannels = const <JavascriptChannel>{}, + bool withJavascript = true, + bool clearCache = false, + bool clearCookies = false, + bool mediaPlaybackRequiresUserGesture = true, + bool hidden = false, + bool enableAppScheme = true, + Rect? rect, + String? userAgent, + bool withZoom = false, + bool displayZoomControls = false, + bool withLocalStorage = true, + bool withLocalUrl = false, + String? localUrlScope, + bool withOverviewMode = false, + bool scrollBar = true, + bool supportMultipleWindows = false, + bool appCacheEnabled = false, + bool allowFileURLs = false, + bool useWideViewPort = false, + String? invalidUrlRegex, + bool geolocationEnabled = false, + bool debuggingEnabled = false, + bool ignoreSSLErrors = false, + }) async { final args = <String, dynamic>{ 'url': url, 'withJavascript': withJavascript, @@ -327,8 +327,7 @@ class FlutterWebviewPlugin { return channelNames; } - void _handleJavascriptChannelMessage( - final String channelName, final String message) { + void _handleJavascriptChannelMessage(final String channelName, final String message) { if (_javascriptChannels.containsKey(channelName)) _javascriptChannels[channelName]! .onMessageReceived(JavascriptMessage(message)); @@ -336,8 +335,7 @@ class FlutterWebviewPlugin { print('Channel "$channelName" is not exstis'); } - void _assertJavascriptChannelNamesAreUnique( - final Set<JavascriptChannel>? channels) { + void _assertJavascriptChannelNamesAreUnique(final Set<JavascriptChannel>? channels) { if (channels == null || channels.isEmpty) { return; } diff --git a/lib/src/webview_scaffold.dart b/lib/src/webview_scaffold.dart index 881d54b8..973f0bbc 100644 --- a/lib/src/webview_scaffold.dart +++ b/lib/src/webview_scaffold.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -154,24 +155,12 @@ class _WebviewScaffoldState extends State<WebviewScaffold> { } void driveWebView() { - final RenderBox box = widget.rrok.currentContext?.findRenderObject()! as RenderBox; - final Offset offset = box.localToGlobal(Offset.zero) - Offset(widget.margin.left != null ? widget.margin.left : 0.0, widget.margin.top != null ? widget.margin.top : 0.0); - final Size size = box.size; - final Rect rect = box.paintBounds; - - final double value = offset.dx / size.width; - - // print('value=$value , offset=$offset , rect=$rect , size=$size , margin=${widget.margin.left}/${widget.margin.top}/${widget.margin.right}/${widget.margin.bottom}'); - switch (widget.transitionType) { case TransitionType.Slide: + final RenderBox box = widget.rrok.currentContext?.findRenderObject()! as RenderBox; + final Offset offset = box.localToGlobal(Offset.zero) - Offset(widget.margin.left != null ? widget.margin.left : 0.0, widget.margin.top != null ? widget.margin.top : 0.0); webviewReference.resize(_rect!.shift(offset)); break; - case TransitionType.Scale: - final double www = size.width * (value * 2); - final double hhh = size.height * (value * 2); - webviewReference.resize(Rect.fromLTWH(offset.dx, offset.dy, size.width - www, size.height - hhh)); - break; case TransitionType.Non: // TODO: Handle this case. break; From f070d77541fe0d7203ea704b38205cdfe82877da Mon Sep 17 00:00:00 2001 From: "mowen@126.com" <mowen@126.com> Date: Tue, 29 Jun 2021 06:21:49 +0800 Subject: [PATCH 5/5] Update webview_scaffold.dart --- lib/src/webview_scaffold.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/webview_scaffold.dart b/lib/src/webview_scaffold.dart index 973f0bbc..71e8b214 100644 --- a/lib/src/webview_scaffold.dart +++ b/lib/src/webview_scaffold.dart @@ -158,7 +158,7 @@ class _WebviewScaffoldState extends State<WebviewScaffold> { switch (widget.transitionType) { case TransitionType.Slide: final RenderBox box = widget.rrok.currentContext?.findRenderObject()! as RenderBox; - final Offset offset = box.localToGlobal(Offset.zero) - Offset(widget.margin.left != null ? widget.margin.left : 0.0, widget.margin.top != null ? widget.margin.top : 0.0); + final Offset offset = box.localToGlobal(Offset.zero) + Offset(widget.margin.left != null ? widget.margin.left : 0.0, widget.margin.top != null ? widget.margin.top : 0.0); webviewReference.resize(_rect!.shift(offset)); break; case TransitionType.Non: