diff --git a/flutter_inappwebview/CHANGELOG.md b/flutter_inappwebview/CHANGELOG.md index 8183257bd..b45f23ceb 100755 --- a/flutter_inappwebview/CHANGELOG.md +++ b/flutter_inappwebview/CHANGELOG.md @@ -11,10 +11,12 @@ #### Platform Interface - Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class -- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress` properties to `InAppWebViewSettings` +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` +- Added `onShowFileChooser` WebView events #### Android Platform - Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event - Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) #### macOS and iOS Platforms diff --git a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart index b7b15065b..dbaeb6d63 100755 --- a/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart +++ b/flutter_inappwebview/lib/src/in_app_browser/in_app_browser.dart @@ -609,4 +609,9 @@ class InAppBrowser implements PlatformInAppBrowserEvents { @override void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + @override + FutureOr onShowFileChooser(ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart index f86793aff..252f3e5f8 100644 --- a/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/headless_in_app_webview.dart @@ -167,7 +167,8 @@ class HeadlessInAppWebView { )? onMicrophoneCaptureStateChanged, void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, - void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed}) + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) : this.fromPlatformCreationParams( params: PlatformHeadlessInAppWebViewCreationParams( controllerFromPlatform: (PlatformInAppWebViewController controller) => @@ -521,6 +522,10 @@ class HeadlessInAppWebView { ? (controller, detail) => onAcceleratorKeyPressed.call(controller, detail) : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, )); ///{@macro flutter_inappwebview_platform_interface.PlatformHeadlessInAppWebView.run} diff --git a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart index b658e652e..6368630e7 100755 --- a/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview/lib/src/in_app_webview/in_app_webview.dart @@ -1,20 +1,19 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import '../webview_environment/webview_environment.dart'; -import 'headless_in_app_webview.dart'; -import 'in_app_webview_controller.dart'; + import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import '../webview_environment/webview_environment.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; ///{@macro flutter_inappwebview_platform_interface.PlatformInAppWebViewWidget} class InAppWebView extends StatefulWidget { @@ -167,7 +166,8 @@ class InAppWebView extends StatefulWidget { )? onMicrophoneCaptureStateChanged, void Function(InAppWebViewController controller, Size oldContentSize, Size newContentSize)? onContentSizeChanged, void Function(InAppWebViewController controller, ProcessFailedDetail detail)? onProcessFailed, - void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed}) + void Function(InAppWebViewController controller, AcceleratorKeyPressedDetail detail)? onAcceleratorKeyPressed, + FutureOr Function(InAppWebViewController controller, ShowFileChooserRequest request)? onShowFileChooser}) : this.fromPlatformCreationParams( key: key, params: PlatformInAppWebViewWidgetCreationParams( @@ -542,6 +542,10 @@ class InAppWebView extends StatefulWidget { ? (controller, detail) => onAcceleratorKeyPressed.call(controller, detail) : null, + onShowFileChooser: onShowFileChooser != null + ? (controller, request) => + onShowFileChooser.call(controller, request) + : null, gestureRecognizers: gestureRecognizers, headlessWebView: headlessWebView?.platform, preventGestureDelay: preventGestureDelay, diff --git a/flutter_inappwebview_android/CHANGELOG.md b/flutter_inappwebview_android/CHANGELOG.md index 9360dccb2..fb2d473f3 100644 --- a/flutter_inappwebview_android/CHANGELOG.md +++ b/flutter_inappwebview_android/CHANGELOG.md @@ -2,6 +2,7 @@ - Updated flutter_inappwebview_platform_interface version to ^1.4.0-beta.3 - Implemented `saveState`, `restoreState` InAppWebViewController methods +- Implemented `onShowFileChooser` WebView event - Merged "Android: implemented PlatformPrintJobController.onComplete" [#2216](https://github.com/pichillilorenzo/flutter_inappwebview/pull/2216) (thanks to [Doflatango](https://github.com/Doflatango)) - Fixed "When useShouldInterceptAjaxRequest is true, some ajax requests doesn't work" [#2197](https://github.com/pichillilorenzo/flutter_inappwebview/issues/2197) diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java new file mode 100644 index 000000000..eb26459b8 --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserRequest.java @@ -0,0 +1,140 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import android.annotation.TargetApi; +import android.os.Build; +import android.webkit.WebChromeClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserRequest { + private int mode; + @NonNull + private List acceptTypes; + private boolean isCaptureEnabled; + @Nullable + private String title; + @Nullable + private String filenameHint; + + public ShowFileChooserRequest(int mode, @NonNull List acceptTypes, boolean isCaptureEnabled, @Nullable String title, @Nullable String filenameHint) { + this.mode = mode; + this.acceptTypes = acceptTypes; + this.isCaptureEnabled = isCaptureEnabled; + this.title = title; + this.filenameHint = filenameHint; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static ShowFileChooserRequest fromFileChooserParams(WebChromeClient.FileChooserParams fileChooserParams) { + int mode = fileChooserParams.getMode(); + List acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes()); + boolean isCaptureEnabled = fileChooserParams.isCaptureEnabled(); + String title = fileChooserParams.getTitle() != null ? fileChooserParams.getTitle().toString() : null; + String filenameHint = fileChooserParams.getFilenameHint(); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + @Nullable + public static ShowFileChooserRequest fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + int mode = (int) map.get("mode"); + List acceptTypes = (List) map.get("acceptTypes"); + boolean isCaptureEnabled = (boolean) map.get("isCaptureEnabled"); + String title = (String) map.get("title"); + String filenameHint = (String) map.get("filenameHint"); + return new ShowFileChooserRequest(mode, acceptTypes, isCaptureEnabled, title, filenameHint); + } + + public Map toMap() { + Map showFileChooserRequestMap = new HashMap<>(); + showFileChooserRequestMap.put("mode", mode); + showFileChooserRequestMap.put("acceptTypes", acceptTypes); + showFileChooserRequestMap.put("isCaptureEnabled", isCaptureEnabled); + showFileChooserRequestMap.put("title", title); + showFileChooserRequestMap.put("filenameHint", filenameHint); + return showFileChooserRequestMap; + } + + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + public @NonNull List getAcceptTypes() { + return acceptTypes; + } + + public void setAcceptTypes(@NonNull List acceptTypes) { + this.acceptTypes = acceptTypes; + } + + public boolean isCaptureEnabled() { + return isCaptureEnabled; + } + + public void setCaptureEnabled(boolean captureEnabled) { + isCaptureEnabled = captureEnabled; + } + + @Nullable + public String getTitle() { + return title; + } + + public void setTitle(@Nullable String title) { + this.title = title; + } + + @Nullable + public String getFilenameHint() { + return filenameHint; + } + + public void setFilenameHint(@Nullable String filenameHint) { + this.filenameHint = filenameHint; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserRequest that = (ShowFileChooserRequest) o; + return mode == that.mode && isCaptureEnabled == that.isCaptureEnabled && acceptTypes.equals(that.acceptTypes) && Objects.equals(title, that.title) && Objects.equals(filenameHint, that.filenameHint); + } + + @Override + public int hashCode() { + int result = mode; + result = 31 * result + acceptTypes.hashCode(); + result = 31 * result + Boolean.hashCode(isCaptureEnabled); + result = 31 * result + Objects.hashCode(title); + result = 31 * result + Objects.hashCode(filenameHint); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserRequest{" + + "mode=" + mode + + ", acceptTypes=" + acceptTypes + + ", isCaptureEnabled=" + isCaptureEnabled + + ", title='" + title + '\'' + + ", filenameHint='" + filenameHint + '\'' + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java new file mode 100644 index 000000000..d7cc839ce --- /dev/null +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/types/ShowFileChooserResponse.java @@ -0,0 +1,71 @@ +package com.pichillilorenzo.flutter_inappwebview_android.types; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ShowFileChooserResponse { + private boolean handledByClient; + @Nullable + private List filePaths; + + public ShowFileChooserResponse(boolean handledByClient, @Nullable List filePaths) { + this.handledByClient = handledByClient; + this.filePaths = filePaths; + } + + @Nullable + public static ShowFileChooserResponse fromMap(@Nullable Map map) { + if (map == null) { + return null; + } + boolean handledByClient = (boolean) map.get("handledByClient"); + List filePaths = (List) map.get("filePaths"); + return new ShowFileChooserResponse(handledByClient, filePaths); + } + + public boolean isHandledByClient() { + return handledByClient; + } + + public void setHandledByClient(boolean handledByClient) { + this.handledByClient = handledByClient; + } + + @Nullable + public List getFilePaths() { + return filePaths; + } + + public void setFilePaths(@Nullable List filePaths) { + this.filePaths = filePaths; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ShowFileChooserResponse that = (ShowFileChooserResponse) o; + return handledByClient == that.handledByClient && Objects.equals(filePaths, that.filePaths); + } + + @Override + public int hashCode() { + int result = Boolean.hashCode(handledByClient); + result = 31 * result + Objects.hashCode(filePaths); + return result; + } + + @NonNull + @Override + public String toString() { + return "ShowFileChooserResponse{" + + "handledByClient=" + handledByClient + + ", filePaths=" + filePaths + + '}'; + } +} diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java index 3a660c7ef..8bcf63ce6 100644 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/WebViewChannelDelegate.java @@ -41,6 +41,8 @@ import com.pichillilorenzo.flutter_inappwebview_android.types.SafeBrowsingResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustAuthResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.ServerTrustChallenge; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.SslCertificateExt; import com.pichillilorenzo.flutter_inappwebview_android.types.SyncBaseCallbackResultImpl; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; @@ -1360,6 +1362,23 @@ public void onRequestFocus() { channel.invokeMethod("onRequestFocus", obj); } + public static class ShowFileChooserCallback extends BaseCallbackResultImpl { + @Nullable + @Override + public ShowFileChooserResponse decodeResult(@Nullable Object obj) { + return ShowFileChooserResponse.fromMap((Map) obj); + } + } + + public void onShowFileChooser(ShowFileChooserRequest request, @NonNull ShowFileChooserCallback callback) { + MethodChannel channel = getChannel(); + if (channel == null) { + callback.defaultBehaviour(null); + return; + } + channel.invokeMethod("onShowFileChooser", request.toMap(), callback); + } + @Override public void dispose() { super.dispose(); diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java index 2f328f345..120962808 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewChromeClient.java @@ -1,5 +1,7 @@ package com.pichillilorenzo.flutter_inappwebview_android.webview.in_app_webview; +import static android.app.Activity.RESULT_OK; + import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; @@ -43,16 +45,18 @@ import androidx.core.content.FileProvider; import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider; -import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; +import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.ActivityResultListener; import com.pichillilorenzo.flutter_inappwebview_android.in_app_browser.InAppBrowserDelegate; -import com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFlutterPlugin; +import com.pichillilorenzo.flutter_inappwebview_android.types.CreateWindowAction; import com.pichillilorenzo.flutter_inappwebview_android.types.GeolocationPermissionShowPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsAlertResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsBeforeUnloadResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsConfirmResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.JsPromptResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.PermissionResponse; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserRequest; +import com.pichillilorenzo.flutter_inappwebview_android.types.ShowFileChooserResponse; import com.pichillilorenzo.flutter_inappwebview_android.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview_android.webview.WebViewChannelDelegate; @@ -62,12 +66,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import io.flutter.plugin.common.PluginRegistry; -import static android.app.Activity.RESULT_OK; - public class InAppWebViewChromeClient extends WebChromeClient implements PluginRegistry.ActivityResultListener, ActivityResultListener { protected static final String LOG_TAG = "IABWebChromeClient"; @@ -76,7 +79,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR private static final int PICKER = 1; private static final int PICKER_LEGACY = 3; final String DEFAULT_MIME_TYPES = "*/*"; - final Map dialogs = new HashMap(); + final Map dialogs = new HashMap<>(); protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER); @@ -524,7 +527,7 @@ public void onCancel(DialogInterface dialog) { @Override public boolean onJsBeforeUnload(final WebView view, String url, final String message, - final JsResult result) { + final JsResult result) { if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onJsBeforeUnload(url, message, new WebViewChannelDelegate.JsBeforeUnloadCallback() { @Override @@ -634,13 +637,13 @@ public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGest String url = result.getExtra(); // Ensure that images with hyperlink return the correct URL, not the image source - if(result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { + if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { Message href = view.getHandler().obtainMessage(); view.requestFocusNodeHref(href); Bundle data = href.getData(); if (data != null) { String imageUrl = data.getString("url"); - if(imageUrl != null && !imageUrl.isEmpty()) { + if (imageUrl != null && !imageUrl.isEmpty()) { url = imageUrl; } } @@ -800,8 +803,8 @@ public void onReceivedIcon(WebView view, Bitmap icon) { @Override public void onReceivedTouchIconUrl(WebView view, - String url, - boolean precomposed) { + String url, + boolean precomposed) { super.onReceivedTouchIconUrl(view, url, precomposed); InAppWebView webView = (InAppWebView) view; @@ -819,25 +822,78 @@ protected ViewGroup getRootView() { return (ViewGroup) activity.findViewById(android.R.id.content); } + private boolean onShowFileChooser(@NonNull ShowFileChooserRequest request, @NonNull ValueCallback filePathsCallback) { + WebViewChannelDelegate.ShowFileChooserCallback callback = new WebViewChannelDelegate.ShowFileChooserCallback() { + @Override + public boolean nonNullSuccess(@NonNull ShowFileChooserResponse response) { + if (response.isHandledByClient()) { + Uri[] uriArray = null; + if (response.getFilePaths() != null) { + uriArray = new Uri[response.getFilePaths().size()]; + for (int i = 0; i < response.getFilePaths().size(); i++) { + uriArray[i] = Uri.parse(response.getFilePaths().get(i)); + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray); + } else { + ((ValueCallback) filePathsCallback).onReceiveValue(uriArray != null ? uriArray[0] : null); + } + return false; + } + return true; + } + + @Override + public void defaultBehaviour(@Nullable ShowFileChooserResponse response) { + String[] acceptTypes = request.getAcceptTypes().toArray(new String[0]); + boolean captureEnabled = request.isCaptureEnabled(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + boolean allowMultiple = request.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes, allowMultiple, captureEnabled); + } else { + startPickerIntent((ValueCallback) filePathsCallback, acceptTypes.length > 0 ? acceptTypes[0] : "", captureEnabled); + } + } + + @Override + public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { + Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : "")); + defaultBehaviour(null); + } + }; + + if (inAppWebView != null && inAppWebView.channelDelegate != null && inAppWebView.customSettings.useOnShowFileChooser) { + inAppWebView.channelDelegate.onShowFileChooser(request, callback); + } else { + callback.defaultBehaviour(null); + } + + return true; + } + protected void openFileChooser(ValueCallback filePathCallback, String acceptType) { - startPickerIntent(filePathCallback, acceptType, null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback) { - startPickerIntent(filePathCallback, "", null); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(""); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, false, null, null), filePathCallback); } protected void openFileChooser(ValueCallback filePathCallback, String acceptType, String capture) { - startPickerIntent(filePathCallback, acceptType, capture); + List acceptTypes = new ArrayList<>(); + acceptTypes.add(acceptType); + onShowFileChooser(new ShowFileChooserRequest(0, acceptTypes, true, null, null), filePathCallback); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, FileChooserParams fileChooserParams) { - String[] acceptTypes = fileChooserParams.getAcceptTypes(); - boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE; - boolean captureEnabled = fileChooserParams.isCaptureEnabled(); - return startPickerIntent(filePathCallback, acceptTypes, allowMultiple, captureEnabled); + return onShowFileChooser(ShowFileChooserRequest.fromFileChooserParams(fileChooserParams), filePathCallback); } @Override @@ -939,7 +995,7 @@ private Uri getCapturedMediaFile() { return null; } - public void startPickerIntent(ValueCallback filePathCallback, String acceptType, @Nullable String capture) { + public void startPickerIntent(ValueCallback filePathCallback, String acceptType, boolean captureEnabled) { filePathCallbackLegacy = filePathCallback; boolean images = acceptsImages(acceptType); @@ -947,12 +1003,11 @@ public void startPickerIntent(ValueCallback filePathCallback, String accept Intent pickerIntent = null; - if (capture != null) { + if (captureEnabled) { if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -995,8 +1050,7 @@ public boolean startPickerIntent(final ValueCallback callback, final Stri if (!needsCameraPermission()) { if (images) { pickerIntent = getPhotoIntent(); - } - else if (video) { + } else if (video) { pickerIntent = getVideoIntent(); } } @@ -1271,8 +1325,8 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj defaultBehaviour(null); } }; - - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequest(request.getOrigin().toString(), Arrays.asList(request.getResources()), null, callback); } else { @@ -1283,17 +1337,17 @@ public void error(String errorCode, @Nullable String errorMessage, @Nullable Obj @Override public void onRequestFocus(WebView view) { - if(inAppWebView != null && inAppWebView.channelDelegate != null) { + if (inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onRequestFocus(); } } @Override public void onPermissionRequestCanceled(PermissionRequest request) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && inAppWebView != null && inAppWebView.channelDelegate != null) { inAppWebView.channelDelegate.onPermissionRequestCanceled(request.getOrigin().toString(), - Arrays.asList(request.getResources())); + Arrays.asList(request.getResources())); } } diff --git a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java index 19e9fd868..9a157f981 100755 --- a/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java +++ b/flutter_inappwebview_android/android/src/main/java/com/pichillilorenzo/flutter_inappwebview_android/webview/in_app_webview/InAppWebViewSettings.java @@ -161,6 +161,7 @@ public class InAppWebViewSettings implements ISettings { public Boolean isUserInteractionEnabled = true; @Nullable public Double alpha; + public Boolean useOnShowFileChooser = false; @NonNull @Override @@ -479,6 +480,9 @@ public InAppWebViewSettings parse(@NonNull Map settings) { case "alpha": alpha = (Double) value; break; + case "useOnShowFileChooser": + useOnShowFileChooser = (Boolean) value; + break; } } @@ -597,6 +601,7 @@ public Map toMap() { settings.put("pluginScriptsForMainFrameOnly", pluginScriptsForMainFrameOnly); settings.put("isUserInteractionEnabled", isUserInteractionEnabled); settings.put("alpha", alpha); + settings.put("useOnShowFileChooser", useOnShowFileChooser); return settings; } diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart index 32589db62..3a057c971 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/headless_in_app_webview.dart @@ -1,8 +1,7 @@ -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; + import '../find_interaction/find_interaction_controller.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; import 'in_app_webview_controller.dart'; @@ -398,6 +397,10 @@ class AndroidHeadlessInAppWebView extends PlatformHeadlessInAppWebView settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart index a7c001b8d..a585ae398 100755 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview.dart @@ -1,16 +1,15 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; -import 'headless_in_app_webview.dart'; import '../find_interaction/find_interaction_controller.dart'; -import 'in_app_webview_controller.dart'; -import '../pull_to_refresh/main.dart'; import '../pull_to_refresh/pull_to_refresh_controller.dart'; +import 'headless_in_app_webview.dart'; +import 'in_app_webview_controller.dart'; /// Object specifying creation parameters for creating a [PlatformInAppWebViewWidget]. /// @@ -128,6 +127,7 @@ class AndroidInAppWebViewWidgetCreationParams super.onCameraCaptureStateChanged, super.onMicrophoneCaptureStateChanged, super.onContentSizeChanged, + super.onShowFileChooser, super.initialUrlRequest, super.initialFile, super.initialData, @@ -246,6 +246,7 @@ class AndroidInAppWebViewWidgetCreationParams onMicrophoneCaptureStateChanged: params.onMicrophoneCaptureStateChanged, onContentSizeChanged: params.onContentSizeChanged, + onShowFileChooser: params.onShowFileChooser, initialUrlRequest: params.initialUrlRequest, initialFile: params.initialFile, initialData: params.initialData, @@ -460,6 +461,10 @@ class AndroidInAppWebViewWidget extends PlatformInAppWebViewWidget { settings.useOnNavigationResponse == null) { settings.useOnNavigationResponse = true; } + if (params.onShowFileChooser != null && + settings.useOnShowFileChooser == null) { + settings.useOnShowFileChooser = true; + } } @override diff --git a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart index 919b7268f..38c4ca88c 100644 --- a/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/flutter_inappwebview_android/lib/src/in_app_webview/in_app_webview_controller.dart @@ -6,14 +6,12 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; - import 'package:flutter_inappwebview_platform_interface/flutter_inappwebview_platform_interface.dart'; import '../in_app_browser/in_app_browser.dart'; import '../print_job/main.dart'; import '../web_message/main.dart'; import '../web_storage/web_storage.dart'; - import '_static_channel.dart'; import 'headless_in_app_webview.dart'; @@ -1392,6 +1390,24 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController .onContentSizeChanged(oldContentSize, newContentSize); } break; + case "onShowFileChooser": + if ((webviewParams != null && + webviewParams!.onShowFileChooser != null) || + _inAppBrowserEventHandler != null) { + Map arguments = + call.arguments.cast(); + ShowFileChooserRequest request = + ShowFileChooserRequest.fromMap(arguments)!; + + if (webviewParams != null && webviewParams!.onShowFileChooser != null) + return (await webviewParams!.onShowFileChooser!( + _controllerFromPlatform, request)) + ?.toMap(); + else + return (await _inAppBrowserEventHandler!.onShowFileChooser(request)) + ?.toMap(); + } + break; case "onCallJsHandler": String handlerName = call.arguments["handlerName"]; Map handlerDataMap = @@ -1478,9 +1494,9 @@ class AndroidInAppWebViewController extends PlatformInAppWebViewController _controllerFromPlatform, request)) ?.toNativeValue()); else - return jsonEncode((await _inAppBrowserEventHandler! - .onAjaxProgress(request)) - ?.toNativeValue()); + return jsonEncode( + (await _inAppBrowserEventHandler!.onAjaxProgress(request)) + ?.toNativeValue()); } return null; case "shouldInterceptFetchRequest": diff --git a/flutter_inappwebview_platform_interface/CHANGELOG.md b/flutter_inappwebview_platform_interface/CHANGELOG.md index 4c4eb434c..d53864cad 100644 --- a/flutter_inappwebview_platform_interface/CHANGELOG.md +++ b/flutter_inappwebview_platform_interface/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.4.0-beta.3 - Added `saveState`, `restoreState` methods to `PlatformInAppWebViewController` class -- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress` properties to `InAppWebViewSettings` +- Added `useOnAjaxReadyStateChange`, `useOnAjaxProgress`, `useOnShowFileChooser` properties to `InAppWebViewSettings` ## 1.4.0-beta.2 diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart index 018bccab7..a22ae1e3b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_browser/platform_in_app_browser.dart @@ -1,30 +1,25 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview_platform_interface/src/types/disposable.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../context_menu/context_menu.dart'; +import '../debug_logging_settings.dart'; import '../find_interaction/platform_find_interaction_controller.dart'; +import '../in_app_webview/in_app_webview_settings.dart'; +import '../in_app_webview/platform_inappwebview_controller.dart'; import '../inappwebview_platform.dart'; +import '../platform_webview_feature.dart'; +import '../print_job/main.dart'; import '../pull_to_refresh/main.dart'; +import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; import '../types/main.dart'; - -import '../in_app_webview/platform_inappwebview_controller.dart'; -import '../in_app_webview/in_app_webview_settings.dart'; - -import '../print_job/main.dart'; import '../web_uri.dart'; import '../webview_environment/platform_webview_environment.dart'; import 'in_app_browser_menu_item.dart'; import 'in_app_browser_settings.dart'; -import '../debug_logging_settings.dart'; -import '../pull_to_refresh/platform_pull_to_refresh_controller.dart'; -import '../platform_webview_feature.dart'; /// Object specifying creation parameters for creating a [PlatformInAppBrowser]. /// @@ -1495,4 +1490,22 @@ abstract class PlatformInAppBrowserEvents { ///**Officially Supported Platforms/Implementations**: ///- Windows ([Official API - ICoreWebView2Controller.add_AcceleratorKeyPressed](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2controller?view=webview2-1.0.2849.39#add_acceleratorkeypressed)) void onAcceleratorKeyPressed(AcceleratorKeyPressedDetail detail) {} + + ///Tell the client to show a file chooser. + ///This is called to handle HTML forms with 'file' input type, + ///in response to the user pressing the "Select File" button. + ///To cancel the request, return a [ShowFileChooserResponse] with [ShowFileChooserResponse.filePaths] to `null`. + /// + ///Note that the WebView does not enforce any restrictions on the chosen file(s). + ///WebView can access all files that your app can access. + ///In case the file(s) are chosen through an untrusted source such as a third-party app, + ///it is your own app's responsibility to check what the returned Uris refer + ///to. + /// + ///**Officially Supported Platforms/Implementations**: + ///- Android native WebView ([Official API - WebChromeClient.onShowFileChooser](https://developer.android.com/reference/android/webkit/WebChromeClient#onShowFileChooser(android.webkit.WebView,%20android.webkit.ValueCallback%3Candroid.net.Uri[]%3E,%20android.webkit.WebChromeClient.FileChooserParams))) + FutureOr onShowFileChooser( + ShowFileChooserRequest request) { + return null; + } } diff --git a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart index 88587804c..2fa1c655b 100755 --- a/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart +++ b/flutter_inappwebview_platform_interface/lib/src/in_app_webview/in_app_webview_settings.dart @@ -2008,6 +2008,15 @@ as it can cause framerate drops on animations in Android 9 and lower (see [Hybri ]) double? alpha; + ///Set to `true` to be able to listen at the [PlatformWebViewCreationParams.onShowFileChooser] event. + /// + ///If the [PlatformWebViewCreationParams.onShowFileChooser] event is implemented and this value is `null`, + ///it will be automatically inferred as `true`, otherwise, the default value is `false`. + ///This logic will not be applied for [PlatformInAppBrowser], where you must set the value manually. + @SupportedPlatforms( + platforms: [AndroidPlatform()]) + bool? useOnShowFileChooser; + ///Specifies a feature policy for the `