Commit 5943059b authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

v3.2.0, added context menu classes, updated docs, fix #235, fix #337, fix #341

parent ecf8d45d
## 3.1.1
## 3.2.0
- Added `ContextMenu` and `ContextMenuItem` classes [#235](https://github.com/pichillilorenzo/flutter_inappwebview/issues/235)
- Added `onCreateContextMenu`, `onHideContextMenu`, `onContextMenuActionItemClicked` context menu events
- Added `contextMenu` to WebView
- Added `disableContextMenu` WebView option
- Added `getSelectedText`, `getHitTestResult` methods to WebView Controller
- Fixed `Confirmation dialog (onbeforeunload) displayed after popped from webview page` [#337](https://github.com/pichillilorenzo/flutter_inappwebview/issues/337)
- Fixed `CookieManager.setCookie` `expiresDate` option
- Fixed `Scrolling not smooth on iOS` [#341](https://github.com/pichillilorenzo/flutter_inappwebview/issues/341)
### BREAKING CHANGES
- Renamed `LongPressHitTestResult` to `InAppWebViewHitTestResult`.
- Renamed `LongPressHitTestResultType` to `InAppWebViewHitTestResultType`.
## 3.1.0
......
......@@ -26,6 +26,8 @@ Also, you need to add `<uses-permission android:name="android.permission.INTERNE
Because of [Flutter AndroidX compatibility](https://flutter.dev/docs/development/packages-and-plugins/androidx-compatibility), the latest version that doesn't use `AndroidX` is `0.6.0`.
Also, note that to use the `InAppWebView` widget on Android, it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)).
### IMPORTANT Note for iOS
If you are starting a new fresh app, you need to create the Flutter App with `flutter create --androidx -i swift` (see [flutter/flutter#13422 (comment)](https://github.com/flutter/flutter/issues/13422#issuecomment-392133780)), otherwise, you will get this message:
......@@ -67,7 +69,8 @@ First, add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](ht
## Usage
Classes:
- [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`.
- [InAppWebView](#inappwebview-class): Flutter Widget for adding an **inline native WebView** integrated into the flutter widget tree. To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`. Also, note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)).
- [ContextMenu](#contextmenu-class): This class represents the WebView context menu.
- [HeadlessInAppWebView](#headlessinappwebview-class): Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
- [InAppBrowser](#inappbrowser-class): In-App Browser using native WebView.
- [ChromeSafariBrowser](#chromesafaribrowser-class): In-App Browser using [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android / [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
......@@ -118,6 +121,8 @@ Keyboard support within webviews is also experimental.
To use `InAppWebView` class on iOS you need to opt-in for the embedded views preview by adding a boolean property to the app's `Info.plist` file, with the key `io.flutter.embedded_views_preview` and the value `YES`.
Also, note that on Android it requires **Android API 20+** (see [AndroidView](https://api.flutter.dev/flutter/widgets/AndroidView-class.html)).
Use `InAppWebViewController` to control the WebView instance.
Example:
```dart
......@@ -133,7 +138,7 @@ Future main() async {
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}z
}
class _MyAppState extends State<MyApp> {
......@@ -296,6 +301,8 @@ Screenshots:
* `resumeTimers`: On Android, it resumes all layout, parsing, and JavaScript timers for all WebViews. This will resume dispatching all timers. On iOS, it resumes all layout, parsing, and JavaScript timers to just this WebView.
* `printCurrentPage`: Prints the current page.
* `getScale`: Gets the current scale of this WebView.
* `getSelectedText`: Gets the selected text.
* `getHitTestResult`: Gets the hit result for hitting an HTML elements.
* `static getDefaultUserAgent`: Gets the default user agent.
##### `InAppWebViewController` Android-specific methods
......@@ -315,6 +322,7 @@ Android-specific methods can be called using the `InAppWebViewController.android
iOS-specific methods can be called using the `InAppWebViewController.ios` attribute.
* `hasOnlySecureContent`: A Boolean value indicating whether all resources on the page have been loaded over securely encrypted connections.
* `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
##### About the JavaScript handler
......@@ -389,6 +397,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly:
* `transparentBackground`: Set to `true` to make the background of the WebView transparent. If your app has a dark theme, this can prevent a white flash on initialization. The default value is `false`.
* `disableVerticalScroll`: Set to `true` to disable vertical scroll. The default value is `false`.
* `disableHorizontalScroll`: Set to `true` to disable horizontal scroll. The default value is `false`.
* `disableContextMenu`: Set to `true` to disable context menu. The default value is `false`.
##### `InAppWebView` Android-specific options
......@@ -499,6 +508,166 @@ Event names that starts with `android` or `ios` are events platform-specific.
* `iosOnDidCommit`: Called when the web view begins to receive web content (available only on iOS).
* `iosOnDidReceiveServerRedirectForProvisionalNavigation`: Called when a web view receives a server redirect (available only on iOS).
### `ContextMenu` class
Class that represents the WebView context menu. It used by `WebView.contextMenu`.
`ContextMenu.menuItems` contains the list of the custom `ContextMenuItem`.
**NOTE**: To make it work properly on Android, JavaScript should be enabled!
Example:
```dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
InAppWebViewController webView;
ContextMenu contextMenu;
String url = "";
double progress = 0;
@override
void initState() {
super.initState();
contextMenu = ContextMenu(
onCreateContextMenu: (hitTestResult) async {
print("onCreateContextMenu");
print(hitTestResult.extra);
print(await webView.getSelectedText());
},
onHideContextMenu: () {
print("onHideContextMenu");
},
onContextMenuActionItemClicked: (contextMenuItemClicked) {
var id = (Platform.isAndroid) ? contextMenuItemClicked.androidId : contextMenuItemClicked.iosId;
print("onContextMenuActionItemClicked: " + id.toString() + " " + contextMenuItemClicked.title);
}
);
contextMenu.menuItems = [
ContextMenuItem(androidId: 1, iosId: "1", title: "Special", action: () async {
print("Menu item Special clicked!");
})
];
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('InAppWebView Example'),
),
body: Container(
child: Column(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0
? LinearProgressIndicator(value: progress)
: Container()),
Expanded(
child: Container(
margin: const EdgeInsets.all(10.0),
decoration:
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
initialUrl: "https://flutter.dev/",
contextMenu: contextMenu,
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
},
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.url = url;
});
},
onLoadStop: (InAppWebViewController controller, String url) async {
setState(() {
this.url = url;
});
},
onProgressChanged: (InAppWebViewController controller, int progress) {
setState(() {
this.progress = progress / 100;
});
},
),
),
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Icon(Icons.arrow_back),
onPressed: () {
if (webView != null) {
webView.goBack();
}
},
),
RaisedButton(
child: Icon(Icons.arrow_forward),
onPressed: () {
if (webView != null) {
webView.goForward();
}
},
),
RaisedButton(
child: Icon(Icons.refresh),
onPressed: () {
if (webView != null) {
webView.reload();
}
},
),
],
),
])),
),
);
}
}
```
### `ContextMenu` Events
* `onCreateContextMenu`: Event fired when the context menu for this WebView is being built.
* `onHideContextMenu`: Event fired when the context menu for this WebView is being hidden.
* `onContextMenuActionItemClicked`: Event fired when a context menu item has been clicked.
### `HeadlessInAppWebView` class
Class that represents a WebView in headless mode. It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
......
......@@ -136,6 +136,7 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Shared.activity.startActivity(myIntent);
result.success(true);
break;
default:
result.notImplemented();
......
......@@ -40,11 +40,12 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
{
String url = (String) call.argument("url");
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
String uuidFallback = (String) call.argument("uuidFallback");
Map<String, String> headersFallback = (Map<String, String>) call.argument("headersFallback");
HashMap<String, Object> optionsFallback = (HashMap<String, Object>) call.argument("optionsFallback");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList");
open(activity, uuid, url, options, uuidFallback, headersFallback, optionsFallback, menuItemList, result);
HashMap<String, Object> contextMenuFallback = (HashMap<String, Object>) call.argument("contextMenuFallback");
open(activity, uuid, url, options, menuItemList, uuidFallback, headersFallback, optionsFallback, contextMenuFallback, result);
}
break;
default:
......@@ -52,8 +53,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
}
}
public void open(Activity activity, String uuid, String url, HashMap<String, Object> options, String uuidFallback,
Map<String, String> headersFallback, HashMap<String, Object> optionsFallback, List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) {
public void open(Activity activity, String uuid, String url, HashMap<String, Object> options, List<HashMap<String, Object>> menuItemList, String uuidFallback,
Map<String, String> headersFallback, HashMap<String, Object> optionsFallback, HashMap<String, Object> contextMenuFallback, MethodChannel.Result result) {
Intent intent = null;
Bundle extras = new Bundle();
......@@ -62,9 +63,11 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
extras.putBoolean("isData", false);
extras.putString("uuid", uuid);
extras.putSerializable("options", options);
extras.putSerializable("headers", (Serializable) headersFallback);
extras.putSerializable("menuItemList", (Serializable) menuItemList);
extras.putSerializable("headers", (Serializable) headersFallback);
extras.putSerializable("contextMenu", (Serializable) contextMenuFallback);
if (CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, ChromeCustomTabsActivity.class);
}
......
......@@ -72,6 +72,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
fromActivity = b.getString("fromActivity");
HashMap<String, Object> optionsMap = (HashMap<String, Object>) b.getSerializable("options");
HashMap<String, Object> contextMenu = (HashMap<String, Object>) b.getSerializable("contextMenu");
options = new InAppBrowserOptions();
options.parse(optionsMap);
......@@ -79,6 +80,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
InAppWebViewOptions webViewOptions = new InAppWebViewOptions();
webViewOptions.parse(optionsMap);
webView.options = webViewOptions;
webView.contextMenu = contextMenu;
actionBar = getSupportActionBar();
......@@ -223,7 +225,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
result.success(isHidden);
break;
case "takeScreenshot":
result.success(takeScreenshot());
takeScreenshot(result);
break;
case "setOptions":
{
......@@ -330,6 +332,16 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
case "getScale":
result.success(getScale());
break;
case "getSelectedText":
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getSelectedText(result);
} else {
result.success(null);
}
break;
case "getHitTestResult":
result.success(getHitTestResult());
break;
default:
result.notImplemented();
}
......@@ -619,24 +631,11 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
close(null);
}
public byte[] takeScreenshot() {
if (webView != null) {
Picture picture = webView.capturePicture();
Bitmap b = Bitmap.createBitmap( webView.getWidth(),
webView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
picture.draw(c);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream.toByteArray();
}
return null;
public void takeScreenshot(MethodChannel.Result result) {
if (webView != null)
webView.takeScreenshot(result);
else
result.success(null);
}
public void setOptions(InAppBrowserOptions newOptions, HashMap<String, Object> newOptionsMap) {
......@@ -843,6 +842,25 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
return null;
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void getSelectedText(MethodChannel.Result result) {
if (webView != null)
webView.getSelectedText(result);
else
result.success(null);
}
public Map<String, Object> getHitTestResult() {
if (webView != null) {
WebView.HitTestResult hitTestResult = webView.getHitTestResult();
Map<String, Object> obj = new HashMap<>();
obj.put("type", hitTestResult.getType());
obj.put("extra", hitTestResult.getExtra());
return obj;
}
return null;
}
public void dispose() {
channel.setMethodCallHandler(null);
if (webView != null) {
......
......@@ -70,7 +70,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
String url = (String) call.argument("url");
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
Map<String, String> headers = (Map<String, String>) call.argument("headers");
openUrl(activity, uuid, url, options, headers);
HashMap<String, Object> contextMenu = (HashMap<String, Object>) call.argument("contextMenu");
openUrl(activity, uuid, url, options, headers, contextMenu);
}
result.success(true);
break;
......@@ -86,7 +87,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
}
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
Map<String, String> headers = (Map<String, String>) call.argument("headers");
openUrl(activity, uuid, url, options, headers);
HashMap<String, Object> contextMenu = (HashMap<String, Object>) call.argument("contextMenu");
openUrl(activity, uuid, url, options, headers, contextMenu);
}
result.success(true);
break;
......@@ -98,7 +100,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
String encoding = (String) call.argument("encoding");
String baseUrl = (String) call.argument("baseUrl");
String historyUrl = (String) call.argument("historyUrl");
openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl);
HashMap<String, Object> contextMenu = (HashMap<String, Object>) call.argument("contextMenu");
openData(activity, uuid, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu);
}
result.success(true);
break;
......@@ -189,7 +192,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
}
}
public void openUrl(Activity activity, String uuid, String url, HashMap<String, Object> options, Map<String, String> headers) {
public void openUrl(Activity activity, String uuid, String url, HashMap<String, Object> options, Map<String, String> headers, HashMap<String, Object> contextMenu) {
Bundle extras = new Bundle();
extras.putString("fromActivity", activity.getClass().getName());
extras.putString("url", url);
......@@ -197,10 +200,11 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
extras.putString("uuid", uuid);
extras.putSerializable("options", options);
extras.putSerializable("headers", (Serializable) headers);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
startInAppBrowserActivity(activity, extras);
}
public void openData(Activity activity, String uuid, HashMap<String, Object> options, String data, String mimeType, String encoding, String baseUrl, String historyUrl) {
public void openData(Activity activity, String uuid, HashMap<String, Object> options, String data, String mimeType, String encoding, String baseUrl, String historyUrl, HashMap<String, Object> contextMenu) {
Bundle extras = new Bundle();
extras.putBoolean("isData", true);
extras.putString("uuid", uuid);
......@@ -210,6 +214,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
extras.putString("encoding", encoding);
extras.putString("baseUrl", baseUrl);
extras.putString("historyUrl", historyUrl);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
startInAppBrowserActivity(activity, extras);
}
......
......@@ -4,6 +4,8 @@ import android.app.Activity;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.View;
......@@ -49,15 +51,16 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
displayListenerProxy.onPreWebViewInitialization(displayManager);
String initialUrl = (String) params.get("initialUrl");
String initialFile = (String) params.get("initialFile");
Map<String, String> initialData = (Map<String, String>) params.get("initialData");
Map<String, String> initialHeaders = (Map<String, String>) params.get("initialHeaders");
final String initialFile = (String) params.get("initialFile");
final Map<String, String> initialData = (Map<String, String>) params.get("initialData");
final Map<String, String> initialHeaders = (Map<String, String>) params.get("initialHeaders");
HashMap<String, Object> initialOptions = (HashMap<String, Object>) params.get("initialOptions");
HashMap<String, Object> contextMenu = (HashMap<String, Object>) params.get("contextMenu");
InAppWebViewOptions options = new InAppWebViewOptions();
options.parse(initialOptions);
webView = new InAppWebView(Shared.activity, this, id, options, containerView);
webView = new InAppWebView(Shared.activity, this, id, options, contextMenu, containerView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
// fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/182
......@@ -86,16 +89,23 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
}
}
if (initialData != null) {
String data = initialData.get("data");
String mimeType = initialData.get("mimeType");
String encoding = initialData.get("encoding");
String baseUrl = initialData.get("baseUrl");
String historyUrl = initialData.get("historyUrl");
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
else
webView.loadUrl(initialUrl, initialHeaders);
final String finalInitialUrl = initialUrl;
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
if (initialData != null) {
String data = initialData.get("data");
String mimeType = initialData.get("mimeType");
String encoding = initialData.get("encoding");
String baseUrl = initialData.get("baseUrl");
String historyUrl = initialData.get("historyUrl");
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
else
webView.loadUrl(finalInitialUrl, initialHeaders);
}
});
if (containerView == null && id instanceof String) {
Map<String, Object> obj = new HashMap<>();
......@@ -381,6 +391,24 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
case "getScale":
result.success((webView != null) ? webView.getUpdatedScale() : null);
break;
case "getSelectedText":
if (webView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.getSelectedText(result);
} else {
result.success(null);
}
break;
case "getHitTestResult":
if (webView != null) {
WebView.HitTestResult hitTestResult = webView.getHitTestResult();
Map<String, Object> obj = new HashMap<>();
obj.put("type", hitTestResult.getType());
obj.put("extra", hitTestResult.getExtra());
result.success(obj);
} else {
result.success(null);
}
break;
default:
result.notImplemented();
}
......
......@@ -13,8 +13,16 @@ import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
......@@ -22,6 +30,9 @@ import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
......@@ -31,6 +42,7 @@ import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerHan
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerTrigger;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util;
......@@ -64,6 +76,18 @@ final public class InAppWebView extends InputAwareWebView {
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler();
public Pattern regexToCancelSubFramesLoadingCompiled;
private GestureDetector gestureDetector = null;
private MotionEvent motionEvent = null;
private LinearLayout floatingContextMenu = null;
public HashMap<String, Object> contextMenu = null;
public Handler headlessHandler = new Handler(Looper.getMainLooper());
private Runnable checkScrollStoppedTask;
private int initialPositionScrollStoppedTask;
private int newCheckScrollStoppedTask = 100;
private Runnable selectedTextTask;
private int newCheckSelectedTextTask = 100;
static final String consoleLogJS = "(function(console) {" +
" var oldLogs = {" +
......@@ -507,6 +531,18 @@ final public class InAppWebView extends InputAwareWebView {
" };" +
"})(window.fetch);";
static final String getSelectedTextJS = "(function(){" +
" var txt;" +
" if (window.getSelection) {" +
" txt = window.getSelection().toString();" +
" } else if (window.document.getSelection) {" +
" txt = window.document.getSelection().toString();" +
" } else if (window.document.selection) {" +
" txt = window.document.selection.createRange().text;" +
" }" +
" return txt;" +
"})();";
public InAppWebView(Context context) {
super(context);
}
......@@ -519,7 +555,7 @@ final public class InAppWebView extends InputAwareWebView {
super(context, attrs, defaultStyle);
}
public InAppWebView(Context context, Object obj, Object id, InAppWebViewOptions options, View containerView) {
public InAppWebView(Context context, Object obj, Object id, InAppWebViewOptions options, HashMap<String, Object> contextMenu, View containerView) {
super(context, containerView);
if (obj instanceof InAppBrowserActivity)
this.inAppBrowserActivity = (InAppBrowserActivity) obj;
......@@ -528,7 +564,8 @@ final public class InAppWebView extends InputAwareWebView {
this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel;
this.id = id;
this.options = options;
//Shared.activity.registerForContextMenu(this);
this.contextMenu = contextMenu;
Shared.activity.registerForContextMenu(this);
}
@Override
......@@ -684,12 +721,64 @@ final public class InAppWebView extends InputAwareWebView {
setVerticalScrollBarEnabled(!options.disableVerticalScroll);
setHorizontalScrollBarEnabled(!options.disableHorizontalScroll);
setOnTouchListener(new View.OnTouchListener() {
gestureDetector = new GestureDetector(this.getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent ev) {
if (floatingContextMenu != null) {
hideContextMenu();
}
return super.onSingleTapUp(ev);
}
});
checkScrollStoppedTask = new Runnable() {
@Override
public void run() {
int newPosition = getScrollY();
if(initialPositionScrollStoppedTask - newPosition == 0){
// has stopped
onScrollStopped();
} else {
initialPositionScrollStoppedTask = getScrollY();
headlessHandler.postDelayed(checkScrollStoppedTask, newCheckScrollStoppedTask);
}
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
selectedTextTask = new Runnable() {
@Override
public void run() {
if (floatingContextMenu != null) {
getSelectedText(new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
if (value == null || value.length() == 0) {
if (floatingContextMenu != null) {
hideContextMenu();
}
} else {
headlessHandler.postDelayed(selectedTextTask, newCheckSelectedTextTask);
}
}
});
}
}
};
}
setOnTouchListener(new OnTouchListener() {
float m_downX;
float m_downY;
@Override
public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
checkScrollStoppedTask.run();
}
if (options.disableHorizontalScroll && options.disableVerticalScroll) {
return (event.getAction() == MotionEvent.ACTION_MOVE);
}
......@@ -738,13 +827,7 @@ final public class InAppWebView extends InputAwareWebView {
});
}
private Point lastTouch;
@Override
public boolean onTouchEvent(MotionEvent ev) {
lastTouch = new Point((int) ev.getX(), (int) ev.getY()) ;
return super.onTouchEvent(ev);
}
private MotionEvent lastMotionEvent = null;
public void setIncognito(boolean enabled) {
WebSettings settings = getSettings();
......@@ -883,8 +966,7 @@ final public class InAppWebView extends InputAwareWebView {
}
public void takeScreenshot(final MethodChannel.Result result) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
headlessHandler.post(new Runnable() {
@Override
public void run() {
int height = (int) (getContentHeight() * scale + 0.5);
......@@ -1192,8 +1274,7 @@ final public class InAppWebView extends InputAwareWebView {
scriptToInject = String.format(jsWrapper, jsonSourceString);
}
final String finalScriptToInject = scriptToInject;
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
headlessHandler.post(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
......@@ -1269,6 +1350,11 @@ final public class InAppWebView extends InputAwareWebView {
int x = (int) (l/scale);
int y = (int) (t/scale);
if (floatingContextMenu != null) {
floatingContextMenu.setAlpha(0f);
floatingContextMenu.setVisibility(View.GONE);
}
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
......@@ -1351,69 +1437,273 @@ final public class InAppWebView extends InputAwareWebView {
public Float getUpdatedScale() {
return scale;
}
/*
@Override
public void onCreateContextMenu(ContextMenu menu) {
Log.d(LOG_TAG, getHitTestResult().getType() + "");
String extra = getHitTestResult().getExtra();
//if (getHitTestResult().getType() == 7 || getHitTestResult().getType() == 5)
if (extra != null)
Log.d(LOG_TAG, extra);
Log.d(LOG_TAG, "\n\nonCreateContextMenu\n\n");
for(int i = 0; i < menu.size(); i++) {
Log.d(LOG_TAG, menu.getItem(i).toString());
}
super.onCreateContextMenu(menu);
sendOnCreateContextMenuEvent();
}
private Integer mActionMode;
private CustomActionModeCallback mActionModeCallback;
private void sendOnCreateContextMenuEvent() {
HitTestResult hitTestResult = getHitTestResult();
Map<String, Object> hitTestResultMap = new HashMap<>();
hitTestResultMap.put("type", hitTestResult.getType());
hitTestResultMap.put("extra", hitTestResult.getExtra());
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("hitTestResult", hitTestResultMap);
channel.invokeMethod("onCreateContextMenu", obj);
}
private Point contextMenuPoint = new Point(0, 0);
private Point lastTouch = new Point(0, 0);
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int mode) {
Log.d(LOG_TAG, "startActionMode");
ViewParent parent = getParent();
if (parent == null || mActionMode != null) {
return null;
public boolean onTouchEvent(MotionEvent ev) {
lastTouch = new Point((int) ev.getX(), (int) ev.getY());
return super.onTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public ActionMode startActionMode(ActionMode.Callback callback) {
return rebuildActionMode(super.startActionMode(callback), callback);
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public ActionMode startActionMode(ActionMode.Callback callback, int type) {
return rebuildActionMode(super.startActionMode(callback, type), callback);
}
public ActionMode rebuildActionMode(
final ActionMode actionMode,
final ActionMode.Callback callback
) {
boolean hasBeenRemovedAndRebuilt = false;
if (floatingContextMenu != null) {
hideContextMenu();
hasBeenRemovedAndRebuilt = true;
}
mActionModeCallback = new CustomActionModeCallback();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mActionMode = ActionMode.TYPE_FLOATING;
//return Shared.activity.getWindow().getDecorView().startActionMode(mActionModeCallback, mActionMode);
return parent.startActionModeForChild(this, mActionModeCallback, mActionMode);
} else {
return parent.startActionModeForChild(this, mActionModeCallback);
if (actionMode == null) {
return null;
}
}
private class CustomActionModeCallback implements ActionMode.Callback {
Menu actionMenu = actionMode.getMenu();
if (options.disableContextMenu) {
actionMenu.clear();
return actionMode;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
floatingContextMenu = (LinearLayout) LayoutInflater.from(this.getContext())
.inflate(R.layout.floating_action_mode, this, false);
HorizontalScrollView horizontalScrollView = (HorizontalScrollView) floatingContextMenu.getChildAt(0);
LinearLayout menuItemListLayout = (LinearLayout) horizontalScrollView.getChildAt(0);
for (int i = 0; i < actionMenu.size(); i++) {
final MenuItem menuItem = actionMenu.getItem(i);
final int itemId = menuItem.getItemId();
final String itemTitle = menuItem.getTitle().toString();
TextView text = (TextView) LayoutInflater.from(this.getContext())
.inflate(R.layout.floating_action_mode_item, this, false);
text.setText(itemTitle);
text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hideContextMenu();
callback.onActionItemClicked(actionMode, menuItem);
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("androidId", itemId);
obj.put("iosId", null);
obj.put("title", itemTitle);
channel.invokeMethod("onContextMenuActionItemClicked", obj);
}
});
if (floatingContextMenu != null) {
menuItemListLayout.addView(text);
}
}
menu.add("ciao");
if (contextMenu != null) {
List<HashMap<String, Object>> customMenuItems = (List<HashMap<String, Object>>) contextMenu.get("menuItems");
if (customMenuItems != null) {
for (final HashMap<String, Object> menuItem : customMenuItems) {
final int itemId = (int) menuItem.get("androidId");
final String itemTitle = (String) menuItem.get("title");
TextView text = (TextView) LayoutInflater.from(this.getContext())
.inflate(R.layout.floating_action_mode_item, this, false);
text.setText(itemTitle);
text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
clearFocus();
hideContextMenu();
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("androidId", itemId);
obj.put("iosId", null);
obj.put("title", itemTitle);
channel.invokeMethod("onContextMenuActionItemClicked", obj);
}
});
if (floatingContextMenu != null) {
menuItemListLayout.addView(text);
return true;
}
}
}
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
final int x = (lastTouch != null) ? lastTouch.x : 0;
final int y = (lastTouch != null) ? lastTouch.y : 0;
contextMenuPoint = new Point(x, y);
if (floatingContextMenu != null) {
floatingContextMenu.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (floatingContextMenu != null) {
floatingContextMenu.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (getSettings().getJavaScriptEnabled()) {
onScrollStopped();
} else {
onFloatingActionGlobalLayout(x, y);
}
}
}
});
addView(floatingContextMenu, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, x, y));
if (hasBeenRemovedAndRebuilt) {
sendOnCreateContextMenuEvent();
}
if (selectedTextTask != null) {
selectedTextTask.run();
}
}
actionMenu.clear();
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
mode.finish();
return true;
return actionMode;
}
public void onFloatingActionGlobalLayout(int x, int y) {
int maxWidth = getWidth();
int maxHeight = getHeight();
int width = floatingContextMenu.getWidth();
int height = floatingContextMenu.getHeight();
int curx = x - (width / 2);
if (curx < 0) {
curx = 0;
} else if (curx + width > maxWidth) {
curx = maxWidth - width;
}
// float size = 12 * scale;
float cury = y - (height * 1.5f);
if (cury < 0) {
cury = y + height;
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
clearFocus();
updateViewLayout(
floatingContextMenu,
new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, curx, ((int) cury) + getScrollY())
);
headlessHandler.post(new Runnable() {
@Override
public void run() {
if (floatingContextMenu != null) {
floatingContextMenu.setVisibility(View.VISIBLE);
floatingContextMenu.animate().alpha(1f).setDuration(100).setListener(null);
}
}
});
}
public void hideContextMenu() {
removeView(floatingContextMenu);
floatingContextMenu = null;
onHideContextMenu();
}
public void onHideContextMenu() {
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
channel.invokeMethod("onHideContextMenu", obj);
}
public void onScrollStopped() {
if (floatingContextMenu != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
adjustFloatingContextMenuPosition();
}
}
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void adjustFloatingContextMenuPosition() {
evaluateJavascript("(function(){" +
" var selection = window.getSelection();" +
" var rangeY = null;" +
" if (selection != null && selection.rangeCount > 0) {" +
" var range = selection.getRangeAt(0);" +
" var clientRect = range.getClientRects();" +
" if (clientRect.length > 0) {" +
" rangeY = clientRect[0].y;" +
" } else if (document.activeElement) {" +
" var boundingClientRect = document.activeElement.getBoundingClientRect();" +
" rangeY = boundingClientRect.y;" +
" }" +
" }" +
" return rangeY;" +
"})();", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
if (floatingContextMenu != null) {
if (value != null) {
int x = contextMenuPoint.x;
int y = (int) ((Float.parseFloat(value) * scale) + (floatingContextMenu.getHeight() / 3.5));
contextMenuPoint.y = y;
onFloatingActionGlobalLayout(x, y);
} else {
floatingContextMenu.setVisibility(View.VISIBLE);
floatingContextMenu.animate().alpha(1f).setDuration(100).setListener(null);
}
}
}
});
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void getSelectedText(final ValueCallback<String> resultCallback) {
evaluateJavascript(getSelectedTextJS, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
value = (value != null) ? value.substring(1, value.length() - 1) : null;
resultCallback.onReceiveValue(value);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public void getSelectedText(final MethodChannel.Result result) {
getSelectedText(new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
result.success(value);
}
});
}
@Override
public void dispose() {
super.dispose();
......@@ -1421,6 +1711,7 @@ final public class InAppWebView extends InputAwareWebView {
@Override
public void destroy() {
headlessHandler.removeCallbacksAndMessages(null);
super.destroy();
}
}
......@@ -42,6 +42,7 @@ public class InAppWebViewOptions implements Options {
public Boolean transparentBackground = false;
public Boolean disableVerticalScroll = false;
public Boolean disableHorizontalScroll = false;
public Boolean disableContextMenu = false;
public Integer textZoom = 100;
public Boolean clearSessionCache = false;
......@@ -165,6 +166,9 @@ public class InAppWebViewOptions implements Options {
case "disableHorizontalScroll":
disableHorizontalScroll = (Boolean) value;
break;
case "disableContextMenu":
disableContextMenu = (Boolean) value;
break;
case "textZoom":
textZoom = (Integer) value;
break;
......@@ -323,6 +327,7 @@ public class InAppWebViewOptions implements Options {
options.put("transparentBackground", transparentBackground);
options.put("disableVerticalScroll", disableVerticalScroll);
options.put("disableHorizontalScroll", disableHorizontalScroll);
options.put("disableContextMenu", disableContextMenu);
options.put("textZoom", textZoom);
options.put("clearSessionCache", clearSessionCache);
options.put("builtInZoomControls", builtInZoomControls);
......
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dip"/>
<solid android:color="#FFF"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
android:background="@drawable/floating_action_mode_shape"
android:elevation="4dp">
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"></LinearLayout>
</HorizontalScrollView>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:background="?android:attr/selectableItemBackground"
android:textColor="#000"
tools:text="Copy" />
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-11 15:01:02.801973","version":"1.17.0"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"android":[{"name":"connectivity","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity-0.4.8+5/","dependencies":[]},{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.4.4/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.7/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-3.3.0/","dependencies":[]}],"macos":[{"name":"connectivity_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/connectivity_macos-0.1.0+3/","dependencies":[]},{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+2/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]}],"date_created":"2020-05-21 03:31:36.578209","version":"1.17.0"}
\ No newline at end of file
......@@ -2,10 +2,11 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_TARGET=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example/test_driver/app.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
......@@ -11,12 +13,33 @@ class InAppWebViewExampleScreen extends StatefulWidget {
class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
InAppWebViewController webView;
ContextMenu contextMenu;
String url = "";
double progress = 0;
@override
void initState() {
super.initState();
contextMenu = ContextMenu(
onCreateContextMenu: (hitTestResult) async {
print("onCreateContextMenu");
print(hitTestResult.extra);
print(await webView.getSelectedText());
},
onHideContextMenu: () {
print("onHideContextMenu");
},
onContextMenuActionItemClicked: (contextMenuItemClicked) {
var id = (Platform.isAndroid) ? contextMenuItemClicked.androidId : contextMenuItemClicked.iosId;
print("onContextMenuActionItemClicked: " + id.toString() + " " + contextMenuItemClicked.title);
}
);
contextMenu.menuItems = [
ContextMenuItem(androidId: 1, iosId: "1", title: "Special", action: () async {
print("Menu item Special clicked!");
})
];
}
@override
......@@ -49,12 +72,13 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
decoration:
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
initialUrl: "s",
contextMenu: contextMenu,
initialUrl: "https://github.com/flutter",
// initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
debuggingEnabled: true
),
),
onWebViewCreated: (InAppWebViewController controller) {
......@@ -106,7 +130,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
setState(() {
this.url = url;
});
},
}
),
),
),
......
final environment = {"NODE_SERVER_IP":"192.168.1.20"};
\ No newline at end of file
final environment = {"NODE_SERVER_IP":"192.168.1.21"};
\ No newline at end of file
......@@ -38,11 +38,12 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
case "open":
let url = arguments!["url"] as! String
let options = arguments!["options"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
let uuidFallback: String = arguments!["uuidFallback"] as! String
let headersFallback = arguments!["headersFallback"] as! [String: String]
let optionsFallback = arguments!["optionsFallback"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
open(uuid: uuid, url: url, options: options, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, menuItemList: menuItemList, result: result)
let contextMenuFallback = arguments!["contextMenuFallback"] as! [String: Any]
open(uuid: uuid, url: url, options: options, menuItemList: menuItemList, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, contextMenuFallback: contextMenuFallback, result: result)
break
default:
result(FlutterMethodNotImplemented)
......@@ -50,7 +51,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
}
}
public func open(uuid: String, url: String, options: [String: Any?], uuidFallback: String?, headersFallback: [String: String], optionsFallback: [String: Any?], menuItemList: [[String: Any]], result: @escaping FlutterResult) {
public func open(uuid: String, url: String, options: [String: Any?], menuItemList: [[String: Any]], uuidFallback: String, headersFallback: [String: String], optionsFallback: [String: Any?], contextMenuFallback: [String: Any], result: @escaping FlutterResult) {
let absoluteUrl = URL(string: url)!.absoluteURL
if self.previousStatusBarStyle == -1 {
......@@ -105,7 +106,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
return
}
SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback!, url: url, options: optionsFallback, headers: headersFallback)
SwiftFlutterPlugin.instance!.inAppBrowserManager!.openUrl(uuid: uuidFallback, url: url, options: optionsFallback, headers: headersFallback, contextMenu: contextMenuFallback)
}
}
}
......@@ -38,12 +38,13 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
let initialData = args["initialData"] as? [String: String]
let initialHeaders = args["initialHeaders"] as? [String: String]
let initialOptions = args["initialOptions"] as! [String: Any?]
let contextMenu = args["contextMenu"] as? [String: Any]
let options = InAppWebViewOptions()
let _ = options.parse(options: initialOptions)
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options)
webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, channel: channel!)
webView = InAppWebView(frame: myView!.bounds, configuration: preWebviewConfiguration, IABController: nil, contextMenu: contextMenu, channel: channel!)
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.autoresizesSubviews = true
myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
......@@ -98,7 +99,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
deinit {
print("FlutterWebViewController - dealloc")
channel?.setMethodCallHandler(nil)
webView!.dispose()
webView?.dispose()
webView = nil
myView = nil
}
......@@ -373,7 +374,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
case "printCurrentPage":
if webView != nil {
webView!.printCurrentPage(printCompletionHandler: {(completed, error) in
if !completed, let _ = error {
if !completed, let err = error {
print(err.localizedDescription)
result(false)
return
}
......@@ -398,6 +400,32 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
case "hasOnlySecureContent":
result( (webView != nil) ? webView!.hasOnlySecureContent : nil )
break
case "getSelectedText":
if webView != nil {
webView!.getSelectedText { (value, error) in
if let err = error {
print(err.localizedDescription)
}
result(value)
}
}
else {
result(nil)
}
break
case "getHitTestResult":
if webView != nil {
webView!.getHitTestResult { (value, error) in
if let err = error {
print(err.localizedDescription)
}
result(value)
}
}
else {
result(nil)
}
break
default:
result(FlutterMethodNotImplemented)
break
......
......@@ -41,7 +41,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
let url = arguments!["url"] as! String
let options = arguments!["options"] as! [String: Any?]
let headers = arguments!["headers"] as! [String: String]
openUrl(uuid: uuid, url: url, options: options, headers: headers)
let contextMenu = arguments!["contextMenu"] as! [String: Any]
openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu)
result(true)
break
case "openFile":
......@@ -56,7 +57,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
}
let options = arguments!["options"] as! [String: Any?]
let headers = arguments!["headers"] as! [String: String]
openUrl(uuid: uuid, url: url, options: options, headers: headers)
let contextMenu = arguments!["contextMenu"] as! [String: Any]
openUrl(uuid: uuid, url: url, options: options, headers: headers, contextMenu: contextMenu)
result(true)
break
case "openData":
......@@ -65,7 +67,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
let mimeType = arguments!["mimeType"] as! String
let encoding = arguments!["encoding"] as! String
let baseUrl = arguments!["baseUrl"] as! String
openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl)
let contextMenu = arguments!["contextMenu"] as! [String: Any]
openData(uuid: uuid, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl, contextMenu: contextMenu)
result(true)
break
case "openWithSystemBrowser":
......@@ -110,7 +113,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
return webViewController
}
public func openUrl(uuid: String, url: String, options: [String: Any?], headers: [String: String]) {
public func openUrl(uuid: String, url: String, options: [String: Any?], headers: [String: String], contextMenu: [String: Any]) {
let absoluteUrl = URL(string: url)!.absoluteURL
let webViewController = prepareInAppBrowserWebViewController(options: options)
......@@ -119,6 +122,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
webViewController.tmpWindow = tmpWindow
webViewController.initURL = absoluteUrl
webViewController.initHeaders = headers
webViewController.contextMenu = contextMenu
if webViewController.isHidden {
webViewController.view.isHidden = true
......@@ -137,7 +141,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
}
}
public func openData(uuid: String, options: [String: Any?], data: String, mimeType: String, encoding: String, baseUrl: String) {
public func openData(uuid: String, options: [String: Any?], data: String, mimeType: String, encoding: String, baseUrl: String, contextMenu: [String: Any]) {
let webViewController = prepareInAppBrowserWebViewController(options: options)
webViewController.uuid = uuid
......@@ -146,6 +150,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
webViewController.initMimeType = mimeType
webViewController.initEncoding = encoding
webViewController.initBaseUrl = baseUrl
webViewController.contextMenu = contextMenu
if webViewController.isHidden {
webViewController.view.isHidden = true
......
......@@ -17,7 +17,7 @@ typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, B
public class InAppWebView_IBWrapper: InAppWebView {
required init(coder: NSCoder) {
let config = WKWebViewConfiguration()
super.init(frame: .zero, configuration: config, IABController: nil, channel: nil)
super.init(frame: .zero, configuration: config, IABController: nil, contextMenu: nil, channel: nil)
self.translatesAutoresizingMaskIntoConstraints = false
}
}
......@@ -46,6 +46,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
var webView: InAppWebView!
var channel: FlutterMethodChannel?
var initURL: URL?
var contextMenu: [String: Any]?
var tmpWindow: UIWindow?
var browserOptions: InAppBrowserOptions?
var webViewOptions: InAppWebViewOptions?
......@@ -281,6 +282,32 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
case "hasOnlySecureContent":
result(webView.hasOnlySecureContent)
break
case "getSelectedText":
if webView != nil {
webView!.getSelectedText { (value, error) in
if let err = error {
print(err.localizedDescription)
}
result(value)
}
}
else {
result(nil)
}
break
case "getHitTestResult":
if webView != nil {
webView!.getHitTestResult { (value, error) in
if let err = error {
print(err.localizedDescription)
}
result(value)
}
}
else {
result(nil)
}
break
default:
result(FlutterMethodNotImplemented)
break
......@@ -290,7 +317,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
public override func viewWillAppear(_ animated: Bool) {
if !viewPrepared {
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions)
self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, channel: channel!)
self.webView = InAppWebView(frame: .zero, configuration: preWebviewConfiguration, IABController: self, contextMenu: contextMenu, channel: channel!)
self.containerWebView.addSubview(self.webView)
prepareConstraints()
prepareWebView()
......@@ -685,7 +712,8 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0)
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
onExit()
channel!.setMethodCallHandler(nil)
channel?.setMethodCallHandler(nil)
channel = nil
}
public func onBrowserCreated() {
......
......@@ -721,6 +721,22 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) {
}
"""
let getSelectedTextJS = """
(function(){
var txt;
if (window.getSelection) {
txt = window.getSelection().toString();
} else if (window.document.getSelection) {
txt = window.document.getSelection().toString();
} else if (window.document.selection) {
txt = window.document.selection.createRange().text;
}
return txt;
})();
"""
var SharedLastTouchPointTimestamp: [InAppWebView: Int64] = [:]
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate {
var IABController: InAppBrowserWebViewController?
......@@ -736,15 +752,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
// This flag is used to block the "shouldOverrideUrlLoading" event when the WKWebView is loading the first time,
// in order to have the same behavior as Android
var activateShouldOverrideUrlLoading = false
var contextMenu: [String: Any]?
// https://github.com/mozilla-mobile/firefox-ios/blob/50531a7e9e4d459fb11d4fcb7d4322e08103501f/Client/Frontend/Browser/ContextMenuHelper.swift
fileprivate var nativeHighlightLongPressRecognizer: UILongPressGestureRecognizer?
var longPressRecognizer: UILongPressGestureRecognizer?
var lastLongPressTouchPoint: CGPoint?
var lastTouchPoint: CGPoint?
var lastTouchPointTimestamp = Int64(Date().timeIntervalSince1970 * 1000)
var contextMenuIsShowing = false
var customIMPs: [IMP] = []
init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, channel: FlutterMethodChannel?) {
init(frame: CGRect, configuration: WKWebViewConfiguration, IABController: InAppBrowserWebViewController?, contextMenu: [String: Any]?, channel: FlutterMethodChannel?) {
super.init(frame: frame, configuration: configuration)
self.channel = channel
self.contextMenu = contextMenu
self.IABController = IABController
uiDelegate = self
navigationDelegate = self
......@@ -757,21 +782,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
/*
public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if let _ = sender as? UIMenuController {
print(action)
// disable/hide UIMenuController
return false
}
return super.canPerformAction(action, withSender: sender)
}*/
// @objc func uiMenuViewControllerDidShowMenu() {
// print("MENU\n\n")
// }
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
......@@ -831,15 +841,71 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
touchLocation.y -= self.scrollView.contentInset.top
touchLocation.x /= self.scrollView.zoomScale
touchLocation.y /= self.scrollView.zoomScale
lastLongPressTouchPoint = touchLocation
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in
if error != nil {
print("Long press gesture recognizer error: \(error?.localizedDescription)")
print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")")
} else {
self.onLongPressHitTestResult(hitTestResult: value as! [String: Any?])
}
})
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
lastTouchPoint = point
lastTouchPointTimestamp = Int64(Date().timeIntervalSince1970 * 1000)
SharedLastTouchPointTimestamp[self] = lastTouchPointTimestamp
UIMenuController.shared.menuItems = []
if let menu = self.contextMenu {
if let menuItems = menu["menuItems"] as? [[String : Any]] {
for menuItem in menuItems {
let id = menuItem["iosId"] as! String
let title = menuItem["title"] as! String
let targetMethodName = "onContextMenuActionItemClicked-" + String(self.hash) + "-" + id
if !self.responds(to: Selector(targetMethodName)) {
let customAction: () -> Void = {
let arguments: [String: Any?] = [
"iosId": id,
"androidId": nil,
"title": title
]
self.channel?.invokeMethod("onContextMenuActionItemClicked", arguments: arguments)
}
let castedCustomAction: AnyObject = unsafeBitCast(customAction as @convention(block) () -> Void, to: AnyObject.self)
let swizzledImplementation = imp_implementationWithBlock(castedCustomAction)
class_addMethod(InAppWebView.self, Selector(targetMethodName), swizzledImplementation, nil)
self.customIMPs.append(swizzledImplementation)
}
let item = UIMenuItem(title: title, action: Selector(targetMethodName))
UIMenuController.shared.menuItems!.append(item)
}
}
}
return super.hitTest(point, with: event)
}
public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if let _ = sender as? UIMenuController {
if self.options?.disableContextMenu == true {
return false
}
if contextMenuIsShowing, !action.description.starts(with: "onContextMenuActionItemClicked-") {
let id = action.description.compactMap({ $0.asciiValue?.description }).joined()
let arguments: [String: Any?] = [
"iosId": id,
"androidId": nil,
"title": action.description
]
self.channel?.invokeMethod("onContextMenuActionItemClicked", arguments: arguments)
}
}
return super.canPerformAction(action, withSender: sender)
}
public func prepare() {
......@@ -854,12 +920,19 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
forKeyPath: #keyPath(WKWebView.url),
options: [.new, .old],
context: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(onCreateContextMenu),
name: UIMenuController.willShowMenuNotification,
object: nil)
// NotificationCenter.default.addObserver(
// self,
// selector: #selector(uiMenuViewControllerDidShowMenu),
// name: UIMenuController.didShowMenuNotification,
// object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(onHideContextMenu),
name: UIMenuController.didHideMenuNotification,
object: nil)
configuration.userContentController = WKUserContentController()
configuration.preferences = WKPreferences()
......@@ -998,10 +1071,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
scrollView.decelerationRate = getDecelerationRate(type: (options?.decelerationRate)!)
scrollView.alwaysBounceVertical = !(options?.alwaysBounceVertical)!
scrollView.alwaysBounceHorizontal = !(options?.alwaysBounceHorizontal)!
scrollView.scrollsToTop = !(options?.scrollsToTop)!
scrollView.isPagingEnabled = !(options?.isPagingEnabled)!
scrollView.alwaysBounceVertical = (options?.alwaysBounceVertical)!
scrollView.alwaysBounceHorizontal = (options?.alwaysBounceHorizontal)!
scrollView.scrollsToTop = (options?.scrollsToTop)!
scrollView.isPagingEnabled = (options?.isPagingEnabled)!
scrollView.maximumZoomScale = CGFloat((options?.maximumZoomScale)!)
scrollView.minimumZoomScale = CGFloat((options?.minimumZoomScale)!)
......@@ -1010,6 +1083,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if (options?.clearCache)! {
clearCache()
}
}
@available(iOS 10.0, *)
......@@ -1076,6 +1151,47 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return configuration
}
@objc func onCreateContextMenu() {
let mapSorted = SharedLastTouchPointTimestamp.sorted { $0.value > $1.value }
if (mapSorted.first?.key != self) {
return
}
contextMenuIsShowing = true
var arguments: [String: Any?] = [
"hitTestResult": nil
]
if let lastLongPressTouhLocation = lastLongPressTouchPoint {
if configuration.preferences.javaScriptEnabled {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastLongPressTouhLocation.x),\(lastLongPressTouhLocation.y))", completionHandler: {(value, error) in
if error != nil {
print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")")
} else {
let hitTestResult = value as! [String: Any?]
arguments["hitTestResult"] = hitTestResult
self.channel?.invokeMethod("onCreateContextMenu", arguments: arguments)
}
})
} else {
channel?.invokeMethod("onCreateContextMenu", arguments: arguments)
}
} else {
channel?.invokeMethod("onCreateContextMenu", arguments: arguments)
}
}
@objc func onHideContextMenu() {
if contextMenuIsShowing == false {
return
}
contextMenuIsShowing = false
let arguments: [String: Any] = [:]
channel?.invokeMethod("onHideContextMenu", arguments: arguments)
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.estimatedProgress) {
......@@ -1363,8 +1479,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}
}
if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
clearCache()
}
......@@ -2080,6 +2194,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
// public func webView(_ webView: WKWebView,
// contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo,
// completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
// print("contextMenuConfigurationForElement")
// let actionProvider: UIContextMenuActionProvider = { _ in
// let editMenu = UIMenu(title: "Edit...", children: [
// UIAction(title: "Copy") { action in
......@@ -2099,76 +2214,82 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
// let contextMenuConfiguration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
// //completionHandler(contextMenuConfiguration)
// completionHandler(nil)
// onContextMenuConfigurationForElement(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
// if result is FlutterError {
// print((result as! FlutterError).message ?? "")
// }
// else if (result as? NSObject) == FlutterMethodNotImplemented {
// completionHandler(nil)
// }
// else {
// var response: [String: Any]
// if let r = result {
// response = r as! [String: Any]
// var action = response["action"] as? Int
// action = action != nil ? action : 0;
// switch action {
// case 0:
// break
// case 1:
// break
// default:
// completionHandler(nil)
// }
// return;
// }
// completionHandler(nil)
// }
// }*/)
//// onContextMenuConfigurationForElement(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
//// if result is FlutterError {
//// print((result as! FlutterError).message ?? "")
//// }
//// else if (result as? NSObject) == FlutterMethodNotImplemented {
//// completionHandler(nil)
//// }
//// else {
//// var response: [String: Any]
//// if let r = result {
//// response = r as! [String: Any]
//// var action = response["action"] as? Int
//// action = action != nil ? action : 0;
//// switch action {
//// case 0:
//// break
//// case 1:
//// break
//// default:
//// completionHandler(nil)
//// }
//// return;
//// }
//// completionHandler(nil)
//// }
//// }*/)
// }
//
////
// @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView,
// contextMenuDidEndForElement elementInfo: WKContextMenuElementInfo) {
// onContextMenuDidEndForElement(linkURL: elementInfo.linkURL?.absoluteString)
// print("contextMenuDidEndForElement")
// print(elementInfo)
// //onContextMenuDidEndForElement(linkURL: elementInfo.linkURL?.absoluteString)
// }
//
// @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView,
// contextMenuForElement elementInfo: WKContextMenuElementInfo,
// willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
// onWillCommitWithAnimator(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
// if result is FlutterError {
// print((result as! FlutterError).message ?? "")
// }
// else if (result as? NSObject) == FlutterMethodNotImplemented {
//
// }
// else {
// var response: [String: Any]
// if let r = result {
// response = r as! [String: Any]
// var action = response["action"] as? Int
// action = action != nil ? action : 0;
//// switch action {
//// case 0:
//// break
//// case 1:
//// break
//// default:
// print("willCommitWithAnimator")
// print(elementInfo)
//// onWillCommitWithAnimator(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
//// if result is FlutterError {
//// print((result as! FlutterError).message ?? "")
//// }
//// else if (result as? NSObject) == FlutterMethodNotImplemented {
////
//// }
// return;
// }
//
// }
// }*/)
//// }
//// else {
//// var response: [String: Any]
//// if let r = result {
//// response = r as! [String: Any]
//// var action = response["action"] as? Int
//// action = action != nil ? action : 0;
////// switch action {
////// case 0:
////// break
////// case 1:
////// break
////// default:
//////
////// }
//// return;
//// }
////
//// }
//// }*/)
// }
//
// @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView,
// contextMenuWillPresentForElement elementInfo: WKContextMenuElementInfo) {
// onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString)
// print("contextMenuWillPresentForElement")
// print(elementInfo.linkURL)
// //onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString)
// }
public func onLoadStart(url: String) {
......@@ -2489,6 +2610,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return Float(scrollView.zoomScale)
}
public func getSelectedText(completionHandler: @escaping (Any?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled {
evaluateJavaScript(getSelectedTextJS, completionHandler: completionHandler)
} else {
completionHandler(nil, nil)
}
}
public func getHitTestResult(completionHandler: @escaping (Any?, Error?) -> Void) {
if configuration.preferences.javaScriptEnabled, let lastTouchLocation = lastTouchPoint {
self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(lastTouchLocation.x),\(lastTouchLocation.y))", completionHandler: {(value, error) in
completionHandler(value, error)
})
} else {
completionHandler(nil, nil)
}
}
public func dispose() {
stopLoading()
configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog")
......@@ -2504,14 +2643,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if #available(iOS 11.0, *) {
configuration.userContentController.removeAllContentRuleLists()
}
longPressRecognizer!.removeTarget(self, action: #selector(longPressGestureDetected))
longPressRecognizer!.delegate = nil
self.scrollView.removeGestureRecognizer(longPressRecognizer!)
NotificationCenter.default.removeObserver(self)
for imp in customIMPs {
imp_removeBlock(imp)
}
longPressRecognizer?.removeTarget(self, action: #selector(longPressGestureDetected))
longPressRecognizer?.delegate = nil
scrollView.removeGestureRecognizer(longPressRecognizer!)
uiDelegate = nil
navigationDelegate = nil
scrollView.delegate = nil
IABController?.webView = nil
isPausedTimersCompletionHandler = nil
channel = nil
SharedLastTouchPointTimestamp.removeValue(forKey: self)
super.removeFromSuperview()
}
......
......@@ -33,6 +33,7 @@ public class InAppWebViewOptions: Options {
var transparentBackground = false
var disableVerticalScroll = false
var disableHorizontalScroll = false
var disableContextMenu = false
var disallowOverScroll = false
var enableViewportScale = false
......
......@@ -31,11 +31,10 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
// let arguments = call.arguments as? NSDictionary
switch call.method {
case "close":
close()
close(result: result)
break
default:
result(FlutterMethodNotImplemented)
......@@ -67,7 +66,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: (safariOptions?.transitionStyle)!)!
}
func close() {
func close(result: FlutterResult?) {
dismiss(animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in
......@@ -75,11 +74,14 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
UIApplication.shared.delegate?.window??.makeKeyAndVisible()
self.onChromeSafariBrowserClosed()
self.dispose()
if result != nil {
result!(true)
}
})
}
public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
close()
close(result: nil)
}
public func safariViewController(_ controller: SFSafariViewController,
......
......@@ -35,3 +35,4 @@ export 'src/webview_options.dart';
export 'src/content_blocker.dart';
export 'src/http_auth_credentials_database.dart';
export 'src/web_storage_manager.dart';
export 'src/context_menu.dart';
......@@ -61,6 +61,8 @@ class ChromeSafariBrowser {
///[headersFallback]: The additional header of the [InAppBrowser] instance fallback to be used in the HTTP request for this URL, specified as a map from name to value.
///
///[optionsFallback]: Options used by the [InAppBrowser] instance fallback.
///
///[contextMenuFallback]: Context Menu used by the [InAppBrowser] instance fallback.
Future<void> open(
{@required String url,
ChromeSafariBrowserClassOptions options,
......@@ -81,11 +83,12 @@ class ChromeSafariBrowser {
args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url);
args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('menuItemList', () => menuItemList);
args.putIfAbsent('uuidFallback',
() => (browserFallback != null) ? browserFallback.uuid : '');
args.putIfAbsent('headersFallback', () => headersFallback);
args.putIfAbsent('optionsFallback', () => optionsFallback?.toMap() ?? {});
args.putIfAbsent('menuItemList', () => menuItemList);
args.putIfAbsent('contextMenuFallback', () => browserFallback?.contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('open', args);
this._isOpened = true;
}
......
import 'package:flutter/foundation.dart';
import 'package:flutter_inappwebview/src/webview.dart';
import 'types.dart';
///Class that represents the WebView context menu. It used by [WebView.contextMenu].
///
///**NOTE**: To make it work properly on Android, JavaScript should be enabled!
class ContextMenu {
///Event fired when the context menu for this WebView is being built.
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(InAppWebViewHitTestResult hitTestResult) onCreateContextMenu;
///Event fired when the context menu for this WebView is being hidden.
final void Function() onHideContextMenu;
///Event fired when a context menu item has been clicked.
///
///[contextMenuItemClicked] represents the [ContextMenuItem] clicked.
final void Function(ContextMenuItem contextMenuItemClicked) onContextMenuActionItemClicked;
///List of the custom [ContextMenuItem].
List<ContextMenuItem> menuItems = List();
ContextMenu({
this.menuItems,
this.onCreateContextMenu,
this.onHideContextMenu,
this.onContextMenuActionItemClicked
});
Map<String, dynamic> toMap() {
return {
"menuItems": menuItems.map((menuItem) => menuItem?.toMap()).toList()
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
}
///Class that represent an item of the [ContextMenu].
class ContextMenuItem {
///Android menu item ID.
int androidId;
///iOS menu item ID.
String iosId;
///Menu item title.
String title;
///Menu item action that will be called when an user clicks on it.
Function() action;
ContextMenuItem({@required this.androidId, @required this.iosId, @required this.title, this.action});
Map<String, dynamic> toMap() {
return {
"androidId": androidId,
"iosId": iosId,
"title": title
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
}
import 'package:flutter/services.dart';
import 'context_menu.dart';
import 'types.dart';
import 'webview.dart';
import 'in_app_webview_controller.dart';
......@@ -16,46 +17,48 @@ class HeadlessInAppWebView implements WebView {
///WebView Controller that can be used to access the [InAppWebViewController] API.
InAppWebViewController webViewController;
HeadlessInAppWebView(
{this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions}) {
HeadlessInAppWebView({
this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions,
this.contextMenu
}) {
uuid = uuidGenerator.v4();
webViewController = new InAppWebViewController(uuid, this);
}
......@@ -83,7 +86,8 @@ class HeadlessInAppWebView implements WebView {
'initialFile': this.initialFile,
'initialData': this.initialData?.toMap(),
'initialHeaders': this.initialHeaders,
'initialOptions': this.initialOptions?.toMap()
'initialOptions': this.initialOptions?.toMap(),
'contextMenu': this.contextMenu?.toMap() ?? {}
});
await _sharedChannel.invokeMethod('createHeadlessWebView', args);
}
......@@ -130,6 +134,9 @@ class HeadlessInAppWebView implements WebView {
@override
final InAppWebViewGroupOptions initialOptions;
@override
final ContextMenu contextMenu;
@override
final String initialUrl;
......@@ -210,7 +217,7 @@ class HeadlessInAppWebView implements WebView {
@override
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult;
@override
final void Function(InAppWebViewController controller, String url) onPrint;
......
......@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'context_menu.dart';
import 'in_app_webview_controller.dart';
import 'webview_options.dart';
......@@ -12,7 +13,12 @@ import 'types.dart';
///This class uses the native WebView of the platform.
///The [webViewController] field can be used to access the [InAppWebViewController] API.
class InAppBrowser {
///Browser's UUID
String uuid;
///Context menu used by the browser
ContextMenu contextMenu;
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>();
bool _isOpened = false;
......@@ -67,6 +73,7 @@ class InAppBrowser {
args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openUrl', args);
}
......@@ -117,6 +124,7 @@ class InAppBrowser {
args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openFile', args);
}
......@@ -146,6 +154,7 @@ class InAppBrowser {
args.putIfAbsent('encoding', () => encoding);
args.putIfAbsent('baseUrl', () => baseUrl);
args.putIfAbsent('historyUrl', () => androidHistoryUrl);
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openData', args);
}
......@@ -429,7 +438,7 @@ class InAppBrowser {
///Event fired when an HTML element of the webview has been clicked and held.
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
void onLongPressHitTestResult(LongPressHitTestResult hitTestResult) {}
void onLongPressHitTestResult(InAppWebViewHitTestResult hitTestResult) {}
///Event fired when the WebView notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
......
......@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
import 'context_menu.dart';
import 'webview.dart';
import 'types.dart';
import 'in_app_webview_controller.dart';
......@@ -38,6 +39,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.initialData,
this.initialHeaders = const {},
@required this.initialOptions,
this.contextMenu,
this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
......@@ -112,6 +114,9 @@ class InAppWebView extends StatefulWidget implements WebView {
@override
final String initialUrl;
@override
final ContextMenu contextMenu;
@override
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
......@@ -189,7 +194,7 @@ class InAppWebView extends StatefulWidget implements WebView {
@override
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult;
@override
final void Function(InAppWebViewController controller, String url) onPrint;
......@@ -258,7 +263,8 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions?.toMap() ?? {}
'initialOptions': widget.initialOptions?.toMap() ?? {},
'contextMenu': widget.contextMenu?.toMap() ?? {}
},
creationParamsCodec: const StandardMessageCodec(),
);
......@@ -291,7 +297,8 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions?.toMap() ?? {}
'initialOptions': widget.initialOptions?.toMap() ?? {},
'contextMenu': widget.contextMenu?.toMap() ?? {}
},
creationParamsCodec: const StandardMessageCodec(),
);
......
......@@ -346,12 +346,12 @@ class InAppWebViewController {
_webview.iosOnWebContentProcessDidTerminate(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnWebContentProcessDidTerminate();
return null;
break;
case "onDidCommit":
if (_webview != null && _webview.iosOnDidCommit != null)
_webview.iosOnDidCommit(this);
else if (_inAppBrowser != null) _inAppBrowser.iosOnDidCommit();
return null;
break;
case "onDidReceiveServerRedirectForProvisionalNavigation":
if (_webview != null &&
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation !=
......@@ -359,21 +359,80 @@ class InAppWebViewController {
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation(this);
else if (_inAppBrowser != null)
_inAppBrowser.iosOnDidReceiveServerRedirectForProvisionalNavigation();
return null;
break;
case "onLongPressHitTestResult":
Map<dynamic, dynamic> hitTestResultMap =
call.arguments["hitTestResult"];
LongPressHitTestResultType type = LongPressHitTestResultType.fromValue(
InAppWebViewHitTestResultType type = InAppWebViewHitTestResultType.fromValue(
hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"];
LongPressHitTestResult hitTestResult =
new LongPressHitTestResult(type: type, extra: extra);
InAppWebViewHitTestResult hitTestResult = InAppWebViewHitTestResult(type: type, extra: extra);
if (_webview != null && _webview.onLongPressHitTestResult != null)
_webview.onLongPressHitTestResult(this, hitTestResult);
else if (_inAppBrowser != null)
_inAppBrowser.onLongPressHitTestResult(hitTestResult);
break;
case "onCreateContextMenu":
ContextMenu contextMenu;
if (_webview != null && _webview.contextMenu != null) {
contextMenu = _webview.contextMenu;
} else if (_inAppBrowser != null && _inAppBrowser.contextMenu != null) {
contextMenu = _inAppBrowser.contextMenu;
}
if (contextMenu != null && contextMenu.onCreateContextMenu != null) {
Map<dynamic, dynamic> hitTestResultMap =
call.arguments["hitTestResult"];
InAppWebViewHitTestResultType type = InAppWebViewHitTestResultType.fromValue(
hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"];
InAppWebViewHitTestResult hitTestResult = InAppWebViewHitTestResult(type: type, extra: extra);
contextMenu.onCreateContextMenu(hitTestResult);
}
break;
case "onHideContextMenu":
ContextMenu contextMenu;
if (_webview != null && _webview.contextMenu != null) {
contextMenu = _webview.contextMenu;
} else if (_inAppBrowser != null && _inAppBrowser.contextMenu != null) {
contextMenu = _inAppBrowser.contextMenu;
}
if (contextMenu != null && contextMenu.onHideContextMenu != null) {
contextMenu.onHideContextMenu();
}
break;
case "onContextMenuActionItemClicked":
ContextMenu contextMenu;
if (_webview != null && _webview.contextMenu != null) {
contextMenu = _webview.contextMenu;
} else if (_inAppBrowser != null && _inAppBrowser.contextMenu != null) {
contextMenu = _inAppBrowser.contextMenu;
}
if (contextMenu != null) {
int androidId = call.arguments["androidId"];
String iosId = call.arguments["iosId"];
String title = call.arguments["title"];
ContextMenuItem menuItemClicked = ContextMenuItem(androidId: androidId, iosId: iosId, title: title, action: null);
for (var menuItem in contextMenu.menuItems) {
if ((Platform.isAndroid && menuItem.androidId == androidId) ||
(Platform.isIOS && menuItem.iosId == iosId)) {
menuItemClicked = menuItem;
menuItem?.action();
break;
}
}
if (contextMenu.onContextMenuActionItemClicked != null) {
contextMenu.onContextMenuActionItemClicked(menuItemClicked);
}
}
break;
case "onCallJsHandler":
String handlerName = call.arguments["handlerName"];
// decode args to json
......@@ -1198,7 +1257,7 @@ class InAppWebViewController {
return await _channel.invokeMethod('getContentHeight', args);
}
///Gets the height of the HTML content.
///Performs a zoom operation in this WebView.
///
///[zoomFactor] represents the zoom factor to apply. On Android, the zoom factor will be clamped to the Webview's zoom limits and, also, this value must be in the range 0.01 to 100.0 inclusive.
///
......@@ -1215,6 +1274,27 @@ class InAppWebViewController {
return await _channel.invokeMethod('getScale', args);
}
///Gets the selected text.
///
///**NOTE**: This method is implemented with using JavaScript.
///Available only on Android 19+.
Future<String> getSelectedText() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel.invokeMethod('getSelectedText', args);
}
///Gets the hit result for hitting an HTML elements.
///
///**NOTE**: On iOS it is implemented using JavaScript.
Future<InAppWebViewHitTestResult> getHitTestResult() async {
Map<String, dynamic> args = <String, dynamic>{};
var hitTestResultMap = await _channel.invokeMethod('getHitTestResult', args);
InAppWebViewHitTestResultType type = InAppWebViewHitTestResultType.fromValue(
hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"];
return InAppWebViewHitTestResult(type: type, extra: extra);
}
///Gets the default user agent.
static Future<String> getDefaultUserAgent() async {
Map<String, dynamic> args = <String, dynamic>{};
......
......@@ -2297,13 +2297,13 @@ class IOSWKWebsiteDataRecord {
}
}
///Class representing the [LongPressHitTestResult] type.
class LongPressHitTestResultType {
///Class representing the [InAppWebViewHitTestResult] type.
class InAppWebViewHitTestResultType {
final int _value;
const LongPressHitTestResultType._internal(this._value);
static LongPressHitTestResultType fromValue(int value) {
const InAppWebViewHitTestResultType._internal(this._value);
static InAppWebViewHitTestResultType fromValue(int value) {
if (value != null && [0, 2, 3, 4, 5, 7, 8, 9].contains(value))
return LongPressHitTestResultType._internal(value);
return InAppWebViewHitTestResultType._internal(value);
return null;
}
......@@ -2331,22 +2331,22 @@ class LongPressHitTestResultType {
}
}
///Default [LongPressHitTestResult], where the target is unknown.
static const UNKNOWN_TYPE = const LongPressHitTestResultType._internal(0);
///[LongPressHitTestResult] for hitting a phone number.
static const PHONE_TYPE = const LongPressHitTestResultType._internal(2);
///[LongPressHitTestResult] for hitting a map address.
static const GEO_TYPE = const LongPressHitTestResultType._internal(3);
///[LongPressHitTestResult] for hitting an email address.
static const EMAIL_TYPE = const LongPressHitTestResultType._internal(4);
///[LongPressHitTestResult] for hitting an HTML::img tag.
static const IMAGE_TYPE = const LongPressHitTestResultType._internal(5);
///[LongPressHitTestResult] for hitting a HTML::a tag with src=http.
static const SRC_ANCHOR_TYPE = const LongPressHitTestResultType._internal(7);
///[LongPressHitTestResult] for hitting a HTML::a tag with src=http + HTML::img.
static const SRC_IMAGE_ANCHOR_TYPE = const LongPressHitTestResultType._internal(8);
///[LongPressHitTestResult] for hitting an edit text area.
static const EDIT_TEXT_TYPE = const LongPressHitTestResultType._internal(9);
///Default [InAppWebViewHitTestResult], where the target is unknown.
static const UNKNOWN_TYPE = const InAppWebViewHitTestResultType._internal(0);
///[InAppWebViewHitTestResult] for hitting a phone number.
static const PHONE_TYPE = const InAppWebViewHitTestResultType._internal(2);
///[InAppWebViewHitTestResult] for hitting a map address.
static const GEO_TYPE = const InAppWebViewHitTestResultType._internal(3);
///[InAppWebViewHitTestResult] for hitting an email address.
static const EMAIL_TYPE = const InAppWebViewHitTestResultType._internal(4);
///[InAppWebViewHitTestResult] for hitting an HTML::img tag.
static const IMAGE_TYPE = const InAppWebViewHitTestResultType._internal(5);
///[InAppWebViewHitTestResult] for hitting a HTML::a tag with src=http.
static const SRC_ANCHOR_TYPE = const InAppWebViewHitTestResultType._internal(7);
///[InAppWebViewHitTestResult] for hitting a HTML::a tag with src=http + HTML::img.
static const SRC_IMAGE_ANCHOR_TYPE = const InAppWebViewHitTestResultType._internal(8);
///[InAppWebViewHitTestResult] for hitting an edit text area.
static const EDIT_TEXT_TYPE = const InAppWebViewHitTestResultType._internal(9);
bool operator ==(value) => value == _value;
......@@ -2354,12 +2354,12 @@ class LongPressHitTestResultType {
int get hashCode => _value.hashCode;
}
///Class that represents the hit result for hitting an HTML elements. Used by [onLongPressHitTestResult] event.
class LongPressHitTestResult {
///Class that represents the hit result for hitting an HTML elements.
class InAppWebViewHitTestResult {
///The type of the hit test result.
LongPressHitTestResultType type;
InAppWebViewHitTestResultType type;
///Additional type-dependant information about the result.
String extra;
LongPressHitTestResult({this.type, this.extra});
InAppWebViewHitTestResult({this.type, this.extra});
}
import 'package:flutter_inappwebview/src/context_menu.dart';
import 'types.dart';
import 'in_app_webview_controller.dart';
......@@ -233,7 +235,7 @@ abstract class WebView {
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult;
InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult;
///Event fired when the webview notifies that a loading URL has been flagged by Safe Browsing.
///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
......@@ -308,44 +310,49 @@ abstract class WebView {
///Initial options that will be used.
final InAppWebViewGroupOptions initialOptions;
WebView(
{this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions});
///Context menu which contains custom menu items to be shown when [ContextMenu] is presented.
final ContextMenu contextMenu;
WebView({
this.onWebViewCreated,
this.onLoadStart,
this.onLoadStop,
this.onLoadError,
this.onLoadHttpError,
this.onProgressChanged,
this.onConsoleMessage,
this.shouldOverrideUrlLoading,
this.onLoadResource,
this.onScrollChanged,
this.onDownloadStart,
this.onLoadResourceCustomScheme,
this.onCreateWindow,
this.onJsAlert,
this.onJsConfirm,
this.onJsPrompt,
this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest,
this.onFindResultReceived,
this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange,
this.onAjaxProgress,
this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory,
this.onPrint,
this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl,
this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions,
this.contextMenu
});
}
\ No newline at end of file
......@@ -128,6 +128,9 @@ class InAppWebViewOptions
///Set to `true` to disable horizontal scroll. The default value is `false`.
bool disableHorizontalScroll;
///Set to `true` to disable context menu. The default value is `false`.
bool disableContextMenu;
InAppWebViewOptions(
{this.useShouldOverrideUrlLoading = false,
this.useOnLoadResource = false,
......@@ -152,7 +155,8 @@ class InAppWebViewOptions
this.cacheEnabled = true,
this.transparentBackground = false,
this.disableVerticalScroll = false,
this.disableHorizontalScroll = false}) {
this.disableHorizontalScroll = false,
this.disableContextMenu = false}) {
if (this.minimumFontSize == null)
this.minimumFontSize = Platform.isAndroid ? 8 : 0;
assert(!this.resourceCustomSchemes.contains("http") &&
......@@ -189,7 +193,8 @@ class InAppWebViewOptions
"cacheEnabled": cacheEnabled,
"transparentBackground": transparentBackground,
"disableVerticalScroll": disableVerticalScroll,
"disableHorizontalScroll": disableHorizontalScroll
"disableHorizontalScroll": disableHorizontalScroll,
"disableContextMenu": disableContextMenu
};
}
......@@ -234,6 +239,7 @@ class InAppWebViewOptions
options.transparentBackground = map["transparentBackground"];
options.disableVerticalScroll = map["disableVerticalScroll"];
options.disableHorizontalScroll = map["disableHorizontalScroll"];
options.disableContextMenu = map["disableContextMenu"];
return options;
}
}
......
name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview or open an in-app browser window.
version: 3.1.0
version: 3.2.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment