From 22ea0091cd19e0253dd6ee4ab020bd77316633c0 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Fri, 26 Mar 2021 21:04:44 +0100 Subject: [PATCH] Added initialSize property and setSize/getSize methods to the HeadlessInAppWebView class, androidOnScaleChanged event is deprecated - use onZoomScaleChanged event, getScale method is deprecated - use getZoomScale method, Removed final keyword for all HeadlessInAppWebView events, Fixed wrong usage of Android WebView scale property --- .idea/libraries/Dart_Packages.xml | 420 ------------------ .idea/libraries/Flutter_Plugins.xml | 4 +- CHANGELOG.md | 9 + .../InAppWebViewFlutterPlugin.java | 2 +- .../InAppWebViewMethodHandler.java | 5 +- .../flutter_inappwebview/Util.java | 5 + .../HeadlessInAppWebView.java | 119 +++++ .../HeadlessInAppWebViewManager.java | 40 +- .../in_app_webview/FlutterWebView.java | 31 +- .../in_app_webview/FlutterWebViewFactory.java | 4 +- .../in_app_webview/InAppWebView.java | 20 +- .../in_app_webview/InAppWebViewClient.java | 5 +- .../flutter_inappwebview/types/Size2D.java | 81 ++++ .../webview_flutter_test.dart | 87 ++-- flutter_inappwebview.iml | 1 - .../HeadlessInAppWebView.swift | 95 ++++ .../HeadlessInAppWebViewManager.swift | 29 +- .../FlutterWebViewController.swift | 70 ++- .../InAppWebView/FlutterWebViewFactory.swift | 3 +- ios/Classes/InAppWebView/InAppWebView.swift | 19 +- ios/Classes/InAppWebViewMethodHandler.swift | 4 +- ios/Classes/Types/Size2D.swift | 35 ++ lib/src/in_app_browser/in_app_browser.dart | 26 +- .../headless_in_app_webview.dart | 214 +++++---- lib/src/in_app_webview/in_app_webview.dart | 9 + .../in_app_webview_controller.dart | 34 +- lib/src/in_app_webview/webview.dart | 28 +- lib/src/util.dart | 20 + pubspec.yaml | 2 +- 29 files changed, 712 insertions(+), 709 deletions(-) delete mode 100644 .idea/libraries/Dart_Packages.xml create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java rename android/src/main/java/com/pichillilorenzo/flutter_inappwebview/{in_app_webview => headless_in_app_webview}/HeadlessInAppWebViewManager.java (71%) create mode 100644 android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/Size2D.java create mode 100644 ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift rename ios/Classes/{InAppWebView => HeadlessInAppWebView}/HeadlessInAppWebViewManager.swift (65%) create mode 100644 ios/Classes/Types/Size2D.swift diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml deleted file mode 100644 index 3b0b4626..00000000 --- a/.idea/libraries/Dart_Packages.xml +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 31799730..65bb3679 100755 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -1,6 +1,8 @@ - + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index b76c4e32..0d925a7a 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 5.3.0 + +- Added `initialSize` property to the `HeadlessInAppWebView` class +- Added `setSize` and `getSize` methods to the `HeadlessInAppWebView` class +- `androidOnScaleChanged` WebView event is now deprecated. Use the new `onZoomScaleChanged` WebView event, that is available for both Android and iOS +- `getScale` WebView method is now deprecated. Use the new `getZoomScale` WebView method +- Removed `final` keyword for all `HeadlessInAppWebView` events +- Fixed wrong usage of Android WebView scale property + ## 5.2.1+1 - Fixed iOS "Unexpectedly found nil while unwrapping an Optional value: file flutter_inappwebview/WKUserContentController.swift, line 36" error when `applePayAPIEnabled` iOS-specific WebView option is enabled diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java index cf44bf6e..574b803c 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewFlutterPlugin.java @@ -10,7 +10,7 @@ import com.pichillilorenzo.flutter_inappwebview.chrome_custom_tabs.ChromeSafariB import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabaseHandler; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserManager; import com.pichillilorenzo.flutter_inappwebview.in_app_webview.FlutterWebViewFactory; -import com.pichillilorenzo.flutter_inappwebview.in_app_webview.HeadlessInAppWebViewManager; +import com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview.HeadlessInAppWebViewManager; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java index 5c4ef366..3f4737e9 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/InAppWebViewMethodHandler.java @@ -2,7 +2,6 @@ package com.pichillilorenzo.flutter_inappwebview; import android.net.Uri; import android.os.Build; -import android.util.Log; import android.webkit.ValueCallback; import android.webkit.WebView; @@ -332,8 +331,8 @@ public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandle case "getOriginalUrl": result.success((webView != null) ? webView.getOriginalUrl() : null); break; - case "getScale": - result.success((webView != null) ? webView.getUpdatedScale() : null); + case "getZoomScale": + result.success((webView != null) ? webView.zoomScale : null); break; case "getSelectedText": if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java index d88d3d6b..bccb1b6d 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/Util.java @@ -1,5 +1,6 @@ package com.pichillilorenzo.flutter_inappwebview; +import android.content.Context; import android.content.res.AssetManager; import android.net.http.SslCertificate; import android.os.Build; @@ -287,4 +288,8 @@ public class Util { } while (i < newline); } } + + public static float getPixelDensity(Context context) { + return context.getResources().getDisplayMetrics().density; + } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java new file mode 100644 index 00000000..e2c3d431 --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebView.java @@ -0,0 +1,119 @@ +package com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.pichillilorenzo.flutter_inappwebview.Shared; +import com.pichillilorenzo.flutter_inappwebview.Util; +import com.pichillilorenzo.flutter_inappwebview.in_app_webview.FlutterWebView; +import com.pichillilorenzo.flutter_inappwebview.types.Size2D; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +public class HeadlessInAppWebView implements MethodChannel.MethodCallHandler { + + protected static final String LOG_TAG = "HeadlessInAppWebView"; + @NonNull + public final String id; + public final MethodChannel channel; + @Nullable + public FlutterWebView flutterWebView; + + public HeadlessInAppWebView(BinaryMessenger messenger, @NonNull String id, @NonNull FlutterWebView flutterWebView) { + this.id = id; + this.flutterWebView = flutterWebView; + this.channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_headless_inappwebview_" + id); + channel.setMethodCallHandler(this); + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + switch (call.method) { + case "dispose": + dispose(); + result.success(true); + break; + case "setSize": + { + Map sizeMap = (Map) call.argument("size"); + Size2D size = Size2D.fromMap(sizeMap); + if (size != null) + setSize(size); + } + result.success(true); + break; + case "getSize": + { + Size2D size = getSize(); + result.success(size != null ? size.toMap() : null); + } + break; + default: + result.notImplemented(); + } + } + + public void onWebViewCreated() { + Map obj = new HashMap<>(); + channel.invokeMethod("onWebViewCreated", obj); + } + + public void prepare(Map params) { + // Add the headless WebView to the view hierarchy. + // This way is also possible to take screenshots. + ViewGroup contentView = (ViewGroup) Shared.activity.findViewById(android.R.id.content); + ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); + if (mainView != null) { + View view = flutterWebView.getView(); + final Map initialSize = (Map) params.get("initialSize"); + Size2D size = Size2D.fromMap(initialSize); + if (size != null) { + setSize(size); + } else { + view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + mainView.addView(view, 0); + view.setVisibility(View.INVISIBLE); + } + } + + public void setSize(@NonNull Size2D size) { + if (flutterWebView != null && flutterWebView.webView != null) { + View view = flutterWebView.getView(); + float scale = Util.getPixelDensity(view.getContext()); + view.setLayoutParams(new FrameLayout.LayoutParams((int) (size.getWidth() * scale), (int) (size.getHeight() * scale))); + } + } + + @Nullable + public Size2D getSize() { + if (flutterWebView != null && flutterWebView.webView != null) { + View view = flutterWebView.getView(); + float scale = Util.getPixelDensity(view.getContext()); + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + return new Size2D(layoutParams.width / scale, layoutParams.height / scale); + } + return null; + } + + public void dispose() { + channel.setMethodCallHandler(null); + HeadlessInAppWebViewManager.webViews.remove(id); + ViewGroup contentView = (ViewGroup) Shared.activity.findViewById(android.R.id.content); + ViewGroup mainView = (ViewGroup) (contentView).getChildAt(0); + if (mainView != null) { + mainView.removeView(flutterWebView.getView()); + } + flutterWebView.dispose(); + flutterWebView = null; + } +} diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/HeadlessInAppWebViewManager.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java similarity index 71% rename from android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/HeadlessInAppWebViewManager.java rename to android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java index 78514268..241142b8 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/HeadlessInAppWebViewManager.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/headless_in_app_webview/HeadlessInAppWebViewManager.java @@ -19,11 +19,10 @@ * */ -package com.pichillilorenzo.flutter_inappwebview.in_app_webview; - -import android.app.Activity; +package com.pichillilorenzo.flutter_inappwebview.headless_in_app_webview; import com.pichillilorenzo.flutter_inappwebview.Shared; +import com.pichillilorenzo.flutter_inappwebview.in_app_webview.FlutterWebView; import java.util.HashMap; import java.util.Map; @@ -33,14 +32,11 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; -/** - * InAppBrowserManager - */ public class HeadlessInAppWebViewManager implements MethodChannel.MethodCallHandler { - public MethodChannel channel; protected static final String LOG_TAG = "HeadlessInAppWebViewManager"; - Map flutterWebViews = new HashMap<>(); + public MethodChannel channel; + public static final Map webViews = new HashMap<>(); public HeadlessInAppWebViewManager(BinaryMessenger messenger) { channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_headless_inappwebview"); @@ -49,40 +45,34 @@ public class HeadlessInAppWebViewManager implements MethodChannel.MethodCallHand @Override public void onMethodCall(final MethodCall call, final Result result) { - final Activity activity = Shared.activity; final String id = (String) call.argument("id"); switch (call.method) { - case "createHeadlessWebView": + case "run": { HashMap params = (HashMap) call.argument("params"); - createHeadlessWebView(activity, id, params); + HeadlessInAppWebViewManager.run(id, params); } result.success(true); break; - case "disposeHeadlessWebView": - disposeHeadlessWebView(id); - result.success(true); - break; default: result.notImplemented(); } } - public void createHeadlessWebView(Activity activity, String id, HashMap params) { - FlutterWebView flutterWebView = new FlutterWebView(Shared.messenger, activity, id, params, null); - flutterWebViews.put(id, flutterWebView); - } - - public void disposeHeadlessWebView(String id) { - if (flutterWebViews.containsKey(id)) { - flutterWebViews.get(id).dispose(); - flutterWebViews.remove(id); - } + public static void run(String id, HashMap params) { + FlutterWebView flutterWebView = new FlutterWebView(Shared.messenger, Shared.activity, id, params, null); + HeadlessInAppWebView headlessInAppWebView = new HeadlessInAppWebView(Shared.messenger, id, flutterWebView); + HeadlessInAppWebViewManager.webViews.put(id, headlessInAppWebView); + + headlessInAppWebView.prepare(params); + headlessInAppWebView.onWebViewCreated(); + flutterWebView.makeInitialLoad(params); } public void dispose() { + HeadlessInAppWebViewManager.webViews.clear(); channel.setMethodCallHandler(null); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebView.java index 7bfa2e71..c5915973 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebView.java @@ -44,16 +44,14 @@ public class FlutterWebView implements PlatformView { public InAppWebViewMethodHandler methodCallDelegate; public PullToRefreshLayout pullToRefreshLayout; - public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, HashMap params, View containerView) { + public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, + HashMap params, View containerView) { channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id); DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); displayListenerProxy.onPreWebViewInitialization(displayManager); - - Map initialUrlRequest = (Map) params.get("initialUrlRequest"); - final String initialFile = (String) params.get("initialFile"); - final Map initialData = (Map) params.get("initialData"); + Map initialOptions = (Map) params.get("initialOptions"); Map contextMenu = (Map) params.get("contextMenu"); Integer windowId = (Integer) params.get("windowId"); @@ -95,6 +93,18 @@ public class FlutterWebView implements PlatformView { channel.setMethodCallHandler(methodCallDelegate); webView.prepare(); + } + + @Override + public View getView() { + return pullToRefreshLayout != null ? pullToRefreshLayout : webView; + } + + public void makeInitialLoad(HashMap params) { + Integer windowId = (Integer) params.get("windowId"); + Map initialUrlRequest = (Map) params.get("initialUrlRequest"); + final String initialFile = (String) params.get("initialFile"); + final Map initialData = (Map) params.get("initialData"); if (windowId != null) { Message resultMsg = InAppWebViewChromeClient.windowWebViewMessages.get(windowId); @@ -109,7 +119,6 @@ public class FlutterWebView implements PlatformView { } catch (IOException e) { e.printStackTrace(); Log.e(LOG_TAG, initialFile + " asset file cannot be found!", e); - return; } } else if (initialData != null) { @@ -125,16 +134,6 @@ public class FlutterWebView implements PlatformView { webView.loadUrl(urlRequest); } } - - if (containerView == null && id instanceof String) { - Map obj = new HashMap<>(); - channel.invokeMethod("onHeadlessWebViewCreated", obj); - } - } - - @Override - public View getView() { - return pullToRefreshLayout != null ? pullToRefreshLayout : webView; } @Override diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebViewFactory.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebViewFactory.java index 4d85ef86..f515d067 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebViewFactory.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/FlutterWebViewFactory.java @@ -23,7 +23,9 @@ public class FlutterWebViewFactory extends PlatformViewFactory { @Override public PlatformView create(Context context, int id, Object args) { HashMap params = (HashMap) args; - return new FlutterWebView(messenger, context, id, params, containerView); + FlutterWebView flutterWebView = new FlutterWebView(messenger, context, id, params, containerView); + flutterWebView.makeInitialLoad(params); + return flutterWebView; } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java index c9322498..6ce140a5 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebView.java @@ -116,7 +116,7 @@ final public class InAppWebView extends InputAwareWebView { public InAppWebViewOptions options; public boolean isLoading = false; public OkHttpClient httpClient; - public float scale = getResources().getDisplayMetrics().density; + public float zoomScale = 1.0f; int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); public Pattern regexToCancelSubFramesLoadingCompiled; @@ -579,6 +579,8 @@ final public class InAppWebView extends InputAwareWebView { } public void takeScreenshot(final @Nullable Map screenshotConfiguration, final MethodChannel.Result result) { + final float pixelDensity = Util.getPixelDensity(getContext()); + headlessHandler.post(new Runnable() { @Override public void run() { @@ -595,10 +597,10 @@ final public class InAppWebView extends InputAwareWebView { if (screenshotConfiguration != null) { Map rect = (Map) screenshotConfiguration.get("rect"); if (rect != null) { - int rectX = (int) Math.floor(rect.get("x") * scale + 0.5); - int rectY = (int) Math.floor(rect.get("y") * scale + 0.5); - int rectWidth = Math.min(resized.getWidth(), (int) Math.floor(rect.get("width") * scale + 0.5)); - int rectHeight = Math.min(resized.getHeight(), (int) Math.floor(rect.get("height") * scale + 0.5)); + int rectX = (int) Math.floor(rect.get("x") * pixelDensity + 0.5); + int rectY = (int) Math.floor(rect.get("y") * pixelDensity + 0.5); + int rectWidth = Math.min(resized.getWidth(), (int) Math.floor(rect.get("width") * pixelDensity + 0.5)); + int rectHeight = Math.min(resized.getHeight(), (int) Math.floor(rect.get("height") * pixelDensity + 0.5)); resized = Bitmap.createBitmap( resized, rectX, @@ -609,7 +611,7 @@ final public class InAppWebView extends InputAwareWebView { Double snapshotWidth = (Double) screenshotConfiguration.get("snapshotWidth"); if (snapshotWidth != null) { - int dstWidth = (int) Math.floor(snapshotWidth * scale + 0.5); + int dstWidth = (int) Math.floor(snapshotWidth * pixelDensity + 0.5); float ratioBitmap = (float) resized.getWidth() / (float) resized.getHeight(); int dstHeight = (int) ((float) dstWidth / ratioBitmap); resized = Bitmap.createScaledBitmap(resized, dstWidth, dstHeight, true); @@ -1222,10 +1224,6 @@ final public class InAppWebView extends InputAwareWebView { } } - public Float getUpdatedScale() { - return scale; - } - @Override public void onCreateContextMenu(ContextMenu menu) { super.onCreateContextMenu(menu); @@ -1509,7 +1507,7 @@ final public class InAppWebView extends InputAwareWebView { if (floatingContextMenu != null) { if (value != null && !value.equalsIgnoreCase("null")) { int x = contextMenuPoint.x; - int y = (int) ((Float.parseFloat(value) * scale) + (floatingContextMenu.getHeight() / 3.5)); + int y = (int) ((Float.parseFloat(value) * Util.getPixelDensity(getContext())) + (floatingContextMenu.getHeight() / 3.5)); contextMenuPoint.y = y; onFloatingActionGlobalLayout(x, y); } else { diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewClient.java index 7616bc5b..0b71c3b1 100755 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewClient.java @@ -23,6 +23,7 @@ import android.webkit.WebViewClient; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.credential_database.CredentialDatabase; import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserDelegate; @@ -508,12 +509,12 @@ public class InAppWebViewClient extends WebViewClient { public void onScaleChanged(WebView view, float oldScale, float newScale) { super.onScaleChanged(view, oldScale, newScale); final InAppWebView webView = (InAppWebView) view; - webView.scale = newScale; + webView.zoomScale = newScale / Util.getPixelDensity(webView.getContext()); Map obj = new HashMap<>(); obj.put("oldScale", oldScale); obj.put("newScale", newScale); - channel.invokeMethod("onScaleChanged", obj); + channel.invokeMethod("onZoomScaleChanged", obj); } @RequiresApi(api = Build.VERSION_CODES.O_MR1) diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/Size2D.java b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/Size2D.java new file mode 100644 index 00000000..2956a91c --- /dev/null +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/types/Size2D.java @@ -0,0 +1,81 @@ +package com.pichillilorenzo.flutter_inappwebview.types; + +import androidx.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class Size2D { + private double width; + private double height; + + public Size2D(double width, double height) { + this.width = width; + this.height = height; + } + + @Nullable + public static Size2D fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + Double width = (Double) map.get("width"); + Double height = (Double) map.get("height"); + assert width != null; + assert height != null; + return new Size2D(width, height); + } + + public Map toMap() { + Map sizeMap = new HashMap<>(); + sizeMap.put("width", width); + sizeMap.put("height", height); + return sizeMap; + } + + public double getWidth() { + return width; + } + + public void setWidth(double width) { + this.width = width; + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Size2D size = (Size2D) o; + + if (Double.compare(size.width, width) != 0) return false; + return Double.compare(size.height, height) == 0; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(width); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(height); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "Size{" + + "width=" + width + + ", height=" + height + + '}'; + } +} diff --git a/example/integration_test/webview_flutter_test.dart b/example/integration_test/webview_flutter_test.dart index 3c17b147..e944839c 100644 --- a/example/integration_test/webview_flutter_test.dart +++ b/example/integration_test/webview_flutter_test.dart @@ -3247,6 +3247,45 @@ setTimeout(function() { await expectLater(onTitleChangedCompleter.future, completes); }); + testWidgets('onZoomScaleChanged', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer pageLoaded = Completer(); + final Completer onZoomScaleChangedCompleter = Completer(); + + var listenForScaleChange = false; + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: Uri.parse('https://github.com/flutter')), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onLoadStop: (controller, url) { + pageLoaded.complete(); + }, + onZoomScaleChanged: (controller, oldScale, newScale) { + if (listenForScaleChange) { + onZoomScaleChangedCompleter.complete(); + } + }, + ), + ), + ); + + final InAppWebViewController controller = + await controllerCompleter.future; + await pageLoaded.future; + listenForScaleChange = true; + + await controller.zoomBy(zoomFactor: 2); + + await expectLater(onZoomScaleChangedCompleter.future, completes); + }); + testWidgets('androidOnPermissionRequest', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); @@ -3341,50 +3380,6 @@ setTimeout(function() { expect(resourceLoaded, containsAll(resourceList)); }, skip: !Platform.isAndroid); - testWidgets('androidOnScaleChanged', (WidgetTester tester) async { - final Completer controllerCompleter = Completer(); - final Completer pageLoaded = Completer(); - final Completer onScaleChangedCompleter = Completer(); - - var listenForScaleChange = false; - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: InAppWebView( - key: GlobalKey(), - initialUrlRequest: - URLRequest(url: Uri.parse('https://flutter.dev/')), - onWebViewCreated: (controller) { - controllerCompleter.complete(controller); - }, - onLoadStop: (controller, url) { - pageLoaded.complete(); - }, - androidOnScaleChanged: (controller, oldScale, newScale) { - if (listenForScaleChange) { - onScaleChangedCompleter.complete(); - } - }, - ), - ), - ); - - final InAppWebViewController controller = - await controllerCompleter.future; - await pageLoaded.future; - listenForScaleChange = true; - - await controller.evaluateJavascript(source: """ - var meta = document.createElement('meta'); - meta.setAttribute('name', 'viewport'); - meta.setAttribute('content', 'width=device-width, initial-scale=2.0, maximum-scale=2.0, minimum-scale=2.0, user-scalable=no'); - document.getElementsByTagName('head')[0].appendChild(meta); - """); - - await expectLater(onScaleChangedCompleter.future, completes); - }, skip: !Platform.isAndroid); - testWidgets('androidOnReceivedIcon', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); @@ -4540,7 +4535,7 @@ setTimeout(function() { controller.zoomBy(zoomFactor: 3.0, iosAnimated: true), completes); }); - testWidgets('getScale', (WidgetTester tester) async { + testWidgets('getZoomScale', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); @@ -4565,7 +4560,7 @@ setTimeout(function() { await controllerCompleter.future; await pageLoaded.future; - final scale = await controller.getScale(); + final scale = await controller.getZoomScale(); expect(scale, isNonZero); expect(scale, isPositive); }); diff --git a/flutter_inappwebview.iml b/flutter_inappwebview.iml index 4cb39159..0adae5aa 100755 --- a/flutter_inappwebview.iml +++ b/flutter_inappwebview.iml @@ -80,6 +80,5 @@ - \ No newline at end of file diff --git a/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift new file mode 100644 index 00000000..6fb9a1be --- /dev/null +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebView.swift @@ -0,0 +1,95 @@ +// +// HeadlessInAppWebView.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 26/03/21. +// + +import Foundation + +public class HeadlessInAppWebView : FlutterMethodCallDelegate { + var id: String + var channel: FlutterMethodChannel? + var flutterWebView: FlutterWebViewController? + + public init(id: String, flutterWebView: FlutterWebViewController) { + self.id = id + super.init() + self.flutterWebView = flutterWebView + self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_headless_inappwebview_" + id, + binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger()) + self.channel?.setMethodCallHandler(self.handle) + } + + public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let arguments = call.arguments as? NSDictionary + + switch call.method { + case "dispose": + dispose() + result(true) + break + case "setSize": + let sizeMap = arguments!["size"] as? [String: Any?] + if let size = Size2D.fromMap(map: sizeMap) { + setSize(size: size) + } + result(true) + break + case "getSize": + result(getSize()?.toMap()) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + public func onWebViewCreated() { + let arguments: [String: Any?] = [:] + channel?.invokeMethod("onWebViewCreated", arguments: arguments) + } + + public func prepare(params: NSDictionary) { + if let view = flutterWebView?.view() { + view.alpha = 0.01 + let initialSize = params["initialSize"] as? [String: Any?] + if let size = Size2D.fromMap(map: initialSize) { + setSize(size: size) + } else { + view.frame = CGRect(x: 0.0, y: 0.0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) + } + if let keyWindow = UIApplication.shared.keyWindow { + /// Note: The WKWebView behaves very unreliable when rendering offscreen + /// on a device. This is especially true with JavaScript, which simply + /// won't be executed sometimes. + /// So, add the headless WKWebView to the view hierarchy. + /// This way is also possible to take screenshots. + keyWindow.insertSubview(view, at: 0) + keyWindow.sendSubviewToBack(view) + } + } + } + + public func setSize(size: Size2D) { + if let view = flutterWebView?.view() { + let width = size.width == -1.0 ? UIScreen.main.bounds.width : CGFloat(size.width) + let height = size.height == -1.0 ? UIScreen.main.bounds.height : CGFloat(size.height) + view.frame = CGRect(x: 0.0, y: 0.0, width: width, height: height) + } + } + + public func getSize() -> Size2D? { + if let view = flutterWebView?.view() { + return Size2D(width: Double(view.frame.width), height: Double(view.frame.height)) + } + return nil + } + + public func dispose() { + channel?.setMethodCallHandler(nil) + channel = nil + HeadlessInAppWebViewManager.webViews.removeValue(forKey: id) + flutterWebView = nil + } +} diff --git a/ios/Classes/InAppWebView/HeadlessInAppWebViewManager.swift b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift similarity index 65% rename from ios/Classes/InAppWebView/HeadlessInAppWebViewManager.swift rename to ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift index 9bb1aa17..0bcc777c 100644 --- a/ios/Classes/InAppWebView/HeadlessInAppWebViewManager.swift +++ b/ios/Classes/HeadlessInAppWebView/HeadlessInAppWebViewManager.swift @@ -16,7 +16,7 @@ import AVFoundation public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin { static var registrar: FlutterPluginRegistrar? static var channel: FlutterMethodChannel? - var flutterWebViews: [String: FlutterWebViewController] = [:] + static var webViews: [String: HeadlessInAppWebView] = [:] public static func register(with registrar: FlutterPluginRegistrar) { @@ -34,13 +34,9 @@ public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin { let id: String = arguments!["id"] as! String switch call.method { - case "createHeadlessWebView": + case "run": let params = arguments!["params"] as! [String: Any?] - createHeadlessWebView(id: id, params: params) - result(true) - break - case "disposeHeadlessWebView": - disposeHeadlessWebView(id: id) + HeadlessInAppWebViewManager.run(id: id, params: params) result(true) break default: @@ -49,17 +45,16 @@ public class HeadlessInAppWebViewManager: NSObject, FlutterPlugin { } } - public func createHeadlessWebView(id: String, params: [String: Any?]) { - let controller = FlutterWebViewController(registrar: HeadlessInAppWebViewManager.registrar!, + public static func run(id: String, params: [String: Any?]) { + let flutterWebView = FlutterWebViewController(registrar: HeadlessInAppWebViewManager.registrar!, withFrame: CGRect.zero, viewIdentifier: id, - arguments: params as NSDictionary) - flutterWebViews[id] = controller - } - - public func disposeHeadlessWebView(id: String) { - if let _ = flutterWebViews[id] { - flutterWebViews.removeValue(forKey: id) - } + params: params as NSDictionary) + let headlessInAppWebView = HeadlessInAppWebView(id: id, flutterWebView: flutterWebView) + HeadlessInAppWebViewManager.webViews[id] = headlessInAppWebView + + headlessInAppWebView.prepare(params: params as NSDictionary) + headlessInAppWebView.onWebViewCreated() + flutterWebView.makeInitialLoad(params: params as NSDictionary) } } diff --git a/ios/Classes/InAppWebView/FlutterWebViewController.swift b/ios/Classes/InAppWebView/FlutterWebViewController.swift index 0ebe03e9..c2a3dd0b 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewController.swift @@ -17,7 +17,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { var myView: UIView? var methodCallDelegate: InAppWebViewMethodHandler? - init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, arguments args: NSDictionary) { + init(registrar: FlutterPluginRegistrar, withFrame frame: CGRect, viewIdentifier viewId: Any, params: NSDictionary) { super.init() self.registrar = registrar @@ -29,14 +29,11 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { myView = UIView(frame: frame) myView!.clipsToBounds = true - let initialUrlRequest = args["initialUrlRequest"] as? [String: Any?] - let initialFile = args["initialFile"] as? String - let initialData = args["initialData"] as? [String: String] - let initialOptions = args["initialOptions"] as! [String: Any?] - let contextMenu = args["contextMenu"] as? [String: Any] - let windowId = args["windowId"] as? Int64 - let initialUserScripts = args["initialUserScripts"] as? [[String: Any]] - let pullToRefreshInitialOptions = args["pullToRefreshOptions"] as! [String: Any?] + let initialOptions = params["initialOptions"] as! [String: Any?] + let contextMenu = params["contextMenu"] as? [String: Any] + let windowId = params["windowId"] as? Int64 + let initialUserScripts = params["initialUserScripts"] as? [[String: Any]] + let pullToRefreshInitialOptions = params["pullToRefreshOptions"] as! [String: Any?] var userScripts: [UserScript] = [] if let initialUserScripts = initialUserScripts { @@ -83,6 +80,17 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { webView!.options = options webView!.prepare() webView!.windowCreated = true + } + + public func view() -> UIView { + return myView! + } + + public func makeInitialLoad(params: NSDictionary) { + let windowId = params["windowId"] as? Int64 + let initialUrlRequest = params["initialUrlRequest"] as? [String: Any?] + let initialFile = params["initialFile"] as? String + let initialData = params["initialData"] as? [String: String] if windowId == nil { if #available(iOS 11.0, *) { @@ -116,37 +124,9 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { else if let wId = windowId, let webViewTransport = InAppWebView.windowWebViews[wId] { webView!.load(webViewTransport.request) } - - if (frame.isEmpty && viewId is String) { - /// Note: The WKWebView behaves very unreliable when rendering offscreen - /// on a device. This is especially true with JavaScript, which simply - /// won't be executed sometimes. - /// Therefore, I decided to add this very ugly hack where the rendering - /// webview will be added to the view hierarchy (between the - /// rootViewController's view and the key window). - self.myView!.alpha = 0.01 - UIApplication.shared.keyWindow!.insertSubview(self.myView!, at: 0) - - let arguments: [String: Any] = ["id": viewId] - channel!.invokeMethod("onHeadlessWebViewCreated", arguments: arguments) - } } - deinit { - print("FlutterWebViewController - dealloc") - channel?.setMethodCallHandler(nil) - methodCallDelegate?.webView = nil - methodCallDelegate = nil - webView?.dispose() - webView = nil - myView = nil - } - - public func view() -> UIView { - return myView! - } - - public func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String]?) { + func load(initialUrlRequest: [String:Any?]?, initialFile: String?, initialData: [String: String]?) { if let initialFile = initialFile { do { try webView?.loadFile(assetFilePath: initialFile) @@ -174,4 +154,18 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView { webView?.loadUrl(urlRequest: urlRequest, allowingReadAccessTo: allowingReadAccessToURL) } } + + func dispose() { + channel?.setMethodCallHandler(nil) + methodCallDelegate?.webView = nil + methodCallDelegate = nil + webView?.dispose() + webView = nil + myView = nil + } + + deinit { + print("FlutterWebViewController - dealloc") + dispose() + } } diff --git a/ios/Classes/InAppWebView/FlutterWebViewFactory.swift b/ios/Classes/InAppWebView/FlutterWebViewFactory.swift index 181441f0..30fcce52 100755 --- a/ios/Classes/InAppWebView/FlutterWebViewFactory.swift +++ b/ios/Classes/InAppWebView/FlutterWebViewFactory.swift @@ -25,7 +25,8 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { let webviewController = FlutterWebViewController(registrar: registrar!, withFrame: frame, viewIdentifier: viewId, - arguments: arguments!) + params: arguments!) + webviewController.makeInitialLoad(params: arguments!) return webviewController } } diff --git a/ios/Classes/InAppWebView/InAppWebView.swift b/ios/Classes/InAppWebView/InAppWebView.swift index bd752306..d28a8904 100755 --- a/ios/Classes/InAppWebView/InAppWebView.swift +++ b/ios/Classes/InAppWebView/InAppWebView.swift @@ -53,6 +53,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi var callAsyncJavaScriptBelowIOS14Results: [String:((Any?) -> Void)] = [:] + var oldZoomScale = Float(1.0) + init(frame: CGRect, configuration: WKWebViewConfiguration, contextMenu: [String: Any]?, channel: FlutterMethodChannel?, userScripts: [UserScript] = []) { super.init(frame: frame, configuration: configuration) self.channel = channel @@ -270,6 +272,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi scrollView.addGestureRecognizer(self.longPressRecognizer) scrollView.addGestureRecognizer(self.recognizerForDisablingContextMenuOnLinks) scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.new, .old], context: nil) + scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale), options: [.new, .old], context: nil) addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), @@ -2056,6 +2059,14 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi } } + public func scrollViewDidZoom(_ scrollView: UIScrollView) { + let newScale = Float(scrollView.zoomScale) + if newScale != oldZoomScale { + self.onZoomScaleChanged(newScale: newScale, oldScale: oldZoomScale) + oldZoomScale = newScale + } + } + public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, @@ -2306,6 +2317,11 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi channel?.invokeMethod("onScrollChanged", arguments: arguments) } + public func onZoomScaleChanged(newScale: Float, oldScale: Float) { + let arguments: [String: Any] = ["newScale": newScale, "oldScale": oldScale] + channel?.invokeMethod("onZoomScaleChanged", arguments: arguments) + } + public func onOverScrolled(x: Int, y: Int, clampedX: Bool, clampedY: Bool) { let arguments: [String: Any] = ["x": x, "y": y, "clampedX": clampedX, "clampedY": clampedY] channel?.invokeMethod("onOverScrolled", arguments: arguments) @@ -2678,7 +2694,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { scrollView.setZoomScale(currentZoomScale * CGFloat(zoomFactor), animated: animated) } - public func getScale() -> Float { + public func getZoomScale() -> Float { return Float(scrollView.zoomScale) } @@ -2869,6 +2885,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { imp_removeBlock(imp) } scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset)) + scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale)) longPressRecognizer.removeTarget(self, action: #selector(longPressGestureDetected)) longPressRecognizer.delegate = nil scrollView.removeGestureRecognizer(longPressRecognizer) diff --git a/ios/Classes/InAppWebViewMethodHandler.swift b/ios/Classes/InAppWebViewMethodHandler.swift index d65d58aa..ae3d0b60 100644 --- a/ios/Classes/InAppWebViewMethodHandler.swift +++ b/ios/Classes/InAppWebViewMethodHandler.swift @@ -293,8 +293,8 @@ public class InAppWebViewMethodHandler: FlutterMethodCallDelegate { webView?.reloadFromOrigin() result(true) break - case "getScale": - result(webView?.getScale()) + case "getZoomScale": + result(webView?.getZoomScale()) break case "hasOnlySecureContent": result(webView?.hasOnlySecureContent ?? false) diff --git a/ios/Classes/Types/Size2D.swift b/ios/Classes/Types/Size2D.swift new file mode 100644 index 00000000..7c2b9c6c --- /dev/null +++ b/ios/Classes/Types/Size2D.swift @@ -0,0 +1,35 @@ +// +// Size.swift +// flutter_inappwebview +// +// Created by Lorenzo Pichilli on 26/03/21. +// + +import Foundation + +public class Size2D : NSObject { + var width: Double + var height: Double + + public init(width: Double, height: Double) { + self.width = width + self.height = height + } + + public static func fromMap(map: [String:Any?]?) -> Size2D? { + guard let map = map else { + return nil + } + return Size2D( + width: map["width"] as? Double ?? -1.0, + height: map["height"] as? Double ?? -1.0 + ) + } + + public func toMap() -> [String:Any?] { + return [ + "width": width, + "height": height + ] + } +} diff --git a/lib/src/in_app_browser/in_app_browser.dart b/lib/src/in_app_browser/in_app_browser.dart index f60753da..ff4e2877 100755 --- a/lib/src/in_app_browser/in_app_browser.dart +++ b/lib/src/in_app_browser/in_app_browser.dart @@ -59,7 +59,7 @@ class InAppBrowser { const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); /// WebView Controller that can be used to access the [InAppWebViewController] API. - late InAppWebViewController webViewController; + late final InAppWebViewController webViewController; ///The window id of a [CreateWindowAction.windowId]. final int? windowId; @@ -620,6 +620,19 @@ class InAppBrowser { ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebView#onOverScrolled(int,%20int,%20boolean,%20boolean) void onOverScrolled(int x, int y, bool clampedX, bool clampedY) {} + ///Event fired when the zoom scale of the WebView has changed. + /// + ///[oldScale] The old zoom scale factor. + /// + ///[newScale] The new zoom scale factor. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + /// + ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619409-scrollviewdidzoom + void onZoomScaleChanged(double oldScale, double newScale) {} + ///Event fired when the WebView notifies that a loading URL has been flagged by Safe Browsing. ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. /// @@ -738,15 +751,8 @@ class InAppBrowser { ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onFormResubmission(android.webkit.WebView,%20android.os.Message,%20android.os.Message) Future? androidOnFormResubmission(Uri? url) {} - ///Event fired when the scale applied to the WebView has changed. - /// - ///[oldScale] The old scale factor. - /// - ///[newScale] The new scale factor. - /// - ///**NOTE**: available only on Android. - /// - ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use `onZoomScaleChanged` instead') void androidOnScaleChanged(double oldScale, double newScale) {} ///Event fired when there is new favicon for the current page. diff --git a/lib/src/in_app_webview/headless_in_app_webview.dart b/lib/src/in_app_webview/headless_in_app_webview.dart index f544527e..33f8d208 100644 --- a/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,5 +1,6 @@ import 'dart:collection'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview/src/util.dart'; @@ -11,6 +12,7 @@ import 'in_app_webview_controller.dart'; import 'in_app_webview_options.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; import '../pull_to_refresh/pull_to_refresh_options.dart'; +import '../util.dart'; ///Class that represents a WebView in headless mode. ///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree. @@ -23,17 +25,33 @@ class HeadlessInAppWebView implements WebView { bool _started = false; bool _running = false; - static const MethodChannel _sharedChannel = - const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); + static const MethodChannel _sharedChannel = const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); + late MethodChannel _channel; ///WebView Controller that can be used to access the [InAppWebViewController] API. - late InAppWebViewController webViewController; + late final InAppWebViewController webViewController; ///The window id of a [CreateWindowAction.windowId]. final int? windowId; + ///The WebView initial size in pixels. + /// + ///Set `-1` to match the corresponding width or height of the current device screen size. + ///`Size(-1, -1)` will match both width and height of the current device screen size. + /// + ///**NOTE for Android**: `Size` width and height values will be converted to `int` values because they cannot have `double` values. + final Size initialSize; + HeadlessInAppWebView( - {this.windowId, + {this.initialSize = const Size(-1, -1), + this.windowId, + this.initialUrlRequest, + this.initialFile, + this.initialData, + this.initialOptions, + this.contextMenu, + this.initialUserScripts, + this.pullToRefreshController, this.onWebViewCreated, this.onLoadStart, this.onLoadStop, @@ -78,6 +96,7 @@ class HeadlessInAppWebView implements WebView { this.androidOnRenderProcessResponsive, this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, + @Deprecated('Use `onZoomScaleChanged` instead') this.androidOnScaleChanged, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, @@ -86,29 +105,26 @@ class HeadlessInAppWebView implements WebView { this.iosOnWebContentProcessDidTerminate, this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.iosOnNavigationResponse, - this.iosShouldAllowDeprecatedTLS, - this.initialUrlRequest, - this.initialFile, - this.initialData, - this.initialOptions, - this.contextMenu, - this.initialUserScripts, - this.pullToRefreshController}) { + this.iosShouldAllowDeprecatedTLS}) { id = IdGenerator.generate(); webViewController = new InAppWebViewController(id, this); + this._channel = MethodChannel( + 'com.pichillilorenzo/flutter_headless_inappwebview_$id'); + this._channel.setMethodCallHandler(handleMethod); } Future handleMethod(MethodCall call) async { switch (call.method) { - case "onHeadlessWebViewCreated": + case "onWebViewCreated": pullToRefreshController?.initMethodChannel(id); if (onWebViewCreated != null) { onWebViewCreated!(webViewController); } break; default: - return webViewController.handleMethod(call); + throw UnimplementedError("Unimplemented ${call.method} method"); } + return null; } ///Runs the headless WebView. @@ -134,9 +150,10 @@ class HeadlessInAppWebView implements WebView { this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], 'pullToRefreshOptions': this.pullToRefreshController?.options.toMap() ?? - PullToRefreshOptions(enabled: false).toMap() + PullToRefreshOptions(enabled: false).toMap(), + 'initialSize': this.initialSize.toMap() }); - await _sharedChannel.invokeMethod('createHeadlessWebView', args); + await _sharedChannel.invokeMethod('run', args); _running = true; } @@ -146,8 +163,7 @@ class HeadlessInAppWebView implements WebView { return; } Map args = {}; - args.putIfAbsent('id', () => id); - await _sharedChannel.invokeMethod('disposeHeadlessWebView', args); + await _channel.invokeMethod('dispose', args); _started = false; _running = false; } @@ -157,26 +173,24 @@ class HeadlessInAppWebView implements WebView { return _running; } - @override - final void Function(InAppWebViewController controller)? - androidOnGeolocationPermissionsHidePrompt; + ///Set the size of the WebView in pixels. + /// + ///Set `-1` to match the corresponding width or height of the current device screen size. + ///`Size(-1, -1)` will match both width and height of the current device screen size. + /// + ///**NOTE for Android**: `Size` width and height values will be converted to `int` values because they cannot have `double` values. + Future setSize(Size size) async { + Map args = {}; + args.putIfAbsent('size', () => size.toMap()); + await _channel.invokeMethod('setSize', args); + } - @override - final Future Function( - InAppWebViewController controller, String origin)? - androidOnGeolocationPermissionsShowPrompt; - - @override - final Future Function( - InAppWebViewController controller, - String origin, - List resources)? androidOnPermissionRequest; - - @override - final Future Function( - InAppWebViewController controller, - Uri url, - SafeBrowsingThreat? threatType)? androidOnSafeBrowsingHit; + ///Gets the current size in pixels of the WebView. + Future getSize() async { + Map args = {}; + Map sizeMap = (await _channel.invokeMethod('getSize', args))?.cast(); + return MapSize.fromMap(sizeMap); + } @override final InAppWebViewInitialData? initialData; @@ -200,211 +214,239 @@ class HeadlessInAppWebView implements WebView { final PullToRefreshController? pullToRefreshController; @override - final void Function(InAppWebViewController controller, Uri? url)? + void Function(InAppWebViewController controller)? + androidOnGeolocationPermissionsHidePrompt; + + @override + Future Function( + InAppWebViewController controller, String origin)? + androidOnGeolocationPermissionsShowPrompt; + + @override + Future Function( + InAppWebViewController controller, + String origin, + List resources)? androidOnPermissionRequest; + + @override + Future Function( + InAppWebViewController controller, + Uri url, + SafeBrowsingThreat? threatType)? androidOnSafeBrowsingHit; + + @override + void Function(InAppWebViewController controller, Uri? url)? onPageCommitVisible; @override - final void Function(InAppWebViewController controller, String? title)? + void Function(InAppWebViewController controller, String? title)? onTitleChanged; @override - final void Function(InAppWebViewController controller)? + void Function(InAppWebViewController controller)? iosOnDidReceiveServerRedirectForProvisionalNavigation; @override - final void Function(InAppWebViewController controller)? + void Function(InAppWebViewController controller)? iosOnWebContentProcessDidTerminate; @override - final Future Function( + Future Function( InAppWebViewController controller, IOSWKNavigationResponse navigationResponse)? iosOnNavigationResponse; @override - final Future Function( + Future Function( InAppWebViewController controller, URLAuthenticationChallenge challenge)? iosShouldAllowDeprecatedTLS; @override - final Future Function( + Future Function( InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxProgress; @override - final Future Function( + Future Function( InAppWebViewController controller, AjaxRequest ajaxRequest)? onAjaxReadyStateChange; @override - final void Function( + void Function( InAppWebViewController controller, ConsoleMessage consoleMessage)? onConsoleMessage; @override - final Future Function(InAppWebViewController controller, + Future Function(InAppWebViewController controller, CreateWindowAction createWindowAction)? onCreateWindow; @override - final void Function(InAppWebViewController controller)? onCloseWindow; + void Function(InAppWebViewController controller)? onCloseWindow; @override - final void Function(InAppWebViewController controller)? onWindowFocus; + void Function(InAppWebViewController controller)? onWindowFocus; @override - final void Function(InAppWebViewController controller)? onWindowBlur; + void Function(InAppWebViewController controller)? onWindowBlur; @override - final void Function(InAppWebViewController controller, Uri url)? + void Function(InAppWebViewController controller, Uri url)? onDownloadStart; @override - final void Function(InAppWebViewController controller, int activeMatchOrdinal, + void Function(InAppWebViewController controller, int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting)? onFindResultReceived; @override - final Future Function( + Future Function( InAppWebViewController controller, JsAlertRequest jsAlertRequest)? onJsAlert; @override - final Future Function( + Future Function( InAppWebViewController controller, JsConfirmRequest jsConfirmRequest)? onJsConfirm; @override - final Future Function( + Future Function( InAppWebViewController controller, JsPromptRequest jsPromptRequest)? onJsPrompt; @override - final void Function(InAppWebViewController controller, Uri? url, int code, + void Function(InAppWebViewController controller, Uri? url, int code, String message)? onLoadError; @override - final void Function(InAppWebViewController controller, Uri? url, + void Function(InAppWebViewController controller, Uri? url, int statusCode, String description)? onLoadHttpError; @override - final void Function( + void Function( InAppWebViewController controller, LoadedResource resource)? onLoadResource; @override - final Future Function( + Future Function( InAppWebViewController controller, Uri url)? onLoadResourceCustomScheme; @override - final void Function(InAppWebViewController controller, Uri? url)? onLoadStart; + void Function(InAppWebViewController controller, Uri? url)? onLoadStart; @override - final void Function(InAppWebViewController controller, Uri? url)? onLoadStop; + void Function(InAppWebViewController controller, Uri? url)? onLoadStop; @override - final void Function(InAppWebViewController controller, + void Function(InAppWebViewController controller, InAppWebViewHitTestResult hitTestResult)? onLongPressHitTestResult; @override - final void Function(InAppWebViewController controller, Uri? url)? onPrint; + void Function(InAppWebViewController controller, Uri? url)? onPrint; @override - final void Function(InAppWebViewController controller, int progress)? + void Function(InAppWebViewController controller, int progress)? onProgressChanged; @override - final Future Function(InAppWebViewController controller, + Future Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? onReceivedClientCertRequest; @override - final Future Function(InAppWebViewController controller, + Future Function(InAppWebViewController controller, URLAuthenticationChallenge challenge)? onReceivedHttpAuthRequest; @override - final Future Function( + Future Function( InAppWebViewController controller, URLAuthenticationChallenge challenge)? onReceivedServerTrustAuthRequest; @override - final void Function(InAppWebViewController controller, int x, int y)? + void Function(InAppWebViewController controller, int x, int y)? onScrollChanged; @override - final void Function( + void Function( InAppWebViewController controller, Uri? url, bool? androidIsReload)? onUpdateVisitedHistory; @override - final void Function(InAppWebViewController controller)? onWebViewCreated; + void Function(InAppWebViewController controller)? onWebViewCreated; @override - final Future Function( + Future Function( InAppWebViewController controller, AjaxRequest ajaxRequest)? shouldInterceptAjaxRequest; @override - final Future Function( + Future Function( InAppWebViewController controller, FetchRequest fetchRequest)? shouldInterceptFetchRequest; @override - final Future Function( + Future Function( InAppWebViewController controller, NavigationAction navigationAction)? shouldOverrideUrlLoading; @override - final void Function(InAppWebViewController controller)? onEnterFullscreen; + void Function(InAppWebViewController controller)? onEnterFullscreen; @override - final void Function(InAppWebViewController controller)? onExitFullscreen; + void Function(InAppWebViewController controller)? onExitFullscreen; @override - final void Function(InAppWebViewController controller, int x, int y, + void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled; @override - final Future Function( + void Function( + InAppWebViewController controller, double oldScale, double newScale)? + onZoomScaleChanged; + + @override + Future Function( InAppWebViewController controller, WebResourceRequest request)? androidShouldInterceptRequest; @override - final Future Function( + Future Function( InAppWebViewController controller, Uri? url)? androidOnRenderProcessUnresponsive; @override - final Future Function( + Future Function( InAppWebViewController controller, Uri? url)? androidOnRenderProcessResponsive; @override - final void Function( + void Function( InAppWebViewController controller, RenderProcessGoneDetail detail)? androidOnRenderProcessGone; @override - final Future Function( + Future Function( InAppWebViewController controller, Uri? url)? androidOnFormResubmission; + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use `onZoomScaleChanged` instead') @override - final void Function( + void Function( InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged; @override - final void Function(InAppWebViewController controller, Uint8List icon)? + void Function(InAppWebViewController controller, Uint8List icon)? androidOnReceivedIcon; @override - final void Function( + void Function( InAppWebViewController controller, Uri url, bool precomposed)? androidOnReceivedTouchIconUrl; @override - final Future Function( + Future Function( InAppWebViewController controller, JsBeforeUnloadRequest jsBeforeUnloadRequest)? androidOnJsBeforeUnload; @override - final void Function( + void Function( InAppWebViewController controller, LoginRequest loginRequest)? androidOnReceivedLoginRequest; } diff --git a/lib/src/in_app_webview/in_app_webview.dart b/lib/src/in_app_webview/in_app_webview.dart index 7c960b34..733cc083 100755 --- a/lib/src/in_app_webview/in_app_webview.dart +++ b/lib/src/in_app_webview/in_app_webview.dart @@ -77,6 +77,7 @@ class InAppWebView extends StatefulWidget implements WebView { this.onWindowFocus, this.onWindowBlur, this.onOverScrolled, + this.onZoomScaleChanged, this.androidOnSafeBrowsingHit, this.androidOnPermissionRequest, this.androidOnGeolocationPermissionsShowPrompt, @@ -86,6 +87,7 @@ class InAppWebView extends StatefulWidget implements WebView { this.androidOnRenderProcessResponsive, this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, + @Deprecated('Use `onZoomScaleChanged` instead') this.androidOnScaleChanged, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, @@ -313,6 +315,11 @@ class InAppWebView extends StatefulWidget implements WebView { final void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled; + @override + final void Function( + InAppWebViewController controller, double oldScale, double newScale)? + onZoomScaleChanged; + @override final Future Function( InAppWebViewController controller, WebResourceRequest request)? @@ -337,6 +344,8 @@ class InAppWebView extends StatefulWidget implements WebView { final Future Function( InAppWebViewController controller, Uri? url)? androidOnFormResubmission; + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use `onZoomScaleChanged` instead') @override final void Function( InAppWebViewController controller, double oldScale, double newScale)? diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index 386ecc0a..91beb5ec 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -101,12 +101,6 @@ class InAppWebViewController { Future handleMethod(MethodCall call) async { switch (call.method) { - case "onHeadlessWebViewCreated": - if (_webview != null && - _webview is HeadlessInAppWebView && - _webview!.onWebViewCreated != null) - _webview!.onWebViewCreated!(this); - break; case "onLoadStart": if ((_webview != null && _webview!.onLoadStart != null) || _inAppBrowser != null) { @@ -361,13 +355,17 @@ class InAppWebViewController { ?.toMap(); } break; - case "onScaleChanged": - if ((_webview != null && _webview!.androidOnScaleChanged != null) || + case "onZoomScaleChanged": + if ((_webview != null && (_webview!.androidOnScaleChanged != null || _webview!.onZoomScaleChanged != null)) || _inAppBrowser != null) { double oldScale = call.arguments["oldScale"]; double newScale = call.arguments["newScale"]; - if (_webview != null && _webview!.androidOnScaleChanged != null) - _webview!.androidOnScaleChanged!(this, oldScale, newScale); + if (_webview != null) { + if (_webview!.onZoomScaleChanged != null) + _webview!.onZoomScaleChanged!(this, oldScale, newScale); + else + _webview!.androidOnScaleChanged!(this, oldScale, newScale); + } else _inAppBrowser!.androidOnScaleChanged(oldScale, newScale); } @@ -1703,16 +1701,18 @@ class InAppWebViewController { return await _channel.invokeMethod('zoomBy', args); } - ///Gets the current scale of this WebView. - /// - ///**Official Android API**: - ///- https://developer.android.com/reference/android/util/DisplayMetrics#density - ///- https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + ///Gets the current zoom scale of the WebView. /// ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollview/1619419-zoomscale - Future getScale() async { + Future getZoomScale() async { Map args = {}; - return await _channel.invokeMethod('getScale', args); + return await _channel.invokeMethod('getZoomScale', args); + } + + ///Use [getZoomScale] instead. + @Deprecated('Use `getZoomScale` instead') + Future getScale() async { + return await getZoomScale(); } ///Gets the selected text. diff --git a/lib/src/in_app_webview/webview.dart b/lib/src/in_app_webview/webview.dart index d63bab68..3bc77ecf 100644 --- a/lib/src/in_app_webview/webview.dart +++ b/lib/src/in_app_webview/webview.dart @@ -401,6 +401,21 @@ abstract class WebView { final void Function(InAppWebViewController controller, int x, int y, bool clampedX, bool clampedY)? onOverScrolled; + ///Event fired when the zoom scale of the WebView has changed. + /// + ///[oldScale] The old zoom scale factor. + /// + ///[newScale] The new zoom scale factor. + /// + ///**NOTE**: available only on Android. + /// + ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + /// + ///**Official iOS API**: https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619409-scrollviewdidzoom + final void Function( + InAppWebViewController controller, double oldScale, double newScale)? + onZoomScaleChanged; + ///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing. ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible. /// @@ -531,15 +546,8 @@ abstract class WebView { final Future Function( InAppWebViewController controller, Uri? url)? androidOnFormResubmission; - ///Event fired when the scale applied to the WebView has changed. - /// - ///[oldScale] The old scale factor. - /// - ///[newScale] The new scale factor. - /// - ///**NOTE**: available only on Android. - /// - ///**Official Android API**: https://developer.android.com/reference/android/webkit/WebViewClient#onScaleChanged(android.webkit.WebView,%20float,%20float) + ///Use [onZoomScaleChanged] instead. + @Deprecated('Use `onZoomScaleChanged` instead') final void Function( InAppWebViewController controller, double oldScale, double newScale)? androidOnScaleChanged; @@ -702,6 +710,7 @@ abstract class WebView { this.onWindowFocus, this.onWindowBlur, this.onOverScrolled, + this.onZoomScaleChanged, this.androidOnSafeBrowsingHit, this.androidOnPermissionRequest, this.androidOnGeolocationPermissionsShowPrompt, @@ -711,6 +720,7 @@ abstract class WebView { this.androidOnRenderProcessResponsive, this.androidOnRenderProcessUnresponsive, this.androidOnFormResubmission, + @Deprecated('Use `onZoomScaleChanged` instead') this.androidOnScaleChanged, this.androidOnReceivedIcon, this.androidOnReceivedTouchIconUrl, diff --git a/lib/src/util.dart b/lib/src/util.dart index 9cf8b9dd..fb3192d5 100644 --- a/lib/src/util.dart +++ b/lib/src/util.dart @@ -485,3 +485,23 @@ extension HexColor on Color { '${green.toRadixString(16).padLeft(2, '0')}' '${blue.toRadixString(16).padLeft(2, '0')}'; } + +extension MapSize on Size { + static Size? fromMap(Map? map) { + if (map == null) { + return null; + } + return Size(map["width"] ?? -1.0, map["height"] ?? -1.0); + } + + Map toJson() { + return toMap(); + } + + Map toMap() { + return { + 'width': width, + 'height': height + }; + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index a9bdf246..1c1731c5 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappwebview description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. -version: 5.2.1+1 +version: 5.3.0 homepage: https://github.com/pichillilorenzo/flutter_inappwebview environment: