added Find Interaction Controller

This commit is contained in:
Lorenzo Pichilli 2022-10-08 14:19:35 +02:00
parent 06ef336c28
commit f5a048cb69
44 changed files with 1630 additions and 318 deletions

View File

@ -5,8 +5,9 @@
- Added `ProxyController` for Android
- Added `PrintJobController` to manage print jobs
- Added `WebAuthenticationSession` for iOS
- Added `FindInteractionController` for Android and iOS
- Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods
- Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy`, `willSuppressErrorPage`, `algorithmicDarkeningAllowed`, `requestedWithHeaderMode`, `enterpriseAuthenticationAppLinkPolicyEnabled` WebView settings
- Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy`, `willSuppressErrorPage`, `algorithmicDarkeningAllowed`, `requestedWithHeaderMode`, `enterpriseAuthenticationAppLinkPolicyEnabled`, `isElementFullscreenEnabled`, `isFindInteractionEnabled` WebView settings
- Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events
- Added support for `onPermissionRequest` event on iOS 15.0+
- Added `debugLoggingSettings` static property for WebView and ChromeSafariBrowser

View File

@ -0,0 +1,66 @@
package com.pichillilorenzo.flutter_inappwebview.find_interaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.types.ChannelDelegateImpl;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class FindInteractionChannelDelegate extends ChannelDelegateImpl {
@Nullable
private FindInteractionController findInteractionController;
public FindInteractionChannelDelegate(@NonNull FindInteractionController findInteractionController, @NonNull MethodChannel channel) {
super(channel);
this.findInteractionController = findInteractionController;
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "findAllAsync":
if (findInteractionController != null && findInteractionController.webView != null) {
String find = (String) call.argument("find");
findInteractionController.webView.findAllAsync(find);
}
result.success(true);
break;
case "findNext":
if (findInteractionController != null && findInteractionController.webView != null) {
Boolean forward = (Boolean) call.argument("forward");
findInteractionController.webView.findNext(forward);
}
result.success(true);
break;
case "clearMatches":
if (findInteractionController != null && findInteractionController.webView != null) {
findInteractionController.webView.clearMatches();
}
result.success(true);
break;
default:
result.notImplemented();
}
}
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
MethodChannel channel = getChannel();
if (channel == null) return;
Map<String, Object> obj = new HashMap<>();
obj.put("activeMatchOrdinal", activeMatchOrdinal);
obj.put("numberOfMatches", numberOfMatches);
obj.put("isDoneCounting", isDoneCounting);
channel.invokeMethod("onFindResultReceived", obj);
}
@Override
public void dispose() {
super.dispose();
findInteractionController = null;
}
}

View File

@ -0,0 +1,42 @@
package com.pichillilorenzo.flutter_inappwebview.find_interaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.webview.InAppWebViewInterface;
import io.flutter.plugin.common.MethodChannel;
public class FindInteractionController implements Disposable {
static final String LOG_TAG = "FindInteractionController";
public static final String METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
@Nullable
public InAppWebViewInterface webView;
@Nullable
public FindInteractionChannelDelegate channelDelegate;
@Nullable
public FindInteractionSettings settings;
public FindInteractionController(@NonNull InAppWebViewInterface webView, @NonNull InAppWebViewFlutterPlugin plugin,
@NonNull Object id, @Nullable FindInteractionSettings settings) {
this.webView = webView;
this.settings = settings;
final MethodChannel channel = new MethodChannel(plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
this.channelDelegate = new FindInteractionChannelDelegate(this, channel);
}
public void prepare() {
}
public void dispose() {
if (channelDelegate != null) {
channelDelegate.dispose();
channelDelegate = null;
}
webView = null;
}
}

View File

@ -0,0 +1,46 @@
package com.pichillilorenzo.flutter_inappwebview.find_interaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.ISettings;
import java.util.HashMap;
import java.util.Map;
public class FindInteractionSettings implements ISettings<FindInteractionController> {
public static final String LOG_TAG = "FindInteractionSettings";
@NonNull
@Override
public FindInteractionSettings parse(@NonNull Map<String, Object> settings) {
// for (Map.Entry<String, Object> pair : settings.entrySet()) {
// String key = pair.getKey();
// Object value = pair.getValue();
// if (value == null) {
// continue;
// }
//
// switch (key) {
//
// }
// }
return this;
}
@NonNull
public Map<String, Object> toMap() {
Map<String, Object> settings = new HashMap<>();
return settings;
}
@NonNull
@Override
public Map<String, Object> getRealSettings(@NonNull FindInteractionController findInteractionController) {
Map<String, Object> realSettings = toMap();
return realSettings;
}
}

View File

@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import com.pichillilorenzo.flutter_inappwebview.find_interaction.FindInteractionController;
import com.pichillilorenzo.flutter_inappwebview.types.Disposable;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Util;
@ -109,6 +110,10 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
webView.inAppBrowserDelegate = this;
webView.plugin = manager.plugin;
FindInteractionController findInteractionController = new FindInteractionController(webView, manager.plugin, id, null);
webView.findInteractionController = findInteractionController;
findInteractionController.prepare();
final MethodChannel channel = new MethodChannel(manager.plugin.messenger, METHOD_CHANNEL_NAME_PREFIX + id);
channelDelegate = new InAppBrowserChannelDelegate(channel);
webView.channelDelegate = new WebViewChannelDelegate(webView, channel);

View File

@ -13,6 +13,7 @@ import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.Util;
import com.pichillilorenzo.flutter_inappwebview.find_interaction.FindInteractionChannelDelegate;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.in_app_browser.InAppBrowserSettings;
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings;
@ -73,24 +74,31 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "getUrl":
WebViewChannelDelegateMethods method = null;
try {
method = WebViewChannelDelegateMethods.valueOf(call.method);
} catch (IllegalArgumentException e) {
result.notImplemented();
return;
}
switch (method) {
case getUrl:
result.success((webView != null) ? webView.getUrl() : null);
break;
case "getTitle":
case getTitle:
result.success((webView != null) ? webView.getTitle() : null);
break;
case "getProgress":
case getProgress:
result.success((webView != null) ? webView.getProgress() : null);
break;
case "loadUrl":
case loadUrl:
if (webView != null) {
Map<String, Object> urlRequest = (Map<String, Object>) call.argument("urlRequest");
webView.loadUrl(URLRequest.fromMap(urlRequest));
}
result.success(true);
break;
case "postUrl":
case postUrl:
if (webView != null) {
String url = (String) call.argument("url");
byte[] postData = (byte[]) call.argument("postData");
@ -98,7 +106,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "loadData":
case loadData:
if (webView != null) {
String data = (String) call.argument("data");
String mimeType = (String) call.argument("mimeType");
@ -109,7 +117,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "loadFile":
case loadFile:
if (webView != null) {
String assetFilePath = (String) call.argument("assetFilePath");
try {
@ -122,7 +130,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "evaluateJavascript":
case evaluateJavascript:
if (webView != null) {
String source = (String) call.argument("source");
Map<String, Object> contentWorldMap = (Map<String, Object>) call.argument("contentWorld");
@ -138,7 +146,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "injectJavascriptFileFromUrl":
case injectJavascriptFileFromUrl:
if (webView != null) {
String urlFile = (String) call.argument("urlFile");
Map<String, Object> scriptHtmlTagAttributes = (Map<String, Object>) call.argument("scriptHtmlTagAttributes");
@ -146,14 +154,14 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "injectCSSCode":
case injectCSSCode:
if (webView != null) {
String source = (String) call.argument("source");
webView.injectCSSCode(source);
}
result.success(true);
break;
case "injectCSSFileFromUrl":
case injectCSSFileFromUrl:
if (webView != null) {
String urlFile = (String) call.argument("urlFile");
Map<String, Object> cssLinkHtmlTagAttributes = (Map<String, Object>) call.argument("cssLinkHtmlTagAttributes");
@ -161,44 +169,44 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "reload":
case reload:
if (webView != null)
webView.reload();
result.success(true);
break;
case "goBack":
case goBack:
if (webView != null)
webView.goBack();
result.success(true);
break;
case "canGoBack":
case canGoBack:
result.success((webView != null) && webView.canGoBack());
break;
case "goForward":
case goForward:
if (webView != null)
webView.goForward();
result.success(true);
break;
case "canGoForward":
case canGoForward:
result.success((webView != null) && webView.canGoForward());
break;
case "goBackOrForward":
case goBackOrForward:
if (webView != null)
webView.goBackOrForward((Integer) call.argument("steps"));
result.success(true);
break;
case "canGoBackOrForward":
case canGoBackOrForward:
result.success((webView != null) && webView.canGoBackOrForward((Integer) call.argument("steps")));
break;
case "stopLoading":
case stopLoading:
if (webView != null)
webView.stopLoading();
result.success(true);
break;
case "isLoading":
case isLoading:
result.success((webView != null) && webView.isLoading());
break;
case "takeScreenshot":
case takeScreenshot:
if (webView != null) {
Map<String, Object> screenshotConfiguration = (Map<String, Object>) call.argument("screenshotConfiguration");
webView.takeScreenshot(screenshotConfiguration, result);
@ -206,7 +214,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
else
result.success(null);
break;
case "setSettings":
case setSettings:
if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) {
InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate();
InAppBrowserSettings inAppBrowserSettings = new InAppBrowserSettings();
@ -221,7 +229,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "getSettings":
case getSettings:
if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) {
InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate();
result.success(inAppBrowserActivity.getCustomSettings());
@ -229,7 +237,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success((webView != null) ? webView.getCustomSettings() : null);
}
break;
case "close":
case close:
if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) {
InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate();
inAppBrowserActivity.close(result);
@ -237,7 +245,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.notImplemented();
}
break;
case "show":
case show:
if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) {
InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate();
inAppBrowserActivity.show();
@ -246,7 +254,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.notImplemented();
}
break;
case "hide":
case hide:
if (webView != null && webView.getInAppBrowserDelegate() instanceof InAppBrowserActivity) {
InAppBrowserActivity inAppBrowserActivity = (InAppBrowserActivity) webView.getInAppBrowserDelegate();
inAppBrowserActivity.hide();
@ -255,10 +263,10 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.notImplemented();
}
break;
case "getCopyBackForwardList":
case getCopyBackForwardList:
result.success((webView != null) ? webView.getCopyBackForwardList() : null);
break;
case "startSafeBrowsing":
case startSafeBrowsing:
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.START_SAFE_BROWSING)) {
WebViewCompat.startSafeBrowsing(webView.getContext(), new ValueCallback<Boolean>() {
@Override
@ -271,37 +279,37 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "clearCache":
case clearCache:
if (webView != null)
webView.clearAllCache();
result.success(true);
break;
case "clearSslPreferences":
case clearSslPreferences:
if (webView != null)
webView.clearSslPreferences();
result.success(true);
break;
case "findAllAsync":
case findAllAsync:
if (webView != null) {
String find = (String) call.argument("find");
webView.findAllAsync(find);
}
result.success(true);
break;
case "findNext":
case findNext:
if (webView != null) {
Boolean forward = (Boolean) call.argument("forward");
webView.findNext(forward);
}
result.success(true);
break;
case "clearMatches":
case clearMatches:
if (webView != null) {
webView.clearMatches();
}
result.success(true);
break;
case "scrollTo":
case scrollTo:
if (webView != null) {
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
@ -310,7 +318,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "scrollBy":
case scrollBy:
if (webView != null) {
Integer x = (Integer) call.argument("x");
Integer y = (Integer) call.argument("y");
@ -319,31 +327,31 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
}
result.success(true);
break;
case "pause":
case pause:
if (webView != null) {
webView.onPause();
}
result.success(true);
break;
case "resume":
case resume:
if (webView != null) {
webView.onResume();
}
result.success(true);
break;
case "pauseTimers":
case pauseTimers:
if (webView != null) {
webView.pauseTimers();
}
result.success(true);
break;
case "resumeTimers":
case resumeTimers:
if (webView != null) {
webView.resumeTimers();
}
result.success(true);
break;
case "printCurrentPage":
case printCurrentPage:
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
PrintJobSettings settings = new PrintJobSettings();
Map<String, Object> settingsMap = (Map<String, Object>) call.argument("settings");
@ -355,31 +363,31 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "getContentHeight":
case getContentHeight:
if (webView instanceof InAppWebView) {
result.success(webView.getContentHeight());
} else {
result.success(null);
}
break;
case "zoomBy":
case zoomBy:
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
double zoomFactor = (double) call.argument("zoomFactor");
webView.zoomBy((float) zoomFactor);
}
result.success(true);
break;
case "getOriginalUrl":
case getOriginalUrl:
result.success((webView != null) ? webView.getOriginalUrl() : null);
break;
case "getZoomScale":
case getZoomScale:
if (webView instanceof InAppWebView) {
result.success(webView.getZoomScale());
} else {
result.success(null);
}
break;
case "getSelectedText":
case getSelectedText:
if ((webView instanceof InAppWebView && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) {
webView.getSelectedText(new ValueCallback<String>() {
@Override
@ -391,14 +399,14 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "getHitTestResult":
case getHitTestResult:
if (webView instanceof InAppWebView) {
result.success(HitTestResult.fromWebViewHitTestResult(webView.getHitTestResult()).toMap());
} else {
result.success(null);
}
break;
case "pageDown":
case pageDown:
if (webView != null) {
boolean bottom = (boolean) call.argument("bottom");
result.success(webView.pageDown(bottom));
@ -406,7 +414,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "pageUp":
case pageUp:
if (webView != null) {
boolean top = (boolean) call.argument("top");
result.success(webView.pageUp(top));
@ -414,7 +422,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "saveWebArchive":
case saveWebArchive:
if (webView != null) {
String filePath = (String) call.argument("filePath");
boolean autoname = (boolean) call.argument("autoname");
@ -428,75 +436,75 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "zoomIn":
case zoomIn:
if (webView != null) {
result.success(webView.zoomIn());
} else {
result.success(false);
}
break;
case "zoomOut":
case zoomOut:
if (webView != null) {
result.success(webView.zoomOut());
} else {
result.success(false);
}
break;
case "clearFocus":
case clearFocus:
if (webView != null) {
webView.clearFocus();
}
result.success(true);
break;
case "setContextMenu":
case setContextMenu:
if (webView != null) {
Map<String, Object> contextMenu = (Map<String, Object>) call.argument("contextMenu");
webView.setContextMenu(contextMenu);
}
result.success(true);
break;
case "requestFocusNodeHref":
case requestFocusNodeHref:
if (webView != null) {
result.success(webView.requestFocusNodeHref());
} else {
result.success(null);
}
break;
case "requestImageRef":
case requestImageRef:
if (webView != null) {
result.success(webView.requestImageRef());
} else {
result.success(null);
}
break;
case "getScrollX":
case getScrollX:
if (webView != null) {
result.success(webView.getScrollX());
} else {
result.success(null);
}
break;
case "getScrollY":
case getScrollY:
if (webView != null) {
result.success(webView.getScrollY());
} else {
result.success(null);
}
break;
case "getCertificate":
case getCertificate:
if (webView != null) {
result.success(SslCertificateExt.toMap(webView.getCertificate()));
} else {
result.success(null);
}
break;
case "clearHistory":
case clearHistory:
if (webView != null) {
webView.clearHistory();
}
result.success(true);
break;
case "addUserScript":
case addUserScript:
if (webView != null && webView.getUserContentController() != null) {
Map<String, Object> userScriptMap = (Map<String, Object>) call.argument("userScript");
UserScript userScript = UserScript.fromMap(userScriptMap);
@ -505,7 +513,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "removeUserScript":
case removeUserScript:
if (webView != null && webView.getUserContentController() != null) {
Integer index = (Integer) call.argument("index");
Map<String, Object> userScriptMap = (Map<String, Object>) call.argument("userScript");
@ -515,20 +523,20 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "removeUserScriptsByGroupName":
case removeUserScriptsByGroupName:
if (webView != null && webView.getUserContentController() != null) {
String groupName = (String) call.argument("groupName");
webView.getUserContentController().removeUserOnlyScriptsByGroupName(groupName);
}
result.success(true);
break;
case "removeAllUserScripts":
case removeAllUserScripts:
if (webView != null && webView.getUserContentController() != null) {
webView.getUserContentController().removeAllUserOnlyScripts();
}
result.success(true);
break;
case "callAsyncJavaScript":
case callAsyncJavaScript:
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String functionBody = (String) call.argument("functionBody");
Map<String, Object> functionArguments = (Map<String, Object>) call.argument("arguments");
@ -545,7 +553,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "isSecureContext":
case isSecureContext:
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.isSecureContext(new ValueCallback<Boolean>() {
@Override
@ -557,7 +565,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(false);
}
break;
case "createWebMessageChannel":
case createWebMessageChannel:
if (webView != null) {
if (webView instanceof InAppWebView && WebViewFeature.isFeatureSupported(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL)) {
result.success(webView.createCompatWebMessageChannel().toMap());
@ -568,7 +576,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(null);
}
break;
case "postWebMessage":
case postWebMessage:
if (webView != null && WebViewFeature.isFeatureSupported(WebViewFeature.POST_WEB_MESSAGE)) {
Map<String, Object> message = (Map<String, Object>) call.argument("message");
String targetOrigin = (String) call.argument("targetOrigin");
@ -600,7 +608,7 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(true);
}
break;
case "addWebMessageListener":
case addWebMessageListener:
if (webView != null) {
Map<String, Object> webMessageListenerMap = (Map<String, Object>) call.argument("webMessageListener");
WebMessageListener webMessageListener = WebMessageListener.fromMap(webView, webView.getPlugin().messenger, webMessageListenerMap);
@ -618,32 +626,35 @@ public class WebViewChannelDelegate extends ChannelDelegateImpl {
result.success(true);
}
break;
case "canScrollVertically":
case canScrollVertically:
if (webView != null) {
result.success(webView.canScrollVertically());
} else {
result.success(false);
}
break;
case "canScrollHorizontally":
case canScrollHorizontally:
if (webView != null) {
result.success(webView.canScrollHorizontally());
} else {
result.success(false);
}
break;
case "isInFullscreen":
case isInFullscreen:
if (webView != null) {
result.success(webView.isInFullscreen());
} else {
result.success(false);
}
break;
default:
result.notImplemented();
}
}
/**
* @deprecated
* Use {@link FindInteractionChannelDelegate#onFindResultReceived} instead.
*/
@Deprecated
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
MethodChannel channel = getChannel();
if (channel == null) return;

View File

@ -0,0 +1,75 @@
package com.pichillilorenzo.flutter_inappwebview.webview;
public enum WebViewChannelDelegateMethods {
getUrl,
getTitle,
getProgress,
loadUrl,
postUrl,
loadData,
loadFile,
evaluateJavascript,
injectJavascriptFileFromUrl,
injectCSSCode,
injectCSSFileFromUrl,
reload,
goBack,
canGoBack,
goForward,
canGoForward,
goBackOrForward,
canGoBackOrForward,
stopLoading,
isLoading,
takeScreenshot,
setSettings,
getSettings,
close,
show,
hide,
getCopyBackForwardList,
startSafeBrowsing,
clearCache,
clearSslPreferences,
findAllAsync,
findNext,
clearMatches,
scrollTo,
scrollBy,
pause,
resume,
pauseTimers,
resumeTimers,
printCurrentPage,
getContentHeight,
zoomBy,
getOriginalUrl,
getZoomScale,
getSelectedText,
getHitTestResult,
pageDown,
pageUp,
saveWebArchive,
zoomIn,
zoomOut,
clearFocus,
setContextMenu,
requestFocusNodeHref,
requestImageRef,
getScrollX,
getScrollY,
getCertificate,
clearHistory,
addUserScript,
removeUserScript,
removeUserScriptsByGroupName,
removeAllUserScripts,
callAsyncJavaScript,
isSecureContext,
createWebMessageChannel,
postWebMessage,
addWebMessageListener,
canScrollVertically,
canScrollHorizontally,
isInFullscreen
}

View File

@ -19,6 +19,7 @@ import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.find_interaction.FindInteractionController;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshSettings;
@ -84,6 +85,10 @@ public class FlutterWebView implements PlatformWebView {
pullToRefreshLayout.prepare();
}
FindInteractionController findInteractionController = new FindInteractionController(webView, plugin, id, null);
webView.findInteractionController = findInteractionController;
findInteractionController.prepare();
webView.prepare();
}

View File

@ -56,6 +56,7 @@ import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewFlutterPlugin;
import com.pichillilorenzo.flutter_inappwebview.find_interaction.FindInteractionController;
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobController;
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobManager;
import com.pichillilorenzo.flutter_inappwebview.print_job.PrintJobSettings;
@ -167,6 +168,9 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
private List<UserScript> initialUserOnlyScript = new ArrayList<>();
@Nullable
public FindInteractionController findInteractionController;
public InAppWebView(Context context) {
super(context);
}
@ -406,7 +410,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
setFindListener(new FindListener() {
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
if (channelDelegate != null) channelDelegate.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
if (findInteractionController != null && findInteractionController.channelDelegate != null)
findInteractionController.channelDelegate.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
if (channelDelegate != null)
channelDelegate.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting);
}
});
@ -1929,6 +1936,10 @@ final public class InAppWebView extends InputAwareWebView implements InAppWebVie
@Override
public void dispose() {
userContentController.dispose();
if (findInteractionController != null) {
findInteractionController.dispose();
findInteractionController = null;
}
if (windowId != null) {
InAppWebViewChromeClient.windowWebViewMessages.remove(windowId);
}

View File

@ -0,0 +1,118 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
void findInteractions() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
TargetPlatform.iOS,
TargetPlatform.macOS,
].contains(defaultTargetPlatform);
testWidgets('find interactions', (WidgetTester tester) async {
final Completer controllerCompleter = Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
final findInteractionController = FindInteractionController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialFile: "test_assets/in_app_webview_initial_file_test.html",
findInteractionController: findInteractionController,
initialSettings: InAppWebViewSettings(
clearCache: true,
isFindInteractionEnabled: true
),
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
onLoadStop: (controller, url) {
pageLoaded.complete();
},
),
),
);
await pageLoaded.future;
await tester.pump();
await Future.delayed(Duration(seconds: 1));
const firstSearchText = "InAppWebViewInitialFileTest";
await expectLater(findInteractionController.findAllAsync(find: firstSearchText), completes);
if ([TargetPlatform.iOS, TargetPlatform.macOS].contains(defaultTargetPlatform)) {
expect(await findInteractionController.getSearchText(), firstSearchText);
final session = await findInteractionController.getActiveFindSession();
expect(session!.resultCount, 2);
}
await expectLater(findInteractionController.findNext(forward: true), completes);
await expectLater(findInteractionController.findNext(forward: false), completes);
await expectLater(findInteractionController.clearMatches(), completes);
if ([TargetPlatform.iOS, TargetPlatform.macOS].contains(defaultTargetPlatform)) {
const secondSearchText = "text";
await expectLater(
findInteractionController.setSearchText(secondSearchText), completes);
await expectLater(
findInteractionController.presentFindNavigator(), completes);
expect(await findInteractionController.getSearchText(), secondSearchText);
expect(await findInteractionController.isFindNavigatorVisible(), true);
await expectLater(findInteractionController.updateResultCount(), completes);
await expectLater(
findInteractionController.dismissFindNavigator(), completes);
expect(await findInteractionController.isFindNavigatorVisible(), false);
}
}, skip: shouldSkip);
testWidgets('onFindResultReceived', (WidgetTester tester) async {
final Completer controllerCompleter = Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
final Completer<int> numberOfMatchesCompleter = Completer<int>();
final findInteractionController = FindInteractionController(
onFindResultReceived: (controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) async {
if (isDoneCounting && !numberOfMatchesCompleter.isCompleted) {
numberOfMatchesCompleter.complete(numberOfMatches);
}
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialFile: "test_assets/in_app_webview_initial_file_test.html",
initialSettings: InAppWebViewSettings(
clearCache: true,
isFindInteractionEnabled: false
),
findInteractionController: findInteractionController,
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
onLoadStop: (controller, url) {
pageLoaded.complete();
},
),
),
);
var controller = await controllerCompleter.future;
await pageLoaded.future;
await tester.pump();
await Future.delayed(Duration(seconds: 1));
await controller.findAllAsync(find: "InAppWebViewInitialFileTest");
final int numberOfMatches = await numberOfMatchesCompleter.future;
expect(numberOfMatches, 2);
}, skip: shouldSkip);
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'find_interactions.dart';
void main() {
final shouldSkip = kIsWeb;
group('FindInteractionController', () {
findInteractions();
}, skip: shouldSkip);
}

View File

@ -37,7 +37,6 @@ import 'load_file_url.dart';
import 'load_url.dart';
import 'on_console_message.dart';
import 'on_download_start_request.dart';
import 'on_find_result_received.dart';
import 'on_js_before_unload.dart';
import 'on_received_error.dart';
import 'on_received_http_error.dart';
@ -106,7 +105,6 @@ void main() {
contentBlocker();
httpAuthCredentialDatabase();
onConsoleMessage();
onFindResultReceived();
onDownloadStartRequest();
javascriptDialogs();
onReceivedHttpError();

View File

@ -1,56 +0,0 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_test/flutter_test.dart';
void onFindResultReceived() {
final shouldSkip = kIsWeb
? true
: ![
TargetPlatform.android,
TargetPlatform.iOS,
TargetPlatform.macOS,
].contains(defaultTargetPlatform);
testWidgets('onFindResultReceived', (WidgetTester tester) async {
final Completer controllerCompleter = Completer<InAppWebViewController>();
final Completer<void> pageLoaded = Completer<void>();
final Completer<int> numberOfMatchesCompleter = Completer<int>();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialFile: "test_assets/in_app_webview_initial_file_test.html",
initialSettings: InAppWebViewSettings(
clearCache: true,
),
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
onLoadStop: (controller, url) {
pageLoaded.complete();
},
onFindResultReceived: (controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting) async {
if (isDoneCounting && !numberOfMatchesCompleter.isCompleted) {
numberOfMatchesCompleter.complete(numberOfMatches);
}
},
),
),
);
var controller = await controllerCompleter.future;
await pageLoaded.future;
await tester.pump();
await Future.delayed(Duration(seconds: 1));
await controller.findAllAsync(find: "InAppWebViewInitialFileTest");
final int numberOfMatches = await numberOfMatchesCompleter.future;
expect(numberOfMatches, 2);
}, skip: shouldSkip);
}

View File

@ -3,6 +3,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:integration_test/integration_test.dart';
import 'in_app_webview/main.dart' as in_app_webview_tests;
import 'find_interaction_controller/main.dart' as find_interaction_controller_tests;
import 'service_worker_controller/main.dart' as service_worker_controller_tests;
import 'proxy_controller/main.dart' as proxy_controller_tests;
import 'headless_in_app_webview/main.dart' as headless_in_app_webview_tests;
@ -26,8 +27,13 @@ void main() {
ChromeSafariBrowser.debugLoggingSettings.maxLogMessageLength = 7000;
WebAuthenticationSession.debugLoggingSettings.usePrint = true;
WebAuthenticationSession.debugLoggingSettings.maxLogMessageLength = 7000;
PullToRefreshController.debugLoggingSettings.usePrint = true;
PullToRefreshController.debugLoggingSettings.maxLogMessageLength = 7000;
FindInteractionController.debugLoggingSettings.usePrint = true;
FindInteractionController.debugLoggingSettings.maxLogMessageLength = 7000;
in_app_webview_tests.main();
find_interaction_controller_tests.main();
service_worker_controller_tests.main();
proxy_controller_tests.main();
headless_in_app_webview_tests.main();

View File

@ -19,12 +19,15 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
InAppWebViewSettings settings = InAppWebViewSettings(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
isFindInteractionEnabled: false,
allowsInlineMediaPlayback: true,
iframeAllow: "camera; microphone",
iframeAllowFullscreen: true
);
PullToRefreshController? pullToRefreshController;
FindInteractionController? findInteractionController;
late ContextMenu contextMenu;
String url = "";
double progress = 0;
@ -79,6 +82,14 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
}
},
);
findInteractionController = kIsWeb
? null
: FindInteractionController(
onFindResultReceived: (controller, activeMatchOrdinal, numberOfMatches, isDoneCounting) => {
print("$activeMatchOrdinal $numberOfMatches $isDoneCounting")
},
);
}
@override
@ -114,7 +125,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
InAppWebView(
key: webViewKey,
initialUrlRequest:
URLRequest(url: Uri.parse('https://github.com/flutter/')),
URLRequest(url: Uri.parse('https://developer.apple.com/videos/play/wwdc2022/10049/?time=264')),
// initialUrlRequest:
// URLRequest(url: Uri.parse(Uri.base.toString().replaceFirst("/#/", "/") + 'page.html')),
// initialFile: "assets/index.html",
@ -122,6 +133,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
initialSettings: settings,
// contextMenu: contextMenu,
pullToRefreshController: pullToRefreshController,
findInteractionController: findInteractionController,
onWebViewCreated: (controller) async {
webViewController = controller;
},
@ -167,6 +179,32 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
this.url = url.toString();
urlController.text = this.url;
});
await findInteractionController?.findAllAsync(find: "video");
// print(await findInteractionController?.getActiveFindSession());
await Future.delayed(Duration(seconds: 1));
findInteractionController?.findNext(forward: true);
findInteractionController?.findNext(forward: true);
findInteractionController?.findNext(forward: true);
await Future.delayed(Duration(seconds: 1));
// findInteractionController?.clearMatches();
findInteractionController?.findNext(forward: true);
findInteractionController?.findNext(forward: true);
findInteractionController?.findNext(forward: true);
findInteractionController?.findNext(forward: true);
await Future.delayed(Duration(seconds: 1));
findInteractionController?.clearMatches();
// print(await findInteractionController?.getSearchText());
// findInteractionController?.findNext(forward: true);
// findInteractionController?.findNext(forward: false);
// findInteractionController?.setSearchText("text");
// print(await findInteractionController?.getSearchText());
// print(await findInteractionController?.isFindNavigatorVisible());
// findInteractionController?.updateResultCount();
// findInteractionController?.clearMatches();
// findInteractionController?.presentFindNavigator();
// await Future.delayed(Duration(milliseconds: 500));
// findInteractionController?.dismissFindNavigator();
// print(await findInteractionController?.isFindNavigatorVisible());
},
onReceivedError: (controller, request, error) {
pullToRefreshController?.endRefreshing();

View File

@ -0,0 +1,168 @@
//
// FindInteractionChannelDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindInteractionChannelDelegate : ChannelDelegate {
private weak var findInteractionController: FindInteractionController?
public init(findInteractionController: FindInteractionController, channel: FlutterMethodChannel) {
super.init(channel: channel)
self.findInteractionController = findInteractionController
}
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "findAllAsync":
if let findInteractionController = findInteractionController {
let find = arguments!["find"] as! String
findInteractionController.findAllAsync(find: find, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "findNext":
if let findInteractionController = findInteractionController {
let forward = arguments!["forward"] as! Bool
findInteractionController.findNext(forward: forward, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "clearMatches":
if let findInteractionController = findInteractionController {
findInteractionController.clearMatches(completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "FindInteractionChannelDelegate", message: error?.localizedDescription, details: nil))
return
}
result(true)
})
} else {
result(false)
}
break
case "setSearchText":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
let searchText = arguments!["searchText"] as? String
interaction.searchText = searchText
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "getSearchText":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
result(interaction.searchText)
} else {
result(nil)
}
} else {
result(nil)
}
break
case "isFindNavigatorVisible":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
result(interaction.isFindNavigatorVisible)
} else {
result(false)
}
} else {
result(false)
}
break
case "updateResultCount":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.updateResultCount()
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "presentFindNavigator":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.presentFindNavigator(showingReplace: false)
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "dismissFindNavigator":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
interaction.dismissFindNavigator()
result(true)
} else {
result(false)
}
} else {
result(false)
}
break
case "getActiveFindSession":
if #available(iOS 16.0, *) {
if let interaction = findInteractionController?.webView?.findInteraction {
result(interaction.activeFindSession?.toMap())
} else {
result(nil)
}
} else {
result(nil)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
let arguments: [String : Any?] = [
"activeMatchOrdinal": activeMatchOrdinal,
"numberOfMatches": numberOfMatches,
"isDoneCounting": isDoneCounting
]
channel?.invokeMethod("onFindResultReceived", arguments: arguments)
}
public override func dispose() {
super.dispose()
findInteractionController = nil
}
deinit {
dispose()
}
}

View File

@ -0,0 +1,108 @@
//
// FindInteractionController.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
import Flutter
public class FindInteractionController : NSObject, Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_find_interaction_";
var webView: InAppWebView?
var channelDelegate: FindInteractionChannelDelegate?
var settings: FindInteractionSettings?
var shouldCallOnRefresh = false
public init(registrar: FlutterPluginRegistrar, id: Any, webView: InAppWebView, settings: FindInteractionSettings?) {
super.init()
self.webView = webView
self.settings = settings
let channel = FlutterMethodChannel(name: FindInteractionController.METHOD_CHANNEL_NAME_PREFIX + String(describing: id),
binaryMessenger: registrar.messenger())
self.channelDelegate = FindInteractionChannelDelegate(findInteractionController: self, channel: channel)
}
public func prepare() {
// if let settings = settings {
//
// }
}
public func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
if #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
interaction.searchText = find
interaction.presentFindNavigator(showingReplace: false)
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else {
let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find ?? "")');"
webView.evaluateJavaScript(startSearch, completionHandler: completionHandler)
}
}
public func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
if #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
if forward {
interaction.findNext()
} else {
interaction.findPrevious()
}
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else {
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
}
}
public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
guard let webView else {
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
return
}
if #available(iOS 16.0, *), webView.isFindInteractionEnabled {
if let interaction = webView.findInteraction {
interaction.searchText = nil
interaction.dismissFindNavigator()
}
if let completionHandler = completionHandler {
completionHandler(nil, nil)
}
} else {
webView.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler)
}
}
public func dispose() {
channelDelegate?.dispose()
channelDelegate = nil
webView = nil
}
deinit {
debugPrint("FindInteractionControl - dealloc")
dispose()
}
}

