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 `Confirmation dialog (onbeforeunload) displayed after popped from webview page` [#337](https://github.com/pichillilorenzo/flutter_inappwebview/issues/337)
- Fixed `CookieManager.setCookie` `expiresDate` option - 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 ## 3.1.0
......
...@@ -26,6 +26,8 @@ Also, you need to add `<uses-permission android:name="android.permission.INTERNE ...@@ -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`. 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 ### 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: 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 ...@@ -67,7 +69,8 @@ First, add `flutter_inappwebview` as a [dependency in your pubspec.yaml file](ht
## Usage ## Usage
Classes: 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. - [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. - [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. - [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. ...@@ -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`. 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. Use `InAppWebViewController` to control the WebView instance.
Example: Example:
```dart ```dart
...@@ -133,7 +138,7 @@ Future main() async { ...@@ -133,7 +138,7 @@ Future main() async {
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@override @override
_MyAppState createState() => new _MyAppState(); _MyAppState createState() => new _MyAppState();
}z }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
...@@ -296,6 +301,8 @@ Screenshots: ...@@ -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. * `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. * `printCurrentPage`: Prints the current page.
* `getScale`: Gets the current scale of this WebView. * `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. * `static getDefaultUserAgent`: Gets the default user agent.
##### `InAppWebViewController` Android-specific methods ##### `InAppWebViewController` Android-specific methods
...@@ -315,6 +322,7 @@ Android-specific methods can be called using the `InAppWebViewController.android ...@@ -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. 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. * `reloadFromOrigin`: Reloads the current page, performing end-to-end revalidation using cache-validating conditionals if possible.
##### About the JavaScript handler ##### About the JavaScript handler
...@@ -389,6 +397,7 @@ Instead, on the `onLoadStop` WebView event, you can use `callHandler` directly: ...@@ -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`. * `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`. * `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`. * `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 ##### `InAppWebView` Android-specific options
...@@ -499,6 +508,166 @@ Event names that starts with `android` or `ios` are events platform-specific. ...@@ -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). * `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). * `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 ### `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. 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. ...@@ -136,6 +136,7 @@ public class ChromeCustomTabsActivity extends Activity implements MethodChannel.
myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); myIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
Shared.activity.startActivity(myIntent); Shared.activity.startActivity(myIntent);
result.success(true);
break; break;
default: default:
result.notImplemented(); result.notImplemented();
......
...@@ -40,11 +40,12 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -40,11 +40,12 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
{ {
String url = (String) call.argument("url"); String url = (String) call.argument("url");
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options"); 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"); String uuidFallback = (String) call.argument("uuidFallback");
Map<String, String> headersFallback = (Map<String, String>) call.argument("headersFallback"); Map<String, String> headersFallback = (Map<String, String>) call.argument("headersFallback");
HashMap<String, Object> optionsFallback = (HashMap<String, Object>) call.argument("optionsFallback"); HashMap<String, Object> optionsFallback = (HashMap<String, Object>) call.argument("optionsFallback");
List<HashMap<String, Object>> menuItemList = (List<HashMap<String, Object>>) call.argument("menuItemList"); HashMap<String, Object> contextMenuFallback = (HashMap<String, Object>) call.argument("contextMenuFallback");
open(activity, uuid, url, options, uuidFallback, headersFallback, optionsFallback, menuItemList, result); open(activity, uuid, url, options, menuItemList, uuidFallback, headersFallback, optionsFallback, contextMenuFallback, result);
} }
break; break;
default: default:
...@@ -52,8 +53,8 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -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, 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, List<HashMap<String, Object>> menuItemList, MethodChannel.Result result) { Map<String, String> headersFallback, HashMap<String, Object> optionsFallback, HashMap<String, Object> contextMenuFallback, MethodChannel.Result result) {
Intent intent = null; Intent intent = null;
Bundle extras = new Bundle(); Bundle extras = new Bundle();
...@@ -62,9 +63,11 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl ...@@ -62,9 +63,11 @@ public class ChromeSafariBrowserManager implements MethodChannel.MethodCallHandl
extras.putBoolean("isData", false); extras.putBoolean("isData", false);
extras.putString("uuid", uuid); extras.putString("uuid", uuid);
extras.putSerializable("options", options); extras.putSerializable("options", options);
extras.putSerializable("headers", (Serializable) headersFallback);
extras.putSerializable("menuItemList", (Serializable) menuItemList); extras.putSerializable("menuItemList", (Serializable) menuItemList);
extras.putSerializable("headers", (Serializable) headersFallback);
extras.putSerializable("contextMenu", (Serializable) contextMenuFallback);
if (CustomTabActivityHelper.isAvailable(activity)) { if (CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, ChromeCustomTabsActivity.class); intent = new Intent(activity, ChromeCustomTabsActivity.class);
} }
......
...@@ -72,6 +72,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -72,6 +72,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
fromActivity = b.getString("fromActivity"); fromActivity = b.getString("fromActivity");
HashMap<String, Object> optionsMap = (HashMap<String, Object>) b.getSerializable("options"); HashMap<String, Object> optionsMap = (HashMap<String, Object>) b.getSerializable("options");
HashMap<String, Object> contextMenu = (HashMap<String, Object>) b.getSerializable("contextMenu");
options = new InAppBrowserOptions(); options = new InAppBrowserOptions();
options.parse(optionsMap); options.parse(optionsMap);
...@@ -79,6 +80,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -79,6 +80,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
InAppWebViewOptions webViewOptions = new InAppWebViewOptions(); InAppWebViewOptions webViewOptions = new InAppWebViewOptions();
webViewOptions.parse(optionsMap); webViewOptions.parse(optionsMap);
webView.options = webViewOptions; webView.options = webViewOptions;
webView.contextMenu = contextMenu;
actionBar = getSupportActionBar(); actionBar = getSupportActionBar();
...@@ -223,7 +225,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -223,7 +225,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
result.success(isHidden); result.success(isHidden);
break; break;
case "takeScreenshot": case "takeScreenshot":
result.success(takeScreenshot()); takeScreenshot(result);
break; break;
case "setOptions": case "setOptions":
{ {
...@@ -330,6 +332,16 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -330,6 +332,16 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
case "getScale": case "getScale":
result.success(getScale()); result.success(getScale());
break; 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: default:
result.notImplemented(); result.notImplemented();
} }
...@@ -619,24 +631,11 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -619,24 +631,11 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
close(null); close(null);
} }
public byte[] takeScreenshot() { public void takeScreenshot(MethodChannel.Result result) {
if (webView != null) { if (webView != null)
Picture picture = webView.capturePicture(); webView.takeScreenshot(result);
Bitmap b = Bitmap.createBitmap( webView.getWidth(), else
webView.getHeight(), Bitmap.Config.ARGB_8888); result.success(null);
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 setOptions(InAppBrowserOptions newOptions, HashMap<String, Object> newOptionsMap) { public void setOptions(InAppBrowserOptions newOptions, HashMap<String, Object> newOptionsMap) {
...@@ -843,6 +842,25 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha ...@@ -843,6 +842,25 @@ public class InAppBrowserActivity extends AppCompatActivity implements MethodCha
return null; 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() { public void dispose() {
channel.setMethodCallHandler(null); channel.setMethodCallHandler(null);
if (webView != null) { if (webView != null) {
......
...@@ -70,7 +70,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -70,7 +70,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
String url = (String) call.argument("url"); String url = (String) call.argument("url");
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options"); HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
Map<String, String> headers = (Map<String, String>) call.argument("headers"); 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); result.success(true);
break; break;
...@@ -86,7 +87,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -86,7 +87,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
} }
HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options"); HashMap<String, Object> options = (HashMap<String, Object>) call.argument("options");
Map<String, String> headers = (Map<String, String>) call.argument("headers"); 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); result.success(true);
break; break;
...@@ -98,7 +100,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -98,7 +100,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
String encoding = (String) call.argument("encoding"); String encoding = (String) call.argument("encoding");
String baseUrl = (String) call.argument("baseUrl"); String baseUrl = (String) call.argument("baseUrl");
String historyUrl = (String) call.argument("historyUrl"); 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); result.success(true);
break; break;
...@@ -189,7 +192,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -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(); Bundle extras = new Bundle();
extras.putString("fromActivity", activity.getClass().getName()); extras.putString("fromActivity", activity.getClass().getName());
extras.putString("url", url); extras.putString("url", url);
...@@ -197,10 +200,11 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -197,10 +200,11 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
extras.putString("uuid", uuid); extras.putString("uuid", uuid);
extras.putSerializable("options", options); extras.putSerializable("options", options);
extras.putSerializable("headers", (Serializable) headers); extras.putSerializable("headers", (Serializable) headers);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
startInAppBrowserActivity(activity, extras); 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(); Bundle extras = new Bundle();
extras.putBoolean("isData", true); extras.putBoolean("isData", true);
extras.putString("uuid", uuid); extras.putString("uuid", uuid);
...@@ -210,6 +214,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler { ...@@ -210,6 +214,7 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
extras.putString("encoding", encoding); extras.putString("encoding", encoding);
extras.putString("baseUrl", baseUrl); extras.putString("baseUrl", baseUrl);
extras.putString("historyUrl", historyUrl); extras.putString("historyUrl", historyUrl);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
startInAppBrowserActivity(activity, extras); startInAppBrowserActivity(activity, extras);
} }
......
...@@ -4,6 +4,8 @@ import android.app.Activity; ...@@ -4,6 +4,8 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.View; import android.view.View;
...@@ -49,15 +51,16 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -49,15 +51,16 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
displayListenerProxy.onPreWebViewInitialization(displayManager); displayListenerProxy.onPreWebViewInitialization(displayManager);
String initialUrl = (String) params.get("initialUrl"); String initialUrl = (String) params.get("initialUrl");
String initialFile = (String) params.get("initialFile"); final String initialFile = (String) params.get("initialFile");
Map<String, String> initialData = (Map<String, String>) params.get("initialData"); final Map<String, String> initialData = (Map<String, String>) params.get("initialData");
Map<String, String> initialHeaders = (Map<String, String>) params.get("initialHeaders"); final Map<String, String> initialHeaders = (Map<String, String>) params.get("initialHeaders");
HashMap<String, Object> initialOptions = (HashMap<String, Object>) params.get("initialOptions"); HashMap<String, Object> initialOptions = (HashMap<String, Object>) params.get("initialOptions");
HashMap<String, Object> contextMenu = (HashMap<String, Object>) params.get("contextMenu");
InAppWebViewOptions options = new InAppWebViewOptions(); InAppWebViewOptions options = new InAppWebViewOptions();
options.parse(initialOptions); 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); displayListenerProxy.onPostWebViewInitialization(displayManager);
// fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/182 // fix https://github.com/pichillilorenzo/flutter_inappwebview/issues/182
...@@ -86,16 +89,23 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -86,16 +89,23 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
} }
} }
if (initialData != null) { final String finalInitialUrl = initialUrl;
String data = initialData.get("data"); Handler handler = new Handler(Looper.getMainLooper());
String mimeType = initialData.get("mimeType"); handler.post(new Runnable() {
String encoding = initialData.get("encoding"); @Override
String baseUrl = initialData.get("baseUrl"); public void run() {
String historyUrl = initialData.get("historyUrl"); if (initialData != null) {
webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); String data = initialData.get("data");
} String mimeType = initialData.get("mimeType");
else String encoding = initialData.get("encoding");
webView.loadUrl(initialUrl, initialHeaders); 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) { if (containerView == null && id instanceof String) {
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
...@@ -381,6 +391,24 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { ...@@ -381,6 +391,24 @@ public class FlutterWebView implements PlatformView, MethodCallHandler {
case "getScale": case "getScale":
result.success((webView != null) ? webView.getUpdatedScale() : null); result.success((webView != null) ? webView.getUpdatedScale() : null);
break; 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: default:
result.notImplemented(); result.notImplemented();
} }
......
...@@ -13,8 +13,16 @@ import android.print.PrintDocumentAdapter; ...@@ -13,8 +13,16 @@ import android.print.PrintDocumentAdapter;
import android.print.PrintManager; import android.print.PrintManager;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; 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.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -22,6 +30,9 @@ import android.webkit.WebBackForwardList; ...@@ -22,6 +30,9 @@ import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem; import android.webkit.WebHistoryItem;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebStorage; import android.webkit.WebStorage;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
...@@ -31,6 +42,7 @@ import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerHan ...@@ -31,6 +42,7 @@ import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerHan
import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerTrigger; import com.pichillilorenzo.flutter_inappwebview.ContentBlocker.ContentBlockerTrigger;
import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappwebview.InAppBrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappwebview.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.Util;
...@@ -64,6 +76,18 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -64,6 +76,18 @@ final public class InAppWebView extends InputAwareWebView {
int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB int okHttpClientCacheSize = 10 * 1024 * 1024; // 10MB
public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler(); public ContentBlockerHandler contentBlockerHandler = new ContentBlockerHandler();
public Pattern regexToCancelSubFramesLoadingCompiled; 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) {" + static final String consoleLogJS = "(function(console) {" +
" var oldLogs = {" + " var oldLogs = {" +
...@@ -507,6 +531,18 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -507,6 +531,18 @@ final public class InAppWebView extends InputAwareWebView {
" };" + " };" +
"})(window.fetch);"; "})(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) { public InAppWebView(Context context) {
super(context); super(context);
} }
...@@ -519,7 +555,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -519,7 +555,7 @@ final public class InAppWebView extends InputAwareWebView {
super(context, attrs, defaultStyle); 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); super(context, containerView);
if (obj instanceof InAppBrowserActivity) if (obj instanceof InAppBrowserActivity)
this.inAppBrowserActivity = (InAppBrowserActivity) obj; this.inAppBrowserActivity = (InAppBrowserActivity) obj;
...@@ -528,7 +564,8 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -528,7 +564,8 @@ final public class InAppWebView extends InputAwareWebView {
this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel; this.channel = (this.inAppBrowserActivity != null) ? this.inAppBrowserActivity.channel : this.flutterWebView.channel;
this.id = id; this.id = id;
this.options = options; this.options = options;
//Shared.activity.registerForContextMenu(this); this.contextMenu = contextMenu;
Shared.activity.registerForContextMenu(this);
} }
@Override @Override
...@@ -684,12 +721,64 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -684,12 +721,64 @@ final public class InAppWebView extends InputAwareWebView {
setVerticalScrollBarEnabled(!options.disableVerticalScroll); setVerticalScrollBarEnabled(!options.disableVerticalScroll);
setHorizontalScrollBarEnabled(!options.disableHorizontalScroll); 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_downX;
float m_downY; float m_downY;
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
checkScrollStoppedTask.run();
}
if (options.disableHorizontalScroll && options.disableVerticalScroll) { if (options.disableHorizontalScroll && options.disableVerticalScroll) {
return (event.getAction() == MotionEvent.ACTION_MOVE); return (event.getAction() == MotionEvent.ACTION_MOVE);
} }
...@@ -738,13 +827,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -738,13 +827,7 @@ final public class InAppWebView extends InputAwareWebView {
}); });
} }
private Point lastTouch; private MotionEvent lastMotionEvent = null;
@Override
public boolean onTouchEvent(MotionEvent ev) {
lastTouch = new Point((int) ev.getX(), (int) ev.getY()) ;
return super.onTouchEvent(ev);
}
public void setIncognito(boolean enabled) { public void setIncognito(boolean enabled) {
WebSettings settings = getSettings(); WebSettings settings = getSettings();
...@@ -883,8 +966,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -883,8 +966,7 @@ final public class InAppWebView extends InputAwareWebView {
} }
public void takeScreenshot(final MethodChannel.Result result) { public void takeScreenshot(final MethodChannel.Result result) {
Handler handler = new Handler(Looper.getMainLooper()); headlessHandler.post(new Runnable() {
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
int height = (int) (getContentHeight() * scale + 0.5); int height = (int) (getContentHeight() * scale + 0.5);
...@@ -1192,8 +1274,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -1192,8 +1274,7 @@ final public class InAppWebView extends InputAwareWebView {
scriptToInject = String.format(jsWrapper, jsonSourceString); scriptToInject = String.format(jsWrapper, jsonSourceString);
} }
final String finalScriptToInject = scriptToInject; final String finalScriptToInject = scriptToInject;
Handler handler = new Handler(Looper.getMainLooper()); headlessHandler.post(new Runnable() {
handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
...@@ -1269,6 +1350,11 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -1269,6 +1350,11 @@ final public class InAppWebView extends InputAwareWebView {
int x = (int) (l/scale); int x = (int) (l/scale);
int y = (int) (t/scale); int y = (int) (t/scale);
if (floatingContextMenu != null) {
floatingContextMenu.setAlpha(0f);
floatingContextMenu.setVisibility(View.GONE);
}
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null) if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid); obj.put("uuid", inAppBrowserActivity.uuid);
...@@ -1351,69 +1437,273 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -1351,69 +1437,273 @@ final public class InAppWebView extends InputAwareWebView {
public Float getUpdatedScale() { public Float getUpdatedScale() {
return scale; return scale;
} }
/*
@Override @Override
public void onCreateContextMenu(ContextMenu menu) { public void onCreateContextMenu(ContextMenu menu) {
Log.d(LOG_TAG, getHitTestResult().getType() + ""); super.onCreateContextMenu(menu);
String extra = getHitTestResult().getExtra(); sendOnCreateContextMenuEvent();
//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());
}
} }
private Integer mActionMode; private void sendOnCreateContextMenuEvent() {
private CustomActionModeCallback mActionModeCallback; 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 @Override
public ActionMode startActionMode(ActionMode.Callback callback, int mode) { public boolean onTouchEvent(MotionEvent ev) {
Log.d(LOG_TAG, "startActionMode"); lastTouch = new Point((int) ev.getX(), (int) ev.getY());
ViewParent parent = getParent(); return super.onTouchEvent(ev);
if (parent == null || mActionMode != null) { }
return null;
@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 (actionMode == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return null;
mActionMode = ActionMode.TYPE_FLOATING;
//return Shared.activity.getWindow().getDecorView().startActionMode(mActionModeCallback, mActionMode);
return parent.startActionModeForChild(this, mActionModeCallback, mActionMode);
} else {
return parent.startActionModeForChild(this, mActionModeCallback);
} }
}
private class CustomActionModeCallback implements ActionMode.Callback { Menu actionMenu = actionMode.getMenu();
if (options.disableContextMenu) {
actionMenu.clear();
return actionMode;
}
@Override floatingContextMenu = (LinearLayout) LayoutInflater.from(this.getContext())
public boolean onCreateActionMode(ActionMode mode, Menu menu) { .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 final int x = (lastTouch != null) ? lastTouch.x : 0;
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { final int y = (lastTouch != null) ? lastTouch.y : 0;
return false; 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 return actionMode;
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { }
mode.finish();
return true; 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 updateViewLayout(
@Override floatingContextMenu,
public void onDestroyActionMode(ActionMode mode) { new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, curx, ((int) cury) + getScrollY())
clearFocus(); );
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 @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
...@@ -1421,6 +1711,7 @@ final public class InAppWebView extends InputAwareWebView { ...@@ -1421,6 +1711,7 @@ final public class InAppWebView extends InputAwareWebView {
@Override @Override
public void destroy() { public void destroy() {
headlessHandler.removeCallbacksAndMessages(null);
super.destroy(); super.destroy();
} }
} }
...@@ -42,6 +42,7 @@ public class InAppWebViewOptions implements Options { ...@@ -42,6 +42,7 @@ public class InAppWebViewOptions implements Options {
public Boolean transparentBackground = false; public Boolean transparentBackground = false;
public Boolean disableVerticalScroll = false; public Boolean disableVerticalScroll = false;
public Boolean disableHorizontalScroll = false; public Boolean disableHorizontalScroll = false;
public Boolean disableContextMenu = false;
public Integer textZoom = 100; public Integer textZoom = 100;
public Boolean clearSessionCache = false; public Boolean clearSessionCache = false;
...@@ -165,6 +166,9 @@ public class InAppWebViewOptions implements Options { ...@@ -165,6 +166,9 @@ public class InAppWebViewOptions implements Options {
case "disableHorizontalScroll": case "disableHorizontalScroll":
disableHorizontalScroll = (Boolean) value; disableHorizontalScroll = (Boolean) value;
break; break;
case "disableContextMenu":
disableContextMenu = (Boolean) value;
break;
case "textZoom": case "textZoom":
textZoom = (Integer) value; textZoom = (Integer) value;
break; break;
...@@ -323,6 +327,7 @@ public class InAppWebViewOptions implements Options { ...@@ -323,6 +327,7 @@ public class InAppWebViewOptions implements Options {
options.put("transparentBackground", transparentBackground); options.put("transparentBackground", transparentBackground);
options.put("disableVerticalScroll", disableVerticalScroll); options.put("disableVerticalScroll", disableVerticalScroll);
options.put("disableHorizontalScroll", disableHorizontalScroll); options.put("disableHorizontalScroll", disableHorizontalScroll);
options.put("disableContextMenu", disableContextMenu);
options.put("textZoom", textZoom); options.put("textZoom", textZoom);
options.put("clearSessionCache", clearSessionCache); options.put("clearSessionCache", clearSessionCache);
options.put("builtInZoomControls", builtInZoomControls); 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"} {"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 \ No newline at end of file
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
# This is a generated file; do not edit or check into version control. # This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter" export "FLUTTER_ROOT=/Users/lorenzopichilli/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/lorenzopichilli/Desktop/flutter_inappwebview/example" 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 "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter" export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios" export "FLUTTER_FRAMEWORK_DIR=/Users/lorenzopichilli/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1" export "FLUTTER_BUILD_NUMBER=1"
export "TRACK_WIDGET_CREATION=true"
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
...@@ -11,12 +13,33 @@ class InAppWebViewExampleScreen extends StatefulWidget { ...@@ -11,12 +13,33 @@ class InAppWebViewExampleScreen extends StatefulWidget {
class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> { class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
InAppWebViewController webView; InAppWebViewController webView;
ContextMenu contextMenu;
String url = ""; String url = "";
double progress = 0; double progress = 0;
@override @override
void initState() { void initState() {
super.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 @override
...@@ -49,12 +72,13 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> { ...@@ -49,12 +72,13 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
decoration: decoration:
BoxDecoration(border: Border.all(color: Colors.blueAccent)), BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView( child: InAppWebView(
initialUrl: "s", contextMenu: contextMenu,
initialUrl: "https://github.com/flutter",
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
debuggingEnabled: true, debuggingEnabled: true
), ),
), ),
onWebViewCreated: (InAppWebViewController controller) { onWebViewCreated: (InAppWebViewController controller) {
...@@ -106,7 +130,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> { ...@@ -106,7 +130,7 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
setState(() { setState(() {
this.url = url; this.url = url;
}); });
}, }
), ),
), ),
), ),
......
final environment = {"NODE_SERVER_IP":"192.168.1.20"}; final environment = {"NODE_SERVER_IP":"192.168.1.21"};
\ No newline at end of file \ No newline at end of file
...@@ -38,11 +38,12 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -38,11 +38,12 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
case "open": case "open":
let url = arguments!["url"] as! String let url = arguments!["url"] as! String
let options = arguments!["options"] as! [String: Any?] let options = arguments!["options"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]]
let uuidFallback: String = arguments!["uuidFallback"] as! String let uuidFallback: String = arguments!["uuidFallback"] as! String
let headersFallback = arguments!["headersFallback"] as! [String: String] let headersFallback = arguments!["headersFallback"] as! [String: String]
let optionsFallback = arguments!["optionsFallback"] as! [String: Any?] let optionsFallback = arguments!["optionsFallback"] as! [String: Any?]
let menuItemList = arguments!["menuItemList"] as! [[String: Any]] let contextMenuFallback = arguments!["contextMenuFallback"] as! [String: Any]
open(uuid: uuid, url: url, options: options, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, menuItemList: menuItemList, result: result) open(uuid: uuid, url: url, options: options, menuItemList: menuItemList, uuidFallback: uuidFallback, headersFallback: headersFallback, optionsFallback: optionsFallback, contextMenuFallback: contextMenuFallback, result: result)
break break
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
...@@ -50,7 +51,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -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 let absoluteUrl = URL(string: url)!.absoluteURL
if self.previousStatusBarStyle == -1 { if self.previousStatusBarStyle == -1 {
...@@ -105,7 +106,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin { ...@@ -105,7 +106,7 @@ public class ChromeSafariBrowserManager: NSObject, FlutterPlugin {
return 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 ...@@ -38,12 +38,13 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
let initialData = args["initialData"] as? [String: String] let initialData = args["initialData"] as? [String: String]
let initialHeaders = args["initialHeaders"] as? [String: String] let initialHeaders = args["initialHeaders"] as? [String: String]
let initialOptions = args["initialOptions"] as! [String: Any?] let initialOptions = args["initialOptions"] as! [String: Any?]
let contextMenu = args["contextMenu"] as? [String: Any]
let options = InAppWebViewOptions() let options = InAppWebViewOptions()
let _ = options.parse(options: initialOptions) let _ = options.parse(options: initialOptions)
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: options) 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] webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.autoresizesSubviews = true myView!.autoresizesSubviews = true
myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
...@@ -98,7 +99,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor ...@@ -98,7 +99,7 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
deinit { deinit {
print("FlutterWebViewController - dealloc") print("FlutterWebViewController - dealloc")
channel?.setMethodCallHandler(nil) channel?.setMethodCallHandler(nil)
webView!.dispose() webView?.dispose()
webView = nil webView = nil
myView = nil myView = nil
} }
...@@ -373,7 +374,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor ...@@ -373,7 +374,8 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
case "printCurrentPage": case "printCurrentPage":
if webView != nil { if webView != nil {
webView!.printCurrentPage(printCompletionHandler: {(completed, error) in webView!.printCurrentPage(printCompletionHandler: {(completed, error) in
if !completed, let _ = error { if !completed, let err = error {
print(err.localizedDescription)
result(false) result(false)
return return
} }
...@@ -398,6 +400,32 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor ...@@ -398,6 +400,32 @@ public class FlutterWebViewController: FlutterMethodCallDelegate, FlutterPlatfor
case "hasOnlySecureContent": case "hasOnlySecureContent":
result( (webView != nil) ? webView!.hasOnlySecureContent : nil ) result( (webView != nil) ? webView!.hasOnlySecureContent : nil )
break 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: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
break break
......
...@@ -41,7 +41,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -41,7 +41,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
let url = arguments!["url"] as! String let url = arguments!["url"] as! String
let options = arguments!["options"] as! [String: Any?] let options = arguments!["options"] as! [String: Any?]
let headers = arguments!["headers"] as! [String: String] 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) result(true)
break break
case "openFile": case "openFile":
...@@ -56,7 +57,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -56,7 +57,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
} }
let options = arguments!["options"] as! [String: Any?] let options = arguments!["options"] as! [String: Any?]
let headers = arguments!["headers"] as! [String: String] 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) result(true)
break break
case "openData": case "openData":
...@@ -65,7 +67,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -65,7 +67,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
let mimeType = arguments!["mimeType"] as! String let mimeType = arguments!["mimeType"] as! String
let encoding = arguments!["encoding"] as! String let encoding = arguments!["encoding"] as! String
let baseUrl = arguments!["baseUrl"] 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) result(true)
break break
case "openWithSystemBrowser": case "openWithSystemBrowser":
...@@ -110,7 +113,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -110,7 +113,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
return webViewController 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 absoluteUrl = URL(string: url)!.absoluteURL
let webViewController = prepareInAppBrowserWebViewController(options: options) let webViewController = prepareInAppBrowserWebViewController(options: options)
...@@ -119,6 +122,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -119,6 +122,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
webViewController.tmpWindow = tmpWindow webViewController.tmpWindow = tmpWindow
webViewController.initURL = absoluteUrl webViewController.initURL = absoluteUrl
webViewController.initHeaders = headers webViewController.initHeaders = headers
webViewController.contextMenu = contextMenu
if webViewController.isHidden { if webViewController.isHidden {
webViewController.view.isHidden = true webViewController.view.isHidden = true
...@@ -137,7 +141,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -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) let webViewController = prepareInAppBrowserWebViewController(options: options)
webViewController.uuid = uuid webViewController.uuid = uuid
...@@ -146,6 +150,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin { ...@@ -146,6 +150,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
webViewController.initMimeType = mimeType webViewController.initMimeType = mimeType
webViewController.initEncoding = encoding webViewController.initEncoding = encoding
webViewController.initBaseUrl = baseUrl webViewController.initBaseUrl = baseUrl
webViewController.contextMenu = contextMenu
if webViewController.isHidden { if webViewController.isHidden {
webViewController.view.isHidden = true webViewController.view.isHidden = true
......
...@@ -17,7 +17,7 @@ typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, B ...@@ -17,7 +17,7 @@ typealias NewerClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, B
public class InAppWebView_IBWrapper: InAppWebView { public class InAppWebView_IBWrapper: InAppWebView {
required init(coder: NSCoder) { required init(coder: NSCoder) {
let config = WKWebViewConfiguration() 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 self.translatesAutoresizingMaskIntoConstraints = false
} }
} }
...@@ -46,6 +46,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS ...@@ -46,6 +46,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
var webView: InAppWebView! var webView: InAppWebView!
var channel: FlutterMethodChannel? var channel: FlutterMethodChannel?
var initURL: URL? var initURL: URL?
var contextMenu: [String: Any]?
var tmpWindow: UIWindow? var tmpWindow: UIWindow?
var browserOptions: InAppBrowserOptions? var browserOptions: InAppBrowserOptions?
var webViewOptions: InAppWebViewOptions? var webViewOptions: InAppWebViewOptions?
...@@ -281,6 +282,32 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS ...@@ -281,6 +282,32 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
case "hasOnlySecureContent": case "hasOnlySecureContent":
result(webView.hasOnlySecureContent) result(webView.hasOnlySecureContent)
break 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: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
break break
...@@ -290,7 +317,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS ...@@ -290,7 +317,7 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
public override func viewWillAppear(_ animated: Bool) { public override func viewWillAppear(_ animated: Bool) {
if !viewPrepared { if !viewPrepared {
let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(options: webViewOptions) 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) self.containerWebView.addSubview(self.webView)
prepareConstraints() prepareConstraints()
prepareWebView() prepareWebView()
...@@ -685,7 +712,8 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS ...@@ -685,7 +712,8 @@ public class InAppBrowserWebViewController: UIViewController, FlutterPlugin, UIS
tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0) tmpWindow?.windowLevel = UIWindow.Level(rawValue: 0.0)
UIApplication.shared.delegate?.window??.makeKeyAndVisible() UIApplication.shared.delegate?.window??.makeKeyAndVisible()
onExit() onExit()
channel!.setMethodCallHandler(nil) channel?.setMethodCallHandler(nil)
channel = nil
} }
public func onBrowserCreated() { public func onBrowserCreated() {
......
...@@ -721,6 +721,22 @@ window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint = function(x, y) { ...@@ -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 { public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate {
var IABController: InAppBrowserWebViewController? var IABController: InAppBrowserWebViewController?
...@@ -736,15 +752,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -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, // 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 // in order to have the same behavior as Android
var activateShouldOverrideUrlLoading = false var activateShouldOverrideUrlLoading = false
var contextMenu: [String: Any]?
// https://github.com/mozilla-mobile/firefox-ios/blob/50531a7e9e4d459fb11d4fcb7d4322e08103501f/Client/Frontend/Browser/ContextMenuHelper.swift // https://github.com/mozilla-mobile/firefox-ios/blob/50531a7e9e4d459fb11d4fcb7d4322e08103501f/Client/Frontend/Browser/ContextMenuHelper.swift
fileprivate var nativeHighlightLongPressRecognizer: UILongPressGestureRecognizer? fileprivate var nativeHighlightLongPressRecognizer: UILongPressGestureRecognizer?
var longPressRecognizer: 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) super.init(frame: frame, configuration: configuration)
self.channel = channel self.channel = channel
self.contextMenu = contextMenu
self.IABController = IABController self.IABController = IABController
uiDelegate = self uiDelegate = self
navigationDelegate = self navigationDelegate = self
...@@ -757,21 +782,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -757,21 +782,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)! 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 { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true return true
...@@ -831,15 +841,71 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -831,15 +841,71 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
touchLocation.y -= self.scrollView.contentInset.top touchLocation.y -= self.scrollView.contentInset.top
touchLocation.x /= self.scrollView.zoomScale touchLocation.x /= self.scrollView.zoomScale
touchLocation.y /= 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 self.evaluateJavaScript("window.\(JAVASCRIPT_BRIDGE_NAME)._findElementsAtPoint(\(touchLocation.x),\(touchLocation.y))", completionHandler: {(value, error) in
if error != nil { if error != nil {
print("Long press gesture recognizer error: \(error?.localizedDescription)") print("Long press gesture recognizer error: \(error?.localizedDescription ?? "")")
} else { } else {
self.onLongPressHitTestResult(hitTestResult: value as! [String: Any?]) 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() { public func prepare() {
...@@ -854,12 +920,19 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -854,12 +920,19 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
forKeyPath: #keyPath(WKWebView.url), forKeyPath: #keyPath(WKWebView.url),
options: [.new, .old], options: [.new, .old],
context: nil) context: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(onCreateContextMenu),
name: UIMenuController.willShowMenuNotification,
object: nil)
// NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
// self, self,
// selector: #selector(uiMenuViewControllerDidShowMenu), selector: #selector(onHideContextMenu),
// name: UIMenuController.didShowMenuNotification, name: UIMenuController.didHideMenuNotification,
// object: nil) object: nil)
configuration.userContentController = WKUserContentController() configuration.userContentController = WKUserContentController()
configuration.preferences = WKPreferences() configuration.preferences = WKPreferences()
...@@ -998,10 +1071,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -998,10 +1071,10 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)! scrollView.showsHorizontalScrollIndicator = (options?.horizontalScrollBarEnabled)!
scrollView.decelerationRate = getDecelerationRate(type: (options?.decelerationRate)!) scrollView.decelerationRate = getDecelerationRate(type: (options?.decelerationRate)!)
scrollView.alwaysBounceVertical = !(options?.alwaysBounceVertical)! scrollView.alwaysBounceVertical = (options?.alwaysBounceVertical)!
scrollView.alwaysBounceHorizontal = !(options?.alwaysBounceHorizontal)! scrollView.alwaysBounceHorizontal = (options?.alwaysBounceHorizontal)!
scrollView.scrollsToTop = !(options?.scrollsToTop)! scrollView.scrollsToTop = (options?.scrollsToTop)!
scrollView.isPagingEnabled = !(options?.isPagingEnabled)! scrollView.isPagingEnabled = (options?.isPagingEnabled)!
scrollView.maximumZoomScale = CGFloat((options?.maximumZoomScale)!) scrollView.maximumZoomScale = CGFloat((options?.maximumZoomScale)!)
scrollView.minimumZoomScale = CGFloat((options?.minimumZoomScale)!) scrollView.minimumZoomScale = CGFloat((options?.minimumZoomScale)!)
...@@ -1010,6 +1083,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -1010,6 +1083,8 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if (options?.clearCache)! { if (options?.clearCache)! {
clearCache() clearCache()
} }
} }
@available(iOS 10.0, *) @available(iOS 10.0, *)
...@@ -1076,6 +1151,47 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -1076,6 +1151,47 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return configuration 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?, override public func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == #keyPath(WKWebView.estimatedProgress) { if keyPath == #keyPath(WKWebView.estimatedProgress) {
...@@ -1363,8 +1479,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -1363,8 +1479,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
} }
} }
if newOptionsMap["clearCache"] != nil && newOptions.clearCache { if newOptionsMap["clearCache"] != nil && newOptions.clearCache {
clearCache() clearCache()
} }
...@@ -2080,6 +2194,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -2080,6 +2194,7 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
// public func webView(_ webView: WKWebView, // public func webView(_ webView: WKWebView,
// contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo, // contextMenuConfigurationForElement elementInfo: WKContextMenuElementInfo,
// completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) { // completionHandler: @escaping (UIContextMenuConfiguration?) -> Void) {
// print("contextMenuConfigurationForElement")
// let actionProvider: UIContextMenuActionProvider = { _ in // let actionProvider: UIContextMenuActionProvider = { _ in
// let editMenu = UIMenu(title: "Edit...", children: [ // let editMenu = UIMenu(title: "Edit...", children: [
// UIAction(title: "Copy") { action in // UIAction(title: "Copy") { action in
...@@ -2099,76 +2214,82 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -2099,76 +2214,82 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
// let contextMenuConfiguration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider) // let contextMenuConfiguration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
// //completionHandler(contextMenuConfiguration) // //completionHandler(contextMenuConfiguration)
// completionHandler(nil) // completionHandler(nil)
// onContextMenuConfigurationForElement(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in //// onContextMenuConfigurationForElement(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
// if result is FlutterError { //// if result is FlutterError {
// print((result as! FlutterError).message ?? "") //// print((result as! FlutterError).message ?? "")
// } //// }
// else if (result as? NSObject) == FlutterMethodNotImplemented { //// else if (result as? NSObject) == FlutterMethodNotImplemented {
// completionHandler(nil) //// completionHandler(nil)
// } //// }
// else { //// else {
// var response: [String: Any] //// var response: [String: Any]
// if let r = result { //// if let r = result {
// response = r as! [String: Any] //// response = r as! [String: Any]
// var action = response["action"] as? Int //// var action = response["action"] as? Int
// action = action != nil ? action : 0; //// action = action != nil ? action : 0;
// switch action { //// switch action {
// case 0: //// case 0:
// break //// break
// case 1: //// case 1:
// break //// break
// default: //// default:
// completionHandler(nil) //// completionHandler(nil)
// } //// }
// return; //// return;
// } //// }
// completionHandler(nil) //// completionHandler(nil)
// } //// }
// }*/) //// }*/)
// } // }
// ////
// @available(iOS 13.0, *) // @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView, // public func webView(_ webView: WKWebView,
// contextMenuDidEndForElement elementInfo: WKContextMenuElementInfo) { // contextMenuDidEndForElement elementInfo: WKContextMenuElementInfo) {
// onContextMenuDidEndForElement(linkURL: elementInfo.linkURL?.absoluteString) // print("contextMenuDidEndForElement")
// print(elementInfo)
// //onContextMenuDidEndForElement(linkURL: elementInfo.linkURL?.absoluteString)
// } // }
// //
// @available(iOS 13.0, *) // @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView, // public func webView(_ webView: WKWebView,
// contextMenuForElement elementInfo: WKContextMenuElementInfo, // contextMenuForElement elementInfo: WKContextMenuElementInfo,
// willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) { // willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
// onWillCommitWithAnimator(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in // print("willCommitWithAnimator")
// if result is FlutterError { // print(elementInfo)
// print((result as! FlutterError).message ?? "") //// onWillCommitWithAnimator(linkURL: elementInfo.linkURL?.absoluteString, result: nil/*{(result) -> Void in
// } //// if result is FlutterError {
// else if (result as? NSObject) == FlutterMethodNotImplemented { //// 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:
//// ////
//// } //// }
// 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, *) // @available(iOS 13.0, *)
// public func webView(_ webView: WKWebView, // public func webView(_ webView: WKWebView,
// contextMenuWillPresentForElement elementInfo: WKContextMenuElementInfo) { // contextMenuWillPresentForElement elementInfo: WKContextMenuElementInfo) {
// onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString) // print("contextMenuWillPresentForElement")
// print(elementInfo.linkURL)
// //onContextMenuWillPresentForElement(linkURL: elementInfo.linkURL?.absoluteString)
// } // }
public func onLoadStart(url: String) { public func onLoadStart(url: String) {
...@@ -2489,6 +2610,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -2489,6 +2610,24 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return Float(scrollView.zoomScale) 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() { public func dispose() {
stopLoading() stopLoading()
configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog") configuration.userContentController.removeScriptMessageHandler(forName: "consoleLog")
...@@ -2504,14 +2643,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi ...@@ -2504,14 +2643,20 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
configuration.userContentController.removeAllContentRuleLists() configuration.userContentController.removeAllContentRuleLists()
} }
longPressRecognizer!.removeTarget(self, action: #selector(longPressGestureDetected)) NotificationCenter.default.removeObserver(self)
longPressRecognizer!.delegate = nil for imp in customIMPs {
self.scrollView.removeGestureRecognizer(longPressRecognizer!) imp_removeBlock(imp)
}
longPressRecognizer?.removeTarget(self, action: #selector(longPressGestureDetected))
longPressRecognizer?.delegate = nil
scrollView.removeGestureRecognizer(longPressRecognizer!)
uiDelegate = nil uiDelegate = nil
navigationDelegate = nil navigationDelegate = nil
scrollView.delegate = nil scrollView.delegate = nil
IABController?.webView = nil IABController?.webView = nil
isPausedTimersCompletionHandler = nil isPausedTimersCompletionHandler = nil
channel = nil
SharedLastTouchPointTimestamp.removeValue(forKey: self)
super.removeFromSuperview() super.removeFromSuperview()
} }
......
...@@ -33,6 +33,7 @@ public class InAppWebViewOptions: Options { ...@@ -33,6 +33,7 @@ public class InAppWebViewOptions: Options {
var transparentBackground = false var transparentBackground = false
var disableVerticalScroll = false var disableVerticalScroll = false
var disableHorizontalScroll = false var disableHorizontalScroll = false
var disableContextMenu = false
var disallowOverScroll = false var disallowOverScroll = false
var enableViewportScale = false var enableViewportScale = false
......
...@@ -31,11 +31,10 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -31,11 +31,10 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary // let arguments = call.arguments as? NSDictionary
switch call.method { switch call.method {
case "close": case "close":
close() close(result: result)
break break
default: default:
result(FlutterMethodNotImplemented) result(FlutterMethodNotImplemented)
...@@ -67,7 +66,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -67,7 +66,7 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
self.modalTransitionStyle = UIModalTransitionStyle(rawValue: (safariOptions?.transitionStyle)!)! self.modalTransitionStyle = UIModalTransitionStyle(rawValue: (safariOptions?.transitionStyle)!)!
} }
func close() { func close(result: FlutterResult?) {
dismiss(animated: true) dismiss(animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in
...@@ -75,11 +74,14 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa ...@@ -75,11 +74,14 @@ public class SafariViewController: SFSafariViewController, FlutterPlugin, SFSafa
UIApplication.shared.delegate?.window??.makeKeyAndVisible() UIApplication.shared.delegate?.window??.makeKeyAndVisible()
self.onChromeSafariBrowserClosed() self.onChromeSafariBrowserClosed()
self.dispose() self.dispose()
if result != nil {
result!(true)
}
}) })
} }
public func safariViewControllerDidFinish(_ controller: SFSafariViewController) { public func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
close() close(result: nil)
} }
public func safariViewController(_ controller: SFSafariViewController, public func safariViewController(_ controller: SFSafariViewController,
......
...@@ -35,3 +35,4 @@ export 'src/webview_options.dart'; ...@@ -35,3 +35,4 @@ export 'src/webview_options.dart';
export 'src/content_blocker.dart'; export 'src/content_blocker.dart';
export 'src/http_auth_credentials_database.dart'; export 'src/http_auth_credentials_database.dart';
export 'src/web_storage_manager.dart'; export 'src/web_storage_manager.dart';
export 'src/context_menu.dart';
...@@ -61,6 +61,8 @@ class ChromeSafariBrowser { ...@@ -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. ///[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. ///[optionsFallback]: Options used by the [InAppBrowser] instance fallback.
///
///[contextMenuFallback]: Context Menu used by the [InAppBrowser] instance fallback.
Future<void> open( Future<void> open(
{@required String url, {@required String url,
ChromeSafariBrowserClassOptions options, ChromeSafariBrowserClassOptions options,
...@@ -81,11 +83,12 @@ class ChromeSafariBrowser { ...@@ -81,11 +83,12 @@ class ChromeSafariBrowser {
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('menuItemList', () => menuItemList);
args.putIfAbsent('uuidFallback', args.putIfAbsent('uuidFallback',
() => (browserFallback != null) ? browserFallback.uuid : ''); () => (browserFallback != null) ? browserFallback.uuid : '');
args.putIfAbsent('headersFallback', () => headersFallback); args.putIfAbsent('headersFallback', () => headersFallback);
args.putIfAbsent('optionsFallback', () => optionsFallback?.toMap() ?? {}); args.putIfAbsent('optionsFallback', () => optionsFallback?.toMap() ?? {});
args.putIfAbsent('menuItemList', () => menuItemList); args.putIfAbsent('contextMenuFallback', () => browserFallback?.contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('open', args); await _sharedChannel.invokeMethod('open', args);
this._isOpened = true; 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 'package:flutter/services.dart';
import 'context_menu.dart';
import 'types.dart'; import 'types.dart';
import 'webview.dart'; import 'webview.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
...@@ -16,46 +17,48 @@ class HeadlessInAppWebView implements WebView { ...@@ -16,46 +17,48 @@ class HeadlessInAppWebView implements WebView {
///WebView Controller that can be used to access the [InAppWebViewController] API. ///WebView Controller that can be used to access the [InAppWebViewController] API.
InAppWebViewController webViewController; InAppWebViewController webViewController;
HeadlessInAppWebView( HeadlessInAppWebView({
{this.onWebViewCreated, this.onWebViewCreated,
this.onLoadStart, this.onLoadStart,
this.onLoadStop, this.onLoadStop,
this.onLoadError, this.onLoadError,
this.onLoadHttpError, this.onLoadHttpError,
this.onProgressChanged, this.onProgressChanged,
this.onConsoleMessage, this.onConsoleMessage,
this.shouldOverrideUrlLoading, this.shouldOverrideUrlLoading,
this.onLoadResource, this.onLoadResource,
this.onScrollChanged, this.onScrollChanged,
this.onDownloadStart, this.onDownloadStart,
this.onLoadResourceCustomScheme, this.onLoadResourceCustomScheme,
this.onCreateWindow, this.onCreateWindow,
this.onJsAlert, this.onJsAlert,
this.onJsConfirm, this.onJsConfirm,
this.onJsPrompt, this.onJsPrompt,
this.onReceivedHttpAuthRequest, this.onReceivedHttpAuthRequest,
this.onReceivedServerTrustAuthRequest, this.onReceivedServerTrustAuthRequest,
this.onReceivedClientCertRequest, this.onReceivedClientCertRequest,
this.onFindResultReceived, this.onFindResultReceived,
this.shouldInterceptAjaxRequest, this.shouldInterceptAjaxRequest,
this.onAjaxReadyStateChange, this.onAjaxReadyStateChange,
this.onAjaxProgress, this.onAjaxProgress,
this.shouldInterceptFetchRequest, this.shouldInterceptFetchRequest,
this.onUpdateVisitedHistory, this.onUpdateVisitedHistory,
this.onPrint, this.onPrint,
this.onLongPressHitTestResult, this.onLongPressHitTestResult,
this.androidOnSafeBrowsingHit, this.androidOnSafeBrowsingHit,
this.androidOnPermissionRequest, this.androidOnPermissionRequest,
this.androidOnGeolocationPermissionsShowPrompt, this.androidOnGeolocationPermissionsShowPrompt,
this.androidOnGeolocationPermissionsHidePrompt, this.androidOnGeolocationPermissionsHidePrompt,
this.iosOnWebContentProcessDidTerminate, this.iosOnWebContentProcessDidTerminate,
this.iosOnDidCommit, this.iosOnDidCommit,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialUrl, this.initialUrl,
this.initialFile, this.initialFile,
this.initialData, this.initialData,
this.initialHeaders, this.initialHeaders,
this.initialOptions}) { this.initialOptions,
this.contextMenu
}) {
uuid = uuidGenerator.v4(); uuid = uuidGenerator.v4();
webViewController = new InAppWebViewController(uuid, this); webViewController = new InAppWebViewController(uuid, this);
} }
...@@ -83,7 +86,8 @@ class HeadlessInAppWebView implements WebView { ...@@ -83,7 +86,8 @@ class HeadlessInAppWebView implements WebView {
'initialFile': this.initialFile, 'initialFile': this.initialFile,
'initialData': this.initialData?.toMap(), 'initialData': this.initialData?.toMap(),
'initialHeaders': this.initialHeaders, 'initialHeaders': this.initialHeaders,
'initialOptions': this.initialOptions?.toMap() 'initialOptions': this.initialOptions?.toMap(),
'contextMenu': this.contextMenu?.toMap() ?? {}
}); });
await _sharedChannel.invokeMethod('createHeadlessWebView', args); await _sharedChannel.invokeMethod('createHeadlessWebView', args);
} }
...@@ -130,6 +134,9 @@ class HeadlessInAppWebView implements WebView { ...@@ -130,6 +134,9 @@ class HeadlessInAppWebView implements WebView {
@override @override
final InAppWebViewGroupOptions initialOptions; final InAppWebViewGroupOptions initialOptions;
@override
final ContextMenu contextMenu;
@override @override
final String initialUrl; final String initialUrl;
...@@ -210,7 +217,7 @@ class HeadlessInAppWebView implements WebView { ...@@ -210,7 +217,7 @@ class HeadlessInAppWebView implements WebView {
@override @override
final void Function(InAppWebViewController controller, final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult; InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult;
@override @override
final void Function(InAppWebViewController controller, String url) onPrint; final void Function(InAppWebViewController controller, String url) onPrint;
......
...@@ -4,6 +4,7 @@ import 'dart:io'; ...@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'context_menu.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
import 'webview_options.dart'; import 'webview_options.dart';
...@@ -12,7 +13,12 @@ import 'types.dart'; ...@@ -12,7 +13,12 @@ import 'types.dart';
///This class uses the native WebView of the platform. ///This class uses the native WebView of the platform.
///The [webViewController] field can be used to access the [InAppWebViewController] API. ///The [webViewController] field can be used to access the [InAppWebViewController] API.
class InAppBrowser { class InAppBrowser {
///Browser's UUID
String uuid; String uuid;
///Context menu used by the browser
ContextMenu contextMenu;
Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap = Map<String, JavaScriptHandlerCallback> javaScriptHandlersMap =
HashMap<String, JavaScriptHandlerCallback>(); HashMap<String, JavaScriptHandlerCallback>();
bool _isOpened = false; bool _isOpened = false;
...@@ -67,6 +73,7 @@ class InAppBrowser { ...@@ -67,6 +73,7 @@ class InAppBrowser {
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openUrl', args); await _sharedChannel.invokeMethod('openUrl', args);
} }
...@@ -117,6 +124,7 @@ class InAppBrowser { ...@@ -117,6 +124,7 @@ class InAppBrowser {
args.putIfAbsent('url', () => assetFilePath); args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
args.putIfAbsent('options', () => options?.toMap() ?? {}); args.putIfAbsent('options', () => options?.toMap() ?? {});
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openFile', args); await _sharedChannel.invokeMethod('openFile', args);
} }
...@@ -146,6 +154,7 @@ class InAppBrowser { ...@@ -146,6 +154,7 @@ class InAppBrowser {
args.putIfAbsent('encoding', () => encoding); args.putIfAbsent('encoding', () => encoding);
args.putIfAbsent('baseUrl', () => baseUrl); args.putIfAbsent('baseUrl', () => baseUrl);
args.putIfAbsent('historyUrl', () => androidHistoryUrl); args.putIfAbsent('historyUrl', () => androidHistoryUrl);
args.putIfAbsent('contextMenu', () => contextMenu?.toMap() ?? {});
await _sharedChannel.invokeMethod('openData', args); await _sharedChannel.invokeMethod('openData', args);
} }
...@@ -429,7 +438,7 @@ class InAppBrowser { ...@@ -429,7 +438,7 @@ class InAppBrowser {
///Event fired when an HTML element of the webview has been clicked and held. ///Event fired when an HTML element of the webview has been clicked and held.
/// ///
///[hitTestResult] represents the hit result for hitting an HTML elements. ///[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. ///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. ///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'; ...@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'context_menu.dart';
import 'webview.dart'; import 'webview.dart';
import 'types.dart'; import 'types.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
...@@ -38,6 +39,7 @@ class InAppWebView extends StatefulWidget implements WebView { ...@@ -38,6 +39,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.initialData, this.initialData,
this.initialHeaders = const {}, this.initialHeaders = const {},
@required this.initialOptions, @required this.initialOptions,
this.contextMenu,
this.onWebViewCreated, this.onWebViewCreated,
this.onLoadStart, this.onLoadStart,
this.onLoadStop, this.onLoadStop,
...@@ -112,6 +114,9 @@ class InAppWebView extends StatefulWidget implements WebView { ...@@ -112,6 +114,9 @@ class InAppWebView extends StatefulWidget implements WebView {
@override @override
final String initialUrl; final String initialUrl;
@override
final ContextMenu contextMenu;
@override @override
final Future<void> Function(InAppWebViewController controller) iosOnDidCommit; final Future<void> Function(InAppWebViewController controller) iosOnDidCommit;
...@@ -189,7 +194,7 @@ class InAppWebView extends StatefulWidget implements WebView { ...@@ -189,7 +194,7 @@ class InAppWebView extends StatefulWidget implements WebView {
@override @override
final void Function(InAppWebViewController controller, final void Function(InAppWebViewController controller,
LongPressHitTestResult hitTestResult) onLongPressHitTestResult; InAppWebViewHitTestResult hitTestResult) onLongPressHitTestResult;
@override @override
final void Function(InAppWebViewController controller, String url) onPrint; final void Function(InAppWebViewController controller, String url) onPrint;
...@@ -258,7 +263,8 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -258,7 +263,8 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile, 'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(), 'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders, 'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions?.toMap() ?? {} 'initialOptions': widget.initialOptions?.toMap() ?? {},
'contextMenu': widget.contextMenu?.toMap() ?? {}
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
...@@ -291,7 +297,8 @@ class _InAppWebViewState extends State<InAppWebView> { ...@@ -291,7 +297,8 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialFile': widget.initialFile, 'initialFile': widget.initialFile,
'initialData': widget.initialData?.toMap(), 'initialData': widget.initialData?.toMap(),
'initialHeaders': widget.initialHeaders, 'initialHeaders': widget.initialHeaders,
'initialOptions': widget.initialOptions?.toMap() ?? {} 'initialOptions': widget.initialOptions?.toMap() ?? {},
'contextMenu': widget.contextMenu?.toMap() ?? {}
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
......
...@@ -346,12 +346,12 @@ class InAppWebViewController { ...@@ -346,12 +346,12 @@ class InAppWebViewController {
_webview.iosOnWebContentProcessDidTerminate(this); _webview.iosOnWebContentProcessDidTerminate(this);
else if (_inAppBrowser != null) else if (_inAppBrowser != null)
_inAppBrowser.iosOnWebContentProcessDidTerminate(); _inAppBrowser.iosOnWebContentProcessDidTerminate();
return null; break;
case "onDidCommit": case "onDidCommit":
if (_webview != null && _webview.iosOnDidCommit != null) if (_webview != null && _webview.iosOnDidCommit != null)
_webview.iosOnDidCommit(this); _webview.iosOnDidCommit(this);
else if (_inAppBrowser != null) _inAppBrowser.iosOnDidCommit(); else if (_inAppBrowser != null) _inAppBrowser.iosOnDidCommit();
return null; break;
case "onDidReceiveServerRedirectForProvisionalNavigation": case "onDidReceiveServerRedirectForProvisionalNavigation":
if (_webview != null && if (_webview != null &&
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation != _webview.iosOnDidReceiveServerRedirectForProvisionalNavigation !=
...@@ -359,21 +359,80 @@ class InAppWebViewController { ...@@ -359,21 +359,80 @@ class InAppWebViewController {
_webview.iosOnDidReceiveServerRedirectForProvisionalNavigation(this); _webview.iosOnDidReceiveServerRedirectForProvisionalNavigation(this);
else if (_inAppBrowser != null) else if (_inAppBrowser != null)
_inAppBrowser.iosOnDidReceiveServerRedirectForProvisionalNavigation(); _inAppBrowser.iosOnDidReceiveServerRedirectForProvisionalNavigation();
return null; break;
case "onLongPressHitTestResult": case "onLongPressHitTestResult":
Map<dynamic, dynamic> hitTestResultMap = Map<dynamic, dynamic> hitTestResultMap =
call.arguments["hitTestResult"]; call.arguments["hitTestResult"];
LongPressHitTestResultType type = LongPressHitTestResultType.fromValue( InAppWebViewHitTestResultType type = InAppWebViewHitTestResultType.fromValue(
hitTestResultMap["type"].toInt()); hitTestResultMap["type"].toInt());
String extra = hitTestResultMap["extra"]; String extra = hitTestResultMap["extra"];
LongPressHitTestResult hitTestResult = InAppWebViewHitTestResult hitTestResult = InAppWebViewHitTestResult(type: type, extra: extra);
new LongPressHitTestResult(type: type, extra: extra);
if (_webview != null && _webview.onLongPressHitTestResult != null) if (_webview != null && _webview.onLongPressHitTestResult != null)
_webview.onLongPressHitTestResult(this, hitTestResult); _webview.onLongPressHitTestResult(this, hitTestResult);
else if (_inAppBrowser != null) else if (_inAppBrowser != null)
_inAppBrowser.onLongPressHitTestResult(hitTestResult); _inAppBrowser.onLongPressHitTestResult(hitTestResult);
break; 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": case "onCallJsHandler":
String handlerName = call.arguments["handlerName"]; String handlerName = call.arguments["handlerName"];
// decode args to json // decode args to json
...@@ -1198,7 +1257,7 @@ class InAppWebViewController { ...@@ -1198,7 +1257,7 @@ class InAppWebViewController {
return await _channel.invokeMethod('getContentHeight', args); 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. ///[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 { ...@@ -1215,6 +1274,27 @@ class InAppWebViewController {
return await _channel.invokeMethod('getScale', args); 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. ///Gets the default user agent.
static Future<String> getDefaultUserAgent() async { static Future<String> getDefaultUserAgent() async {
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
......
...@@ -2297,13 +2297,13 @@ class IOSWKWebsiteDataRecord { ...@@ -2297,13 +2297,13 @@ class IOSWKWebsiteDataRecord {
} }
} }
///Class representing the [LongPressHitTestResult] type. ///Class representing the [InAppWebViewHitTestResult] type.
class LongPressHitTestResultType { class InAppWebViewHitTestResultType {
final int _value; final int _value;
const LongPressHitTestResultType._internal(this._value); const InAppWebViewHitTestResultType._internal(this._value);
static LongPressHitTestResultType fromValue(int value) { static InAppWebViewHitTestResultType fromValue(int value) {
if (value != null && [0, 2, 3, 4, 5, 7, 8, 9].contains(value)) if (value != null && [0, 2, 3, 4, 5, 7, 8, 9].contains(value))
return LongPressHitTestResultType._internal(value); return InAppWebViewHitTestResultType._internal(value);
return null; return null;
} }
...@@ -2331,22 +2331,22 @@ class LongPressHitTestResultType { ...@@ -2331,22 +2331,22 @@ class LongPressHitTestResultType {
} }
} }
///Default [LongPressHitTestResult], where the target is unknown. ///Default [InAppWebViewHitTestResult], where the target is unknown.
static const UNKNOWN_TYPE = const LongPressHitTestResultType._internal(0); static const UNKNOWN_TYPE = const InAppWebViewHitTestResultType._internal(0);
///[LongPressHitTestResult] for hitting a phone number. ///[InAppWebViewHitTestResult] for hitting a phone number.
static const PHONE_TYPE = const LongPressHitTestResultType._internal(2); static const PHONE_TYPE = const InAppWebViewHitTestResultType._internal(2);
///[LongPressHitTestResult] for hitting a map address. ///[InAppWebViewHitTestResult] for hitting a map address.
static const GEO_TYPE = const LongPressHitTestResultType._internal(3); static const GEO_TYPE = const InAppWebViewHitTestResultType._internal(3);
///[LongPressHitTestResult] for hitting an email address. ///[InAppWebViewHitTestResult] for hitting an email address.
static const EMAIL_TYPE = const LongPressHitTestResultType._internal(4); static const EMAIL_TYPE = const InAppWebViewHitTestResultType._internal(4);
///[LongPressHitTestResult] for hitting an HTML::img tag. ///[InAppWebViewHitTestResult] for hitting an HTML::img tag.
static const IMAGE_TYPE = const LongPressHitTestResultType._internal(5); static const IMAGE_TYPE = const InAppWebViewHitTestResultType._internal(5);
///[LongPressHitTestResult] for hitting a HTML::a tag with src=http. ///[InAppWebViewHitTestResult] for hitting a HTML::a tag with src=http.
static const SRC_ANCHOR_TYPE = const LongPressHitTestResultType._internal(7); static const SRC_ANCHOR_TYPE = const InAppWebViewHitTestResultType._internal(7);
///[LongPressHitTestResult] for hitting a HTML::a tag with src=http + HTML::img. ///[InAppWebViewHitTestResult] for hitting a HTML::a tag with src=http + HTML::img.
static const SRC_IMAGE_ANCHOR_TYPE = const LongPressHitTestResultType._internal(8); static const SRC_IMAGE_ANCHOR_TYPE = const InAppWebViewHitTestResultType._internal(8);
///[LongPressHitTestResult] for hitting an edit text area. ///[InAppWebViewHitTestResult] for hitting an edit text area.
static const EDIT_TEXT_TYPE = const LongPressHitTestResultType._internal(9); static const EDIT_TEXT_TYPE = const InAppWebViewHitTestResultType._internal(9);
bool operator ==(value) => value == _value; bool operator ==(value) => value == _value;
...@@ -2354,12 +2354,12 @@ class LongPressHitTestResultType { ...@@ -2354,12 +2354,12 @@ class LongPressHitTestResultType {
int get hashCode => _value.hashCode; int get hashCode => _value.hashCode;
} }
///Class that represents the hit result for hitting an HTML elements. Used by [onLongPressHitTestResult] event. ///Class that represents the hit result for hitting an HTML elements.
class LongPressHitTestResult { class InAppWebViewHitTestResult {
///The type of the hit test result. ///The type of the hit test result.
LongPressHitTestResultType type; InAppWebViewHitTestResultType type;
///Additional type-dependant information about the result. ///Additional type-dependant information about the result.
String extra; String extra;
LongPressHitTestResult({this.type, this.extra}); InAppWebViewHitTestResult({this.type, this.extra});
} }
import 'package:flutter_inappwebview/src/context_menu.dart';
import 'types.dart'; import 'types.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
...@@ -233,7 +235,7 @@ abstract class WebView { ...@@ -233,7 +235,7 @@ abstract class WebView {
/// ///
///[hitTestResult] represents the hit result for hitting an HTML elements. ///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(InAppWebViewController controller, 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. ///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. ///The default behavior is to show an interstitial to the user, with the reporting checkbox visible.
...@@ -308,44 +310,49 @@ abstract class WebView { ...@@ -308,44 +310,49 @@ abstract class WebView {
///Initial options that will be used. ///Initial options that will be used.
final InAppWebViewGroupOptions initialOptions; final InAppWebViewGroupOptions initialOptions;
WebView( ///Context menu which contains custom menu items to be shown when [ContextMenu] is presented.
{this.onWebViewCreated, final ContextMenu contextMenu;
this.onLoadStart,
this.onLoadStop, WebView({
this.onLoadError, this.onWebViewCreated,
this.onLoadHttpError, this.onLoadStart,
this.onProgressChanged, this.onLoadStop,
this.onConsoleMessage, this.onLoadError,
this.shouldOverrideUrlLoading, this.onLoadHttpError,
this.onLoadResource, this.onProgressChanged,
this.onScrollChanged, this.onConsoleMessage,
this.onDownloadStart, this.shouldOverrideUrlLoading,
this.onLoadResourceCustomScheme, this.onLoadResource,
this.onCreateWindow, this.onScrollChanged,
this.onJsAlert, this.onDownloadStart,
this.onJsConfirm, this.onLoadResourceCustomScheme,
this.onJsPrompt, this.onCreateWindow,
this.onReceivedHttpAuthRequest, this.onJsAlert,
this.onReceivedServerTrustAuthRequest, this.onJsConfirm,
this.onReceivedClientCertRequest, this.onJsPrompt,
this.onFindResultReceived, this.onReceivedHttpAuthRequest,
this.shouldInterceptAjaxRequest, this.onReceivedServerTrustAuthRequest,
this.onAjaxReadyStateChange, this.onReceivedClientCertRequest,
this.onAjaxProgress, this.onFindResultReceived,
this.shouldInterceptFetchRequest, this.shouldInterceptAjaxRequest,
this.onUpdateVisitedHistory, this.onAjaxReadyStateChange,
this.onPrint, this.onAjaxProgress,
this.onLongPressHitTestResult, this.shouldInterceptFetchRequest,
this.androidOnSafeBrowsingHit, this.onUpdateVisitedHistory,
this.androidOnPermissionRequest, this.onPrint,
this.androidOnGeolocationPermissionsShowPrompt, this.onLongPressHitTestResult,
this.androidOnGeolocationPermissionsHidePrompt, this.androidOnSafeBrowsingHit,
this.iosOnWebContentProcessDidTerminate, this.androidOnPermissionRequest,
this.iosOnDidCommit, this.androidOnGeolocationPermissionsShowPrompt,
this.iosOnDidReceiveServerRedirectForProvisionalNavigation, this.androidOnGeolocationPermissionsHidePrompt,
this.initialUrl, this.iosOnWebContentProcessDidTerminate,
this.initialFile, this.iosOnDidCommit,
this.initialData, this.iosOnDidReceiveServerRedirectForProvisionalNavigation,
this.initialHeaders, this.initialUrl,
this.initialOptions}); this.initialFile,
this.initialData,
this.initialHeaders,
this.initialOptions,
this.contextMenu
});
} }
\ No newline at end of file
...@@ -128,6 +128,9 @@ class InAppWebViewOptions ...@@ -128,6 +128,9 @@ class InAppWebViewOptions
///Set to `true` to disable horizontal scroll. The default value is `false`. ///Set to `true` to disable horizontal scroll. The default value is `false`.
bool disableHorizontalScroll; bool disableHorizontalScroll;
///Set to `true` to disable context menu. The default value is `false`.
bool disableContextMenu;
InAppWebViewOptions( InAppWebViewOptions(
{this.useShouldOverrideUrlLoading = false, {this.useShouldOverrideUrlLoading = false,
this.useOnLoadResource = false, this.useOnLoadResource = false,
...@@ -152,7 +155,8 @@ class InAppWebViewOptions ...@@ -152,7 +155,8 @@ class InAppWebViewOptions
this.cacheEnabled = true, this.cacheEnabled = true,
this.transparentBackground = false, this.transparentBackground = false,
this.disableVerticalScroll = false, this.disableVerticalScroll = false,
this.disableHorizontalScroll = false}) { this.disableHorizontalScroll = false,
this.disableContextMenu = false}) {
if (this.minimumFontSize == null) if (this.minimumFontSize == null)
this.minimumFontSize = Platform.isAndroid ? 8 : 0; this.minimumFontSize = Platform.isAndroid ? 8 : 0;
assert(!this.resourceCustomSchemes.contains("http") && assert(!this.resourceCustomSchemes.contains("http") &&
...@@ -189,7 +193,8 @@ class InAppWebViewOptions ...@@ -189,7 +193,8 @@ class InAppWebViewOptions
"cacheEnabled": cacheEnabled, "cacheEnabled": cacheEnabled,
"transparentBackground": transparentBackground, "transparentBackground": transparentBackground,
"disableVerticalScroll": disableVerticalScroll, "disableVerticalScroll": disableVerticalScroll,
"disableHorizontalScroll": disableHorizontalScroll "disableHorizontalScroll": disableHorizontalScroll,
"disableContextMenu": disableContextMenu
}; };
} }
...@@ -234,6 +239,7 @@ class InAppWebViewOptions ...@@ -234,6 +239,7 @@ class InAppWebViewOptions
options.transparentBackground = map["transparentBackground"]; options.transparentBackground = map["transparentBackground"];
options.disableVerticalScroll = map["disableVerticalScroll"]; options.disableVerticalScroll = map["disableVerticalScroll"];
options.disableHorizontalScroll = map["disableHorizontalScroll"]; options.disableHorizontalScroll = map["disableHorizontalScroll"];
options.disableContextMenu = map["disableContextMenu"];
return options; return options;
} }
} }
......
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview or open an in-app browser window. 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 homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: 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