View File

@ -0,0 +1,25 @@
//
// FindInteractionSettings.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
public class FindInteractionSettings : ISettings<FindInteractionController> {
override init(){
super.init()
}
override func parse(settings: [String: Any?]) -> FindInteractionSettings {
let _ = super.parse(settings: settings)
return self
}
override func getRealSettings(obj: FindInteractionController?) -> [String: Any?] {
let realSettings: [String: Any?] = toMap()
return realSettings
}
}

View File

@ -77,6 +77,12 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
pullToRefreshControl.delegate = webView
pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController(
registrar: SwiftFlutterPlugin.instance!.registrar!,
id: id, webView: webView, settings: nil)
webView.findInteractionController = findInteractionController
findInteractionController.prepare()
prepareWebView()
webView.windowCreated = true

View File

@ -61,6 +61,12 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView, Disposable
webView!.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView!
pullToRefreshControl.prepare()
let findInteractionController = FindInteractionController(
registrar: SwiftFlutterPlugin.instance!.registrar!,
id: viewId, webView: webView!, settings: nil)
webView!.findInteractionController = findInteractionController
findInteractionController.prepare()
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.autoresizesSubviews = true

View File

@ -12,7 +12,8 @@ import WebKit
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate,
WKDownloadDelegate,
PullToRefreshDelegate, Disposable {
PullToRefreshDelegate,
Disposable {
static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappwebview_"
var id: Any? // viewId
@ -22,6 +23,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
var channelDelegate: WebViewChannelDelegate?
var settings: InAppWebViewSettings?
var pullToRefreshControl: PullToRefreshControl?
var findInteractionController: FindInteractionController?
var webMessageChannels: [String:WebMessageChannel] = [:]
var webMessageListeners: [WebMessageListener] = []
var currentOriginalUrl: URL?
@ -329,7 +331,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
name: UIMenuController.didHideMenuNotification,
object: nil)
// if #available(iOS 15.0, *) {
// TODO: Still not working on iOS 16.0!
// if #available(iOS 16.0, *) {
// addObserver(self,
// forKeyPath: #keyPath(WKWebView.fullscreenState),
// options: .new,
@ -413,6 +416,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
}
if #available(iOS 16.0, *) {
isFindInteractionEnabled = settings.isFindInteractionEnabled
}
// debugging is always enabled for iOS,
// there isn't any option to set about it such as on Android.
@ -456,6 +463,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
if #available(iOS 15.4, *) {
configuration.preferences.isSiteSpecificQuirksModeEnabled = settings.isSiteSpecificQuirksModeEnabled
configuration.preferences.isElementFullscreenEnabled = settings.isElementFullscreenEnabled
}
}
}
@ -669,7 +677,9 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
}
}
// else if keyPath == #keyPath(WKWebView.fullscreenState) {
} else if #available(iOS 16.0, *) {
// TODO: Still not working on iOS 16.0!
// if keyPath == #keyPath(WKWebView.fullscreenState) {
// if fullscreenState == .enteringFullscreen {
// channelDelegate?.onEnterFullscreen()
// } else if fullscreenState == .exitingFullscreen {
@ -2513,6 +2523,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
@objc func onEnterFullscreen(_ notification: Notification) {
// TODO: Still not working on iOS 16.0!
// if #available(iOS 16.0, *) {
// channelDelegate?.onEnterFullscreen()
// inFullscreen = true
// }
// else
if (isVideoPlayerWindow(notification.object as AnyObject?)) {
channelDelegate?.onEnterFullscreen()
inFullscreen = true
@ -2520,6 +2536,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate,
}
@objc func onExitFullscreen(_ notification: Notification) {
// TODO: Still not working on iOS 16.0!
// if #available(iOS 16.0, *) {
// channelDelegate?.onExitFullscreen()
// inFullscreen = false
// }
// else
if (isVideoPlayerWindow(notification.object as AnyObject?)) {
channelDelegate?.onExitFullscreen()
inFullscreen = false
@ -2648,6 +2670,7 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if let wId = _windowId, let webViewTransport = InAppWebView.windowWebViews[wId] {
webView = webViewTransport.webView
}
webView.findInteractionController?.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting)
webView.channelDelegate?.onFindResultReceived(activeMatchOrdinal: activeMatchOrdinal, numberOfMatches: numberOfMatches, isDoneCounting: isDoneCounting)
} else if message.name == "onCallAsyncJavaScriptResultBelowIOS14Received" {
let body = message.body as! [String: Any?]
@ -2701,19 +2724,6 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
}
}
public func findAllAsync(find: String?, completionHandler: ((Any?, Error?) -> Void)?) {
let startSearch = "window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsync('\(find ?? "")');"
evaluateJavaScript(startSearch, completionHandler: completionHandler)
}
public func findNext(forward: Bool, completionHandler: ((Any?, Error?) -> Void)?) {
evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findNext(\(forward ? "true" : "false"));", completionHandler: completionHandler)
}
public func clearMatches(completionHandler: ((Any?, Error?) -> Void)?) {
evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._clearMatches();", completionHandler: completionHandler)
}
public func scrollTo(x: Int, y: Int, animated: Bool) {
scrollView.setContentOffset(CGPoint(x: x, y: y), animated: animated)
}
@ -3004,8 +3014,11 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
if #available(iOS 15.0, *) {
removeObserver(self, forKeyPath: #keyPath(WKWebView.cameraCaptureState))
removeObserver(self, forKeyPath: #keyPath(WKWebView.microphoneCaptureState))
// removeObserver(self, forKeyPath: #keyPath(WKWebView.fullscreenState))
}
// TODO: Still not working on iOS 16.0!
// if #available(iOS 16.0, *) {
// removeObserver(self, forKeyPath: #keyPath(WKWebView.fullscreenState))
// }
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset))
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.zoomScale))
resumeTimers()
@ -3044,6 +3057,8 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
disablePullToRefresh()
pullToRefreshControl?.dispose()
pullToRefreshControl = nil
findInteractionController?.dispose()
findInteractionController = nil
uiDelegate = nil
navigationDelegate = nil
scrollView.delegate = nil

View File

@ -74,6 +74,8 @@ public class InAppWebViewSettings: ISettings<InAppWebView> {
var isTextInteractionEnabled = true
var isSiteSpecificQuirksModeEnabled = true
var upgradeKnownHostsToHTTPS = true
var isElementFullscreenEnabled = true
var isFindInteractionEnabled = false
override init(){
super.init()
@ -146,6 +148,10 @@ public class InAppWebViewSettings: ISettings<InAppWebView> {
}
if #available(iOS 15.4, *) {
realSettings["isSiteSpecificQuirksModeEnabled"] = configuration.preferences.isSiteSpecificQuirksModeEnabled
realSettings["isElementFullscreenEnabled"] = configuration.preferences.isElementFullscreenEnabled
}
if #available(iOS 16.0, *) {
realSettings["isFindInteractionEnabled"] = webView.isFindInteractionEnabled
}
}
return realSettings

View File

@ -19,17 +19,22 @@ public class WebViewChannelDelegate : ChannelDelegate {
public override func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "getUrl":
guard let method = WebViewChannelDelegateMethods.init(rawValue: call.method) else {
result(FlutterMethodNotImplemented)
return
}
switch method {
case .getUrl:
result(webView?.url?.absoluteString)
break
case "getTitle":
case .getTitle:
result(webView?.title)
break
case "getProgress":
case .getProgress:
result( (webView != nil) ? Int(webView!.estimatedProgress * 100) : nil )
break
case "loadUrl":
case .loadUrl:
let urlRequest = arguments!["urlRequest"] as! [String:Any?]
let allowingReadAccessTo = arguments!["allowingReadAccessTo"] as? String
var allowingReadAccessToURL: URL? = nil
@ -39,7 +44,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
webView?.loadUrl(urlRequest: URLRequest.init(fromPluginMap: urlRequest), allowingReadAccessTo: allowingReadAccessToURL)
result(true)
break
case "postUrl":
case .postUrl:
if let webView = webView {
let url = arguments!["url"] as! String
let postData = arguments!["postData"] as! FlutterStandardTypedData
@ -47,7 +52,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
}
result(true)
break
case "loadData":
case .loadData:
let data = arguments!["data"] as! String
let mimeType = arguments!["mimeType"] as! String
let encoding = arguments!["encoding"] as! String
@ -60,7 +65,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
webView?.loadData(data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL)
result(true)
break
case "loadFile":
case .loadFile:
let assetFilePath = arguments!["assetFilePath"] as! String
do {
@ -72,7 +77,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
}
result(true)
break
case "evaluateJavascript":
case .evaluateJavascript:
if let webView = webView {
let source = arguments!["source"] as! String
let contentWorldMap = arguments!["contentWorld"] as? [String:Any?]
@ -91,58 +96,58 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "injectJavascriptFileFromUrl":
case .injectJavascriptFileFromUrl:
let urlFile = arguments!["urlFile"] as! String
let scriptHtmlTagAttributes = arguments!["scriptHtmlTagAttributes"] as? [String:Any?]
webView?.injectJavascriptFileFromUrl(urlFile: urlFile, scriptHtmlTagAttributes: scriptHtmlTagAttributes)
result(true)
break
case "injectCSSCode":
case .injectCSSCode:
let source = arguments!["source"] as! String
webView?.injectCSSCode(source: source)
result(true)
break
case "injectCSSFileFromUrl":
case .injectCSSFileFromUrl:
let urlFile = arguments!["urlFile"] as! String
let cssLinkHtmlTagAttributes = arguments!["cssLinkHtmlTagAttributes"] as? [String:Any?]
webView?.injectCSSFileFromUrl(urlFile: urlFile, cssLinkHtmlTagAttributes: cssLinkHtmlTagAttributes)
result(true)
break
case "reload":
case .reload:
webView?.reload()
result(true)
break
case "goBack":
case .goBack:
webView?.goBack()
result(true)
break
case "canGoBack":
case .canGoBack:
result(webView?.canGoBack ?? false)
break
case "goForward":
case .goForward:
webView?.goForward()
result(true)
break
case "canGoForward":
case .canGoForward:
result(webView?.canGoForward ?? false)
break
case "goBackOrForward":
case .goBackOrForward:
let steps = arguments!["steps"] as! Int
webView?.goBackOrForward(steps: steps)
result(true)
break
case "canGoBackOrForward":
case .canGoBackOrForward:
let steps = arguments!["steps"] as! Int
result(webView?.canGoBackOrForward(steps: steps) ?? false)
break
case "stopLoading":
case .stopLoading:
webView?.stopLoading()
result(true)
break
case "isLoading":
case .isLoading:
result(webView?.isLoading ?? false)
break
case "takeScreenshot":
case .takeScreenshot:
if let webView = webView, #available(iOS 11.0, *) {
let screenshotConfiguration = arguments!["screenshotConfiguration"] as? [String: Any?]
webView.takeScreenshot(with: screenshotConfiguration, completionHandler: { (screenshot) -> Void in
@ -153,7 +158,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "setSettings":
case .setSettings:
if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController {
let inAppBrowserSettings = InAppBrowserSettings()
let inAppBrowserSettingsMap = arguments!["settings"] as! [String: Any]
@ -167,14 +172,14 @@ public class WebViewChannelDelegate : ChannelDelegate {
}
result(true)
break
case "getSettings":
case .getSettings:
if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController {
result(iabController.getSettings())
} else {
result(webView?.getSettings())
}
break
case "close":
case .close:
if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController {
iabController.close {
result(true)
@ -183,7 +188,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(FlutterMethodNotImplemented)
}
break
case "show":
case .show:
if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController {
iabController.show {
result(true)
@ -192,7 +197,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(FlutterMethodNotImplemented)
}
break
case "hide":
case .hide:
if let iabController = webView?.inAppBrowserDelegate as? InAppBrowserWebViewController {
iabController.hide {
result(true)
@ -201,13 +206,13 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(FlutterMethodNotImplemented)
}
break
case "getCopyBackForwardList":
case .getCopyBackForwardList:
result(webView?.getCopyBackForwardList())
break
case "findAllAsync":
if let webView = webView {
case .findAllAsync:
if let webView = webView, let findInteractionController = webView.findInteractionController {
let find = arguments!["find"] as! String
webView.findAllAsync(find: find, completionHandler: {(value, error) in
findInteractionController.findAllAsync(find: find, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil))
return
@ -218,10 +223,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "findNext":
if let webView = webView {
case .findNext:
if let webView = webView, let findInteractionController = webView.findInteractionController {
let forward = arguments!["forward"] as! Bool
webView.findNext(forward: forward, completionHandler: {(value, error) in
findInteractionController.findNext(forward: forward, completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil))
return
@ -232,9 +237,9 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "clearMatches":
if let webView = webView {
webView.clearMatches(completionHandler: {(value, error) in
case .clearMatches:
if let webView = webView, let findInteractionController = webView.findInteractionController {
findInteractionController.clearMatches(completionHandler: {(value, error) in
if error != nil {
result(FlutterError(code: "WebViewChannelDelegate", message: error?.localizedDescription, details: nil))
return
@ -245,33 +250,33 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "clearCache":
case .clearCache:
webView?.clearCache()
result(true)
break
case "scrollTo":
case .scrollTo:
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
let animated = arguments!["animated"] as! Bool
webView?.scrollTo(x: x, y: y, animated: animated)
result(true)
break
case "scrollBy":
case .scrollBy:
let x = arguments!["x"] as! Int
let y = arguments!["y"] as! Int
let animated = arguments!["animated"] as! Bool
webView?.scrollBy(x: x, y: y, animated: animated)
result(true)
break
case "pauseTimers":
case .pauseTimers:
webView?.pauseTimers()
result(true)
break
case "resumeTimers":
case .resumeTimers:
webView?.resumeTimers()
result(true)
break
case "printCurrentPage":
case .printCurrentPage:
if let webView = webView {
let settings = PrintJobSettings()
if let settingsMap = arguments!["settings"] as? [String: Any?] {
@ -282,29 +287,29 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "getContentHeight":
case .getContentHeight:
result(webView?.getContentHeight())
break
case "zoomBy":
case .zoomBy:
let zoomFactor = (arguments!["zoomFactor"] as! NSNumber).floatValue
let animated = arguments!["animated"] as! Bool
webView?.zoomBy(zoomFactor: zoomFactor, animated: animated)
result(true)
break
case "reloadFromOrigin":
case .reloadFromOrigin:
webView?.reloadFromOrigin()
result(true)
break
case "getOriginalUrl":
case .getOriginalUrl:
result(webView?.getOriginalUrl()?.absoluteString)
break
case "getZoomScale":
case .getZoomScale:
result(webView?.getZoomScale())
break
case "hasOnlySecureContent":
case .hasOnlySecureContent:
result(webView?.hasOnlySecureContent ?? false)
break
case "getSelectedText":
case .getSelectedText:
if let webView = webView {
webView.getSelectedText { (value, error) in
if let err = error {
@ -319,7 +324,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "getHitTestResult":
case .getHitTestResult:
if let webView = webView {
webView.getHitTestResult { (hitTestResult) in
result(hitTestResult.toMap())
@ -329,11 +334,11 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "clearFocus":
case .clearFocus:
webView?.clearFocus()
result(true)
break
case "setContextMenu":
case .setContextMenu:
if let webView = webView {
let contextMenu = arguments!["contextMenu"] as? [String: Any]
webView.contextMenu = contextMenu
@ -342,7 +347,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "requestFocusNodeHref":
case .requestFocusNodeHref:
if let webView = webView {
webView.requestFocusNodeHref { (value, error) in
if let err = error {
@ -356,7 +361,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "requestImageRef":
case .requestImageRef:
if let webView = webView {
webView.requestImageRef { (value, error) in
if let err = error {
@ -370,24 +375,24 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "getScrollX":
case .getScrollX:
if let webView = webView {
result(Int(webView.scrollView.contentOffset.x))
} else {
result(nil)
}
break
case "getScrollY":
case .getScrollY:
if let webView = webView {
result(Int(webView.scrollView.contentOffset.y))
} else {
result(nil)
}
break
case "getCertificate":
case .getCertificate:
result(webView?.getCertificate()?.toMap())
break
case "addUserScript":
case .addUserScript:
if let webView = webView {
let userScriptMap = arguments!["userScript"] as! [String: Any?]
let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView.windowId)!
@ -396,23 +401,23 @@ public class WebViewChannelDelegate : ChannelDelegate {
}
result(true)
break
case "removeUserScript":
case .removeUserScript:
let index = arguments!["index"] as! Int
let userScriptMap = arguments!["userScript"] as! [String: Any?]
let userScript = UserScript.fromMap(map: userScriptMap, windowId: webView?.windowId)!
webView?.configuration.userContentController.removeUserOnlyScript(at: index, injectionTime: userScript.injectionTime)
result(true)
break
case "removeUserScriptsByGroupName":
case .removeUserScriptsByGroupName:
let groupName = arguments!["groupName"] as! String
webView?.configuration.userContentController.removeUserOnlyScripts(with: groupName)
result(true)
break
case "removeAllUserScripts":
case .removeAllUserScripts:
webView?.configuration.userContentController.removeAllUserOnlyScripts()
result(true)
break
case "callAsyncJavaScript":
case .callAsyncJavaScript:
if let webView = webView, #available(iOS 10.3, *) {
if #available(iOS 14.0, *) {
let functionBody = arguments!["functionBody"] as! String
@ -436,7 +441,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "createPdf":
case .createPdf:
if let webView = webView, #available(iOS 14.0, *) {
let configuration = arguments!["pdfConfiguration"] as? [String: Any?]
webView.createPdf(configuration: configuration, completionHandler: { (pdf) -> Void in
@ -447,7 +452,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "createWebArchiveData":
case .createWebArchiveData:
if let webView = webView, #available(iOS 14.0, *) {
webView.createWebArchiveData(dataCompletionHandler: { (webArchiveData) -> Void in
result(webArchiveData)
@ -457,7 +462,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "saveWebArchive":
case .saveWebArchive:
if let webView = webView, #available(iOS 14.0, *) {
let filePath = arguments!["filePath"] as! String
let autoname = arguments!["autoname"] as! Bool
@ -469,7 +474,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "isSecureContext":
case .isSecureContext:
if let webView = webView {
webView.isSecureContext(completionHandler: { (isSecureContext) in
result(isSecureContext)
@ -479,7 +484,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "createWebMessageChannel":
case .createWebMessageChannel:
if let webView = webView {
let _ = webView.createWebMessageChannel { (webMessageChannel) in
result(webMessageChannel.toMap())
@ -488,7 +493,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "postWebMessage":
case .postWebMessage:
if let webView = webView {
let message = arguments!["message"] as! [String: Any?]
let targetOrigin = arguments!["targetOrigin"] as! String
@ -516,7 +521,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "addWebMessageListener":
case .addWebMessageListener:
if let webView = webView {
let webMessageListenerMap = arguments!["webMessageListener"] as! [String: Any?]
let webMessageListener = WebMessageListener.fromMap(map: webMessageListenerMap)!
@ -530,21 +535,21 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "canScrollVertically":
case .canScrollVertically:
if let webView = webView {
result(webView.canScrollVertically())
} else {
result(false)
}
break
case "canScrollHorizontally":
case .canScrollHorizontally:
if let webView = webView {
result(webView.canScrollHorizontally())
} else {
result(false)
}
break
case "pauseAllMediaPlayback":
case .pauseAllMediaPlayback:
if let webView = webView, #available(iOS 15.0, *) {
webView.pauseAllMediaPlayback(completionHandler: { () -> Void in
result(true)
@ -553,7 +558,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "setAllMediaPlaybackSuspended":
case .setAllMediaPlaybackSuspended:
if let webView = webView, #available(iOS 15.0, *) {
let suspended = arguments!["suspended"] as! Bool
webView.setAllMediaPlaybackSuspended(suspended, completionHandler: { () -> Void in
@ -563,7 +568,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "closeAllMediaPresentations":
case .closeAllMediaPresentations:
if let webView = self.webView, #available(iOS 14.5, *) {
// closeAllMediaPresentations with completionHandler v15.0 makes the app crash
// with error EXC_BAD_ACCESS, so use closeAllMediaPresentations v14.5
@ -573,7 +578,7 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "requestMediaPlaybackState":
case .requestMediaPlaybackState:
if let webView = webView, #available(iOS 15.0, *) {
webView.requestMediaPlaybackState(completionHandler: { (state) -> Void in
result(state.rawValue)
@ -582,32 +587,33 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(nil)
}
break
case "getMetaThemeColor":
case .getMetaThemeColor:
if let webView = webView, #available(iOS 15.0, *) {
result(webView.themeColor?.hexString)
} else {
result(nil)
}
break
case "isInFullscreen":
// if let webView = webView, #available(iOS 15.0, *) {
// result(webView.fullscreenState == .inFullscreen)
// }
case .isInFullscreen:
if let webView = webView {
result(webView.inFullscreen)
if #available(iOS 16.0, *) {
result(webView.fullscreenState == .inFullscreen)
} else {
result(webView.inFullscreen)
}
}
else {
result(false)
}
break
case "getCameraCaptureState":
case .getCameraCaptureState:
if let webView = webView, #available(iOS 15.0, *) {
result(webView.cameraCaptureState.rawValue)
} else {
result(nil)
}
break
case "setCameraCaptureState":
case .setCameraCaptureState:
if let webView = webView, #available(iOS 15.0, *) {
let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none
webView.setCameraCaptureState(state) {
@ -617,14 +623,14 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
case "getMicrophoneCaptureState":
case .getMicrophoneCaptureState:
if let webView = webView, #available(iOS 15.0, *) {
result(webView.microphoneCaptureState.rawValue)
} else {
result(nil)
}
break
case "setMicrophoneCaptureState":
case .setMicrophoneCaptureState:
if let webView = webView, #available(iOS 15.0, *) {
let state = WKMediaCaptureState.init(rawValue: arguments!["state"] as! Int) ?? WKMediaCaptureState.none
webView.setMicrophoneCaptureState(state) {
@ -634,12 +640,10 @@ public class WebViewChannelDelegate : ChannelDelegate {
result(false)
}
break
default:
result(FlutterMethodNotImplemented)
break
}
}
@available(*, deprecated, message: "Use FindInteractionChannelDelegate.onFindResultReceived instead.")
public func onFindResultReceived(activeMatchOrdinal: Int, numberOfMatches: Int, isDoneCounting: Bool) {
let arguments: [String : Any?] = [
"activeMatchOrdinal": activeMatchOrdinal,

View File

@ -0,0 +1,89 @@
//
// WebViewChannelDelegateMethods.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 08/10/22.
//
import Foundation
public enum WebViewChannelDelegateMethods: String {
case getUrl = "getUrl"
case getTitle = "getTitle"
case getProgress = "getProgress"
case loadUrl = "loadUrl"
case postUrl = "postUrl"
case loadData = "loadData"
case loadFile = "loadFile"
case evaluateJavascript = "evaluateJavascript"
case injectJavascriptFileFromUrl = "injectJavascriptFileFromUrl"
case injectCSSCode = "injectCSSCode"
case injectCSSFileFromUrl = "injectCSSFileFromUrl"
case reload = "reload"
case goBack = "goBack"
case canGoBack = "canGoBack"
case goForward = "goForward"
case canGoForward = "canGoForward"
case goBackOrForward = "goBackOrForward"
case canGoBackOrForward = "canGoBackOrForward"
case stopLoading = "stopLoading"
case isLoading = "isLoading"
case takeScreenshot = "takeScreenshot"
case setSettings = "setSettings"
case getSettings = "getSettings"
case close = "close"
case show = "show"
case hide = "hide"
case getCopyBackForwardList = "getCopyBackForwardList"
@available(*, deprecated, message: "Use FindInteractionController.findAllAsync instead.")
case findAllAsync = "findAllAsync"
@available(*, deprecated, message: "Use FindInteractionController.findNext instead.")
case findNext = "findNext"
@available(*, deprecated, message: "Use FindInteractionController.clearMatches instead.")
case clearMatches = "clearMatches"
case clearCache = "clearCache"
case scrollTo = "scrollTo"
case scrollBy = "scrollBy"
case pauseTimers = "pauseTimers"
case resumeTimers = "resumeTimers"
case printCurrentPage = "printCurrentPage"
case getContentHeight = "getContentHeight"
case zoomBy = "zoomBy"
case reloadFromOrigin = "reloadFromOrigin"
case getOriginalUrl = "getOriginalUrl"
case getZoomScale = "getZoomScale"
case hasOnlySecureContent = "hasOnlySecureContent"
case getSelectedText = "getSelectedText"
case getHitTestResult = "getHitTestResult"
case clearFocus = "clearFocus"
case setContextMenu = "setContextMenu"
case requestFocusNodeHref = "requestFocusNodeHref"
case requestImageRef = "requestImageRef"
case getScrollX = "getScrollX"
case getScrollY = "getScrollY"
case getCertificate = "getCertificate"
case addUserScript = "addUserScript"
case removeUserScript = "removeUserScript"
case removeUserScriptsByGroupName = "removeUserScriptsByGroupName"
case removeAllUserScripts = "removeAllUserScripts"
case callAsyncJavaScript = "callAsyncJavaScript"
case createPdf = "createPdf"
case createWebArchiveData = "createWebArchiveData"
case saveWebArchive = "saveWebArchive"
case isSecureContext = "isSecureContext"
case createWebMessageChannel = "createWebMessageChannel"
case postWebMessage = "postWebMessage"
case addWebMessageListener = "addWebMessageListener"
case canScrollVertically = "canScrollVertically"
case canScrollHorizontally = "canScrollHorizontally"
case pauseAllMediaPlayback = "pauseAllMediaPlayback"
case setAllMediaPlaybackSuspended = "setAllMediaPlaybackSuspended"
case closeAllMediaPresentations = "closeAllMediaPresentations"
case requestMediaPlaybackState = "requestMediaPlaybackState"
case getMetaThemeColor = "getMetaThemeColor"
case isInFullscreen = "isInFullscreen"
case getCameraCaptureState = "getCameraCaptureState"
case setCameraCaptureState = "setCameraCaptureState"
case getMicrophoneCaptureState = "getMicrophoneCaptureState"
case setMicrophoneCaptureState = "setMicrophoneCaptureState"
}

View File

@ -42,7 +42,7 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findAllAsyncForElement = function(element, key
span.setAttribute(
"id",
"WKWEBVIEW_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
"\(JAVASCRIPT_BRIDGE_NAME)_SEARCH_WORD_" + \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE)
);
span.setAttribute("class", "\(JAVASCRIPT_BRIDGE_NAME)_Highlight");
var backgroundColor = \(FIND_TEXT_HIGHLIGHT_SEARCH_RESULT_COUNT_JS_SOURCE) == 0 ? "#FF9732" : "#FFFF00";

View File

@ -28,17 +28,17 @@ public class PullToRefreshControl : UIRefreshControl, Disposable {
}
public func prepare() {
if let options = settings {
if options.enabled {
if let settings = settings {
if settings.enabled {
delegate?.enablePullToRefresh()
}
if let color = options.color, !color.isEmpty {
if let color = settings.color, !color.isEmpty {
tintColor = UIColor(hexString: color)
}
if let backgroundTintColor = options.backgroundColor, !backgroundTintColor.isEmpty {
if let backgroundTintColor = settings.backgroundColor, !backgroundTintColor.isEmpty {
backgroundColor = UIColor(hexString: backgroundTintColor)
}
if let attributedTitleMap = options.attributedTitle {
if let attributedTitleMap = settings.attributedTitle {
attributedTitle = NSAttributedString.fromMap(map: attributedTitleMap)
}
}

View File

@ -0,0 +1,19 @@
//
// UIFindSession.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 07/10/22.
//
import Foundation
@available(iOS 16.0, *)
extension UIFindSession {
public func toMap () -> [String:Any?] {
return [
"resultCount": resultCount,
"highlightedResultIndex": highlightedResultIndex,
"searchResultDisplayStyle": searchResultDisplayStyle.rawValue
]
}
}

View File

@ -62,6 +62,10 @@ class WebViewFeature {
static const PROXY_OVERRIDE =
WebViewFeature._internal('PROXY_OVERRIDE', 'PROXY_OVERRIDE');
///This feature covers [ProxySettings.reverseBypassEnabled].
static const PROXY_OVERRIDE_REVERSE_BYPASS = WebViewFeature._internal(
'PROXY_OVERRIDE_REVERSE_BYPASS', 'PROXY_OVERRIDE_REVERSE_BYPASS');
///
static const RECEIVE_HTTP_ERROR =
WebViewFeature._internal('RECEIVE_HTTP_ERROR', 'RECEIVE_HTTP_ERROR');
@ -223,6 +227,7 @@ class WebViewFeature {
WebViewFeature.OFF_SCREEN_PRERASTER,
WebViewFeature.POST_WEB_MESSAGE,
WebViewFeature.PROXY_OVERRIDE,
WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS,
WebViewFeature.RECEIVE_HTTP_ERROR,
WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR,
WebViewFeature.SAFE_BROWSING_ALLOWLIST,

View File

@ -0,0 +1,226 @@
import 'dart:developer' as developer;
import 'package:flutter/services.dart';
import '../in_app_webview/in_app_webview_settings.dart';
import '../debug_logging_settings.dart';
import '../types/main.dart';
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
class FindInteractionController {
MethodChannel? _channel;
///Debug settings.
static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings();
///Event fired as find-on-page operations progress.
///The listener may be notified multiple times while the operation is underway, and the [numberOfMatches] value should not be considered final unless [isDoneCounting] is true.
///
///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
///
///[numberOfMatches] represents how many matches have been found.
///
///[isDoneCounting] whether the find operation has actually completed.
///
///**NOTE**: on iOS, if [InAppWebViewSettings.isFindInteractionEnabled] is `true`, this event will not be called.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.FindListener.onFindResultReceived](https://developer.android.com/reference/android/webkit/WebView.FindListener#onFindResultReceived(int,%20int,%20boolean)))
///- iOS
final void Function(
FindInteractionController controller,
int activeMatchOrdinal,
int numberOfMatches,
bool isDoneCounting)? onFindResultReceived;
FindInteractionController({this.onFindResultReceived}) {}
void initMethodChannel(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_find_interaction_$id');
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
}
_debugLog(String method, dynamic args) {
if (FindInteractionController.debugLoggingSettings.enabled) {
for (var regExp
in FindInteractionController.debugLoggingSettings.excludeFilter) {
if (regExp.hasMatch(method)) return;
}
var maxLogMessageLength =
FindInteractionController.debugLoggingSettings.maxLogMessageLength;
String message = "FindInteractionController " +
" calling \"" +
method.toString() +
"\" using " +
args.toString();
if (maxLogMessageLength >= 0 && message.length > maxLogMessageLength) {
message = message.substring(0, maxLogMessageLength) + "...";
}
if (!FindInteractionController.debugLoggingSettings.usePrint) {
developer.log(message, name: this.runtimeType.toString());
} else {
print("[${this.runtimeType.toString()}] $message");
}
}
}
Future<dynamic> _handleMethod(MethodCall call) async {
_debugLog(call.method, call.arguments);
switch (call.method) {
case "onFindResultReceived":
if (onFindResultReceived != null) {
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
onFindResultReceived!(
this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
}
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
///Finds all instances of find on the page and highlights them. Notifies [FindInteractionController.onFindResultReceived] listener.
///
///[find] represents the string to find.
///
///**NOTE**: on Android native WebView, it finds all instances asynchronously. Successive calls to this will cancel any pending searches.
///
///**NOTE**: on iOS, if [InAppWebViewSettings.isFindInteractionEnabled] is `true`,
///it uses the built-in find interaction native UI,
///otherwise this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.findAllAsync](https://developer.android.com/reference/android/webkit/WebView#findAllAsync(java.lang.String)))
///- iOS (if [InAppWebViewSettings.isFindInteractionEnabled] is `true`: [Official API - UIFindInteraction.presentFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975832-presentfindnavigator?changes=_2) with [Official API - UIFindInteraction.searchText](https://developer.apple.com/documentation/uikit/uifindinteraction/3975834-searchtext?changes=_2))
Future<void> findAllAsync({required String find}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await _channel?.invokeMethod('findAllAsync', args);
}
///Highlights and scrolls to the next match found by [findAllAsync]. Notifies [FindInteractionController.onFindResultReceived] listener.
///
///[forward] represents the direction to search.
///
///**NOTE**: on iOS, if [InAppWebViewSettings.isFindInteractionEnabled] is `true`,
///it uses the built-in find interaction native UI,
///otherwise this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.findNext](https://developer.android.com/reference/android/webkit/WebView#findNext(boolean)))
///- iOS (if [InAppWebViewSettings.isFindInteractionEnabled] is `true`: [Official API - UIFindInteraction.findNext](https://developer.apple.com/documentation/uikit/uifindinteraction/3975829-findnext?changes=_2) and ([Official API - UIFindInteraction.findPrevious](https://developer.apple.com/documentation/uikit/uifindinteraction/3975830-findprevious?changes=_2)))
Future<void> findNext({required bool forward}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await _channel?.invokeMethod('findNext', args);
}
///Clears the highlighting surrounding text matches created by [findAllAsync].
///
///**NOTE**: on iOS, if [InAppWebViewSettings.isFindInteractionEnabled] is `true`,
///it uses the built-in find interaction native UI,
///otherwise this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.clearMatches](https://developer.android.com/reference/android/webkit/WebView#clearMatches()))
///- iOS (if [InAppWebViewSettings.isFindInteractionEnabled] is `true`: [Official API - UIFindInteraction.dismissFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975827-dismissfindnavigator?changes=_2))
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel?.invokeMethod('clearMatches', args);
}
///Pre-populate the system find panel's search text field with a search query.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.searchText](https://developer.apple.com/documentation/uikit/uifindinteraction/3975834-searchtext?changes=_2))
Future<void> setSearchText(String? searchText) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('searchText', () => searchText);
await _channel?.invokeMethod('setSearchText', args);
}
///Get the system find panel's search text field value.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.searchText](https://developer.apple.com/documentation/uikit/uifindinteraction/3975834-searchtext?changes=_2))
Future<String?> getSearchText() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel?.invokeMethod('getSearchText', args);
}
///A Boolean value that indicates when the find panel displays onscreen.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.isFindNavigatorVisible](https://developer.apple.com/documentation/uikit/uifindinteraction/3975828-isfindnavigatorvisible?changes=_2))
Future<bool?> isFindNavigatorVisible() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel?.invokeMethod('isFindNavigatorVisible', args);
}
///Updates the results the interface displays for the active search.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.updateResultCount](https://developer.apple.com/documentation/uikit/uifindinteraction/3975835-updateresultcount?changes=_2))
Future<void> updateResultCount() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel?.invokeMethod('updateResultCount', args);
}
///Begins a search, displaying the find panel.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.presentFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975832-presentfindnavigator?changes=_2))
Future<void> presentFindNavigator() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel?.invokeMethod('presentFindNavigator', args);
}
///Dismisses the find panel, if present.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.dismissFindNavigator](https://developer.apple.com/documentation/uikit/uifindinteraction/3975827-dismissfindnavigator?changes=_2))
Future<void> dismissFindNavigator() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel?.invokeMethod('dismissFindNavigator', args);
}
///If there's a currently active find session (implying [isFindNavigatorVisible] is `true`), returns the active find session.
///
///**NOTE**: available only on iOS and only if [InAppWebViewSettings.isFindInteractionEnabled] is `true`.
///
///**Supported Platforms/Implementations**:
///- iOS ([Official API - UIFindInteraction.activeFindSession](https://developer.apple.com/documentation/uikit/uifindinteraction/3975825-activefindsession?changes=_7____4_8&language=objc))
Future<FindSession?> getActiveFindSession() async {
Map<String, dynamic> args = <String, dynamic>{};
Map<String, dynamic>? result =
(await _channel?.invokeMethod('getActiveFindSession', args))
?.cast<String, dynamic>();
return FindSession.fromMap(result);
}
}

View File

@ -0,0 +1 @@
export 'find_interaction_controller.dart';

View File

@ -6,6 +6,7 @@ import 'dart:developer' as developer;
import 'package:flutter/services.dart';
import '../context_menu.dart';
import '../find_interaction/find_interaction_controller.dart';
import '../pull_to_refresh/main.dart';
import '../types/main.dart';
@ -60,6 +61,9 @@ class InAppBrowser {
///Represents the pull-to-refresh feature controller.
PullToRefreshController? pullToRefreshController;
///Represents the find interaction feature controller.
FindInteractionController? findInteractionController;
///Initial list of user scripts to be loaded at start or end of a page loading.
final UnmodifiableListView<UserScript>? initialUserScripts;
@ -129,6 +133,7 @@ class InAppBrowser {
_debugLog(call.method, call.arguments);
this._isOpened = true;
this.pullToRefreshController?.initMethodChannel(id);
this.findInteractionController?.initMethodChannel(id);
onBrowserCreated();
break;
case "onExit":
@ -662,18 +667,8 @@ class InAppBrowser {
return null;
}
///Event fired as find-on-page operations progress.
///The listener may be notified multiple times while the operation is underway, and the [numberOfMatches] value should not be considered final unless [isDoneCounting] is true.
///
///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
///
///[numberOfMatches] represents how many matches have been found.
///
///[isDoneCounting] whether the find operation has actually completed.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.FindListener.onFindResultReceived](https://developer.android.com/reference/android/webkit/WebView.FindListener#onFindResultReceived(int,%20int,%20boolean)))
///- iOS
///Use [FindInteractionController.onFindResultReceived] instead.
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
void onFindResultReceived(
int activeMatchOrdinal, int numberOfMatches, bool isDoneCounting) {}

View File

@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/src/util.dart';
import '../context_menu.dart';
import '../find_interaction/find_interaction_controller.dart';
import '../types/main.dart';
import '../print_job/main.dart';
import 'webview.dart';
@ -61,6 +62,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.contextMenu,
this.initialUserScripts,
this.pullToRefreshController,
this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE,
this.onWebViewCreated,
this.onLoadStart,
@ -87,6 +89,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
@ -173,6 +176,7 @@ class HeadlessInAppWebView implements WebView, Disposable {
switch (call.method) {
case "onWebViewCreated":
pullToRefreshController?.initMethodChannel(id);
findInteractionController?.initMethodChannel(id);
if (onWebViewCreated != null) {
onWebViewCreated!(webViewController);
}
@ -323,6 +327,9 @@ class HeadlessInAppWebView implements WebView, Disposable {
@override
final PullToRefreshController? pullToRefreshController;
@override
final FindInteractionController? findInteractionController;
@override
final WebViewImplementation implementation;
@ -422,6 +429,8 @@ class HeadlessInAppWebView implements WebView, Disposable {
void Function(InAppWebViewController controller,
DownloadStartRequest downloadStartRequest)? onDownloadStartRequest;
///Use [FindInteractionController.onFindResultReceived] instead.
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
@override
void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting)? onFindResultReceived;

View File

@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
import '../find_interaction/find_interaction_controller.dart';
import '../web/web_platform_manager.dart';
import '../context_menu.dart';
@ -50,6 +51,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.initialSettings,
this.initialUserScripts,
this.pullToRefreshController,
this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE,
this.contextMenu,
this.onWebViewCreated,
@ -77,6 +79,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
@ -206,6 +209,9 @@ class InAppWebView extends StatefulWidget implements WebView {
@override
final PullToRefreshController? pullToRefreshController;
@override
final FindInteractionController? findInteractionController;
@override
final ContextMenu? contextMenu;
@ -294,6 +300,8 @@ class InAppWebView extends StatefulWidget implements WebView {
final void Function(InAppWebViewController controller,
DownloadStartRequest downloadStartRequest)? onDownloadStartRequest;
///Use [FindInteractionController.onFindResultReceived] instead.
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
@override
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting)? onFindResultReceived;
@ -725,6 +733,7 @@ class _InAppWebViewState extends State<InAppWebView> {
void _onPlatformViewCreated(int id) {
_controller = InAppWebViewController(id, widget);
widget.pullToRefreshController?.initMethodChannel(id);
widget.findInteractionController?.initMethodChannel(id);
if (widget.onWebViewCreated != null) {
widget.onWebViewCreated!(_controller!);
}

View File

@ -29,6 +29,7 @@ import 'webview.dart';
import '_static_channel.dart';
import '../print_job/main.dart';
import '../find_interaction/main.dart';
///List of forbidden names for JavaScript handlers.
// ignore: non_constant_identifier_names
@ -804,17 +805,34 @@ class InAppWebViewController {
}
break;
case "onFindResultReceived":
if ((_webview != null && _webview!.onFindResultReceived != null) ||
if ((_webview != null && (_webview!.onFindResultReceived != null ||
(_webview!.findInteractionController != null && _webview!.findInteractionController!.onFindResultReceived != null))) ||
_inAppBrowser != null) {
int activeMatchOrdinal = call.arguments["activeMatchOrdinal"];
int numberOfMatches = call.arguments["numberOfMatches"];
bool isDoneCounting = call.arguments["isDoneCounting"];
if (_webview != null && _webview!.onFindResultReceived != null)
_webview!.onFindResultReceived!(
if (_webview != null) {
if (_webview!.findInteractionController != null &&
_webview!.findInteractionController!.onFindResultReceived !=
null)
_webview!.findInteractionController!.onFindResultReceived!(
_webview!.findInteractionController!, activeMatchOrdinal,
numberOfMatches, isDoneCounting);
else
_webview!.onFindResultReceived!(
this, activeMatchOrdinal, numberOfMatches, isDoneCounting);
else
_inAppBrowser!.onFindResultReceived(
activeMatchOrdinal, numberOfMatches, isDoneCounting);
}
else {
if (_inAppBrowser!.findInteractionController != null &&
_inAppBrowser!.findInteractionController!
.onFindResultReceived != null)
_inAppBrowser!.findInteractionController!.onFindResultReceived!(
_webview!.findInteractionController!, activeMatchOrdinal,
numberOfMatches, isDoneCounting);
else
_inAppBrowser!.onFindResultReceived(
activeMatchOrdinal, numberOfMatches, isDoneCounting);
}
}
break;
case "onPermissionRequest":
@ -2159,45 +2177,24 @@ class InAppWebViewController {
await _channel.invokeMethod('clearCache', args);
}
///Finds all instances of find on the page and highlights them. Notifies [WebView.onFindResultReceived] listener.
///
///[find] represents the string to find.
///
///**NOTE**: on Android native WebView, it finds all instances asynchronously. Successive calls to this will cancel any pending searches.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.findAllAsync](https://developer.android.com/reference/android/webkit/WebView#findAllAsync(java.lang.String)))
///- iOS
///Use [FindInteractionController.findAllAsync] instead.
@Deprecated("Use FindInteractionController.findAllAsync instead")
Future<void> findAllAsync({required String find}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('find', () => find);
await _channel.invokeMethod('findAllAsync', args);
}
///Highlights and scrolls to the next match found by [findAllAsync]. Notifies [WebView.onFindResultReceived] listener.
///
///[forward] represents the direction to search.
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.findNext](https://developer.android.com/reference/android/webkit/WebView#findNext(boolean)))
///- iOS
///Use [FindInteractionController.findNext] instead.
@Deprecated("Use FindInteractionController.findNext instead")
Future<void> findNext({required bool forward}) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('forward', () => forward);
await _channel.invokeMethod('findNext', args);
}
///Clears the highlighting surrounding text matches created by [findAllAsync()].
///
///**NOTE**: on iOS, this is implemented using CSS and Javascript.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.clearMatches](https://developer.android.com/reference/android/webkit/WebView#clearMatches()))
///- iOS
///Use [FindInteractionController.clearMatches] instead.
@Deprecated("Use FindInteractionController.clearMatches instead")
Future<void> clearMatches() async {
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('clearMatches', args);

View File

@ -1057,6 +1057,26 @@ class InAppWebViewSettings {
///- iOS
bool upgradeKnownHostsToHTTPS;
///Sets whether fullscreen API is enabled or not.
///
///The default value is `true`.
///
///**NOTE**: available on iOS 15.4+.
///
///**Supported Platforms/Implementations**:
///- iOS
bool isElementFullscreenEnabled;
///Sets whether the web view's built-in find interaction native UI is enabled or not.
///
///The default value is `false`.
///
///**NOTE**: available on iOS 16.0+.
///
///**Supported Platforms/Implementations**:
///- iOS
bool isFindInteractionEnabled;
///Specifies a feature policy for the `<iframe>`.
///The policy defines what features are available to the `<iframe>` based on the origin of the request
///(e.g. access to the microphone, camera, battery, web-share API, etc.).
@ -1220,6 +1240,8 @@ class InAppWebViewSettings {
this.isTextInteractionEnabled = true,
this.isSiteSpecificQuirksModeEnabled = true,
this.upgradeKnownHostsToHTTPS = true,
this.isElementFullscreenEnabled = true,
this.isFindInteractionEnabled = false,
this.iframeAllow,
this.iframeAllowFullscreen,
this.iframeSandbox,
@ -1373,6 +1395,8 @@ class InAppWebViewSettings {
"isTextInteractionEnabled": isTextInteractionEnabled,
"isSiteSpecificQuirksModeEnabled": isSiteSpecificQuirksModeEnabled,
"upgradeKnownHostsToHTTPS": upgradeKnownHostsToHTTPS,
"isElementFullscreenEnabled": isElementFullscreenEnabled,
"isFindInteractionEnabled": isFindInteractionEnabled,
"iframeAllow": iframeAllow,
"iframeAllowFullscreen": iframeAllowFullscreen,
"iframeSandbox": iframeSandbox?.map((e) => e.toNativeValue()).toList(),
@ -1576,6 +1600,8 @@ class InAppWebViewSettings {
settings.isSiteSpecificQuirksModeEnabled =
map["isSiteSpecificQuirksModeEnabled"];
settings.upgradeKnownHostsToHTTPS = map["upgradeKnownHostsToHTTPS"];
settings.isElementFullscreenEnabled = map["isElementFullscreenEnabled"];
settings.isFindInteractionEnabled = map["isFindInteractionEnabled"];
}
}
return settings;

View File

@ -4,4 +4,5 @@ export 'in_app_webview_controller.dart';
export 'in_app_webview_settings.dart';
export 'headless_in_app_webview.dart';
export 'android/main.dart';
export 'apple/main.dart';
export 'apple/main.dart';
export '../find_interaction/find_interaction_controller.dart';

View File

@ -1,6 +1,7 @@
import 'dart:collection';
import 'dart:typed_data';
import '../find_interaction/find_interaction_controller.dart';
import '../pull_to_refresh/pull_to_refresh_controller.dart';
import '../context_menu.dart';
@ -333,18 +334,8 @@ abstract class WebView {
InAppWebViewController controller, ClientCertChallenge challenge)?
onReceivedClientCertRequest;
///Event fired as find-on-page operations progress.
///The listener may be notified multiple times while the operation is underway, and the [numberOfMatches] value should not be considered final unless [isDoneCounting] is true.
///
///[activeMatchOrdinal] represents the zero-based ordinal of the currently selected match.
///
///[numberOfMatches] represents how many matches have been found.
///
///[isDoneCounting] whether the find operation has actually completed.
///
///**Supported Platforms/Implementations**:
///- Android native WebView ([Official API - WebView.FindListener.onFindResultReceived](https://developer.android.com/reference/android/webkit/WebView.FindListener#onFindResultReceived(int,%20int,%20boolean)))
///- iOS
///Use [FindInteractionController.onFindResultReceived] instead.
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
final void Function(InAppWebViewController controller, int activeMatchOrdinal,
int numberOfMatches, bool isDoneCounting)? onFindResultReceived;
@ -960,6 +951,13 @@ abstract class WebView {
///- iOS
final PullToRefreshController? pullToRefreshController;
///Represents the find interaction feature controller.
///
///**Supported Platforms/Implementations**:
///- Android native WebView
///- iOS
final FindInteractionController? findInteractionController;
///Represents the WebView native implementation to be used.
///The default value is [WebViewImplementation.NATIVE].
final WebViewImplementation implementation;
@ -994,6 +992,7 @@ abstract class WebView {
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
@Deprecated('Use FindInteractionController.onFindResultReceived instead')
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
@ -1076,5 +1075,6 @@ abstract class WebView {
this.contextMenu,
this.initialUserScripts,
this.pullToRefreshController,
this.findInteractionController,
this.implementation = WebViewImplementation.NATIVE});
}

View File

@ -17,3 +17,4 @@ export 'web_message/main.dart';
export 'web_authentication_session/main.dart';
export 'print_job/main.dart';
export 'debug_logging_settings.dart';
export 'find_interaction/main.dart';

View File

@ -1,4 +1,5 @@
import 'dart:ui';
import 'dart:developer' as developer;
import 'package:flutter/services.dart';
import '../in_app_webview/webview.dart';
@ -7,6 +8,7 @@ import '../util.dart';
import '../types/main.dart';
import '../in_app_webview/in_app_webview_settings.dart';
import 'pull_to_refresh_settings.dart';
import '../debug_logging_settings.dart';
///A standard controller that can initiate the refreshing of a scroll views contents.
///This should be used whenever the user can refresh the contents of a WebView via a vertical swipe gesture.
@ -26,6 +28,9 @@ class PullToRefreshController {
late PullToRefreshSettings settings;
MethodChannel? _channel;
///Debug settings.
static DebugLoggingSettings debugLoggingSettings = DebugLoggingSettings();
///Event called when a swipe gesture triggers a refresh.
final void Function()? onRefresh;
@ -40,7 +45,46 @@ class PullToRefreshController {
this.settings = settings ?? PullToRefreshSettings();
}
void initMethodChannel(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
}
_debugLog(String method, dynamic args) {
if (PullToRefreshController.debugLoggingSettings.enabled) {
for (var regExp
in PullToRefreshController.debugLoggingSettings.excludeFilter) {
if (regExp.hasMatch(method)) return;
}
var maxLogMessageLength =
PullToRefreshController.debugLoggingSettings.maxLogMessageLength;
String message = "PullToRefreshController " +
" calling \"" +
method.toString() +
"\" using " +
args.toString();
if (maxLogMessageLength >= 0 && message.length > maxLogMessageLength) {
message = message.substring(0, maxLogMessageLength) + "...";
}
if (!PullToRefreshController.debugLoggingSettings.usePrint) {
developer.log(message, name: this.runtimeType.toString());
} else {
print("[${this.runtimeType.toString()}] $message");
}
}
}
Future<dynamic> _handleMethod(MethodCall call) async {
_debugLog(call.method, call.arguments);
switch (call.method) {
case "onRefresh":
if (onRefresh != null) onRefresh!();
@ -162,17 +206,4 @@ class PullToRefreshController {
args.putIfAbsent('attributedTitle', () => attributedTitle.toMap());
await _channel?.invokeMethod('setStyledTitle', args);
}
void initMethodChannel(dynamic id) {
this._channel = MethodChannel(
'com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
this._channel?.setMethodCallHandler((call) async {
try {
return await _handleMethod(call);
} on Error catch (e) {
print(e);
print(e.stackTrace);
}
});
}
}

View File

@ -0,0 +1,24 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
import 'search_result_display_style.dart';
part 'find_session.g.dart';
@ExchangeableObject()
class FindSession_ {
///Returns the total number of results.
int resultCount;
///Returns the index of the currently highlighted result.
///If no result is currently highlighted.
int highlightedResultIndex;
/// Defines how results are reported through the find panel's UI.
SearchResultDisplayStyle_ searchResultDisplayStyle;
FindSession_({
required this.resultCount,
required this.highlightedResultIndex,
required this.searchResultDisplayStyle
});
}

View File

@ -0,0 +1,56 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'find_session.dart';
// **************************************************************************
// ExchangeableObjectGenerator
// **************************************************************************
class FindSession {
///Returns the total number of results.
int resultCount;
///Returns the index of the currently highlighted result.
///If no result is currently highlighted.
int highlightedResultIndex;
/// Defines how results are reported through the find panel's UI.
SearchResultDisplayStyle searchResultDisplayStyle;
FindSession(
{required this.resultCount,
required this.highlightedResultIndex,
required this.searchResultDisplayStyle});
///Gets a possible [FindSession] instance from a [Map] value.
static FindSession? fromMap(Map<String, dynamic>? map) {
if (map == null) {
return null;
}
final instance = FindSession(
resultCount: map['resultCount'],
highlightedResultIndex: map['highlightedResultIndex'],
searchResultDisplayStyle: SearchResultDisplayStyle.fromNativeValue(
map['searchResultDisplayStyle'])!,
);
return instance;
}
///Converts instance to a map.
Map<String, dynamic> toMap() {
return {
"resultCount": resultCount,
"highlightedResultIndex": highlightedResultIndex,
"searchResultDisplayStyle": searchResultDisplayStyle.toNativeValue(),
};
}
///Converts instance to a map.
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return 'FindSession{resultCount: $resultCount, highlightedResultIndex: $highlightedResultIndex, searchResultDisplayStyle: $searchResultDisplayStyle}';
}
}

View File

@ -161,3 +161,5 @@ export 'webview_package_info.dart' show WebViewPackageInfo, AndroidWebViewPackag
export 'webview_render_process_action.dart' show WebViewRenderProcessAction;
export 'window_features.dart' show WindowFeatures, IOSWKWindowFeatures;
export 'requested_with_header_mode.dart' show RequestedWithHeaderMode;
export 'find_session.dart' show FindSession;
export 'search_result_display_style.dart' show SearchResultDisplayStyle;

View File

@ -0,0 +1,20 @@
import 'package:flutter_inappwebview_internal_annotations/flutter_inappwebview_internal_annotations.dart';
part 'search_result_display_style.g.dart';
///Constants that describe the results summary the find panel UI includes.
@ExchangeableEnum()
class SearchResultDisplayStyle_ {
// ignore: unused_field
final int _value;
const SearchResultDisplayStyle_._internal(this._value);
///The find panel includes the total number of results the session reports and the index of the target result.
static const CURRENT_AND_TOTAL = const SearchResultDisplayStyle_._internal(0);
///The find panel includes the total number of results the session reports.
static const TOTAL = const SearchResultDisplayStyle_._internal(1);
///The find panel doesnt include the number of results the session reports.
static const NONE = const SearchResultDisplayStyle_._internal(2);
}

View File

@ -0,0 +1,85 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'search_result_display_style.dart';
// **************************************************************************
// ExchangeableEnumGenerator
// **************************************************************************
///Constants that describe the results summary the find panel UI includes.
class SearchResultDisplayStyle {
final int _value;
final int _nativeValue;
const SearchResultDisplayStyle._internal(this._value, this._nativeValue);
// ignore: unused_element
factory SearchResultDisplayStyle._internalMultiPlatform(
int value, Function nativeValue) =>
SearchResultDisplayStyle._internal(value, nativeValue());
///The find panel includes the total number of results the session reports and the index of the target result.
static const CURRENT_AND_TOTAL = SearchResultDisplayStyle._internal(0, 0);
///The find panel includes the total number of results the session reports.
static const TOTAL = SearchResultDisplayStyle._internal(1, 1);
///The find panel doesnt include the number of results the session reports.
static const NONE = SearchResultDisplayStyle._internal(2, 2);
///Set of all values of [SearchResultDisplayStyle].
static final Set<SearchResultDisplayStyle> values = [
SearchResultDisplayStyle.CURRENT_AND_TOTAL,
SearchResultDisplayStyle.TOTAL,
SearchResultDisplayStyle.NONE,
].toSet();
///Gets a possible [SearchResultDisplayStyle] instance from [int] value.
static SearchResultDisplayStyle? fromValue(int? value) {
if (value != null) {
try {
return SearchResultDisplayStyle.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets a possible [SearchResultDisplayStyle] instance from a native value.
static SearchResultDisplayStyle? fromNativeValue(int? value) {
if (value != null) {
try {
return SearchResultDisplayStyle.values
.firstWhere((element) => element.toNativeValue() == value);
} catch (e) {
return null;
}
}
return null;
}
///Gets [int] value.
int toValue() => _value;
///Gets [int] native value.
int toNativeValue() => _nativeValue;
@override
int get hashCode => _value.hashCode;
@override
bool operator ==(value) => value == _value;
@override
String toString() {
switch (_value) {
case 0:
return 'CURRENT_AND_TOTAL';
case 1:
return 'TOTAL';
case 2:
return 'NONE';
}
return _value.toString();
}
}