Added support for pull-to-refresh feature (fix #395), Fixed issue not rendering WebView content when scrolling on iOS (fix #704), Fixed InAppBrowser.openData method, InAppBrowser.initialUserScripts InAppBrowser.id HeadlessInAppWebView.id properties are final now

This commit is contained in:
Lorenzo Pichilli 2021-03-05 23:19:50 +01:00
parent da94374dda
commit b6d4fb9596
43 changed files with 1615 additions and 426 deletions

View File

@ -133,7 +133,7 @@
<entry key="file"> <entry key="file">
<value> <value>
<list> <list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" />
</list> </list>
</value> </value>
@ -377,7 +377,7 @@
<entry key="process"> <entry key="process">
<value> <value>
<list> <list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.1.0/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" />
</list> </list>
</value> </value>
@ -441,7 +441,7 @@
<entry key="source_span"> <entry key="source_span">
<value> <value>
<list> <list>
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib" />
<option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" /> <option value="$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" />
</list> </list>
</value> </value>
@ -647,7 +647,7 @@
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.0.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/ffi-1.0.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.0.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/file-6.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/glob-1.2.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.2.0/lib" />
@ -682,7 +682,7 @@
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-1.1.0-nullsafety.1/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-1.1.0-nullsafety.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pool-1.5.0-nullsafety.3/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pool-1.5.0-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.0.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/process-4.1.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.5/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.5/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-2.0.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-2.0.0/lib" />
@ -691,7 +691,7 @@
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-2.1.0-nullsafety.4/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-2.1.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.10-nullsafety.3/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.10-nullsafety.3/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0-nullsafety.4/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.6/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0-nullsafety.6/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib" />
<root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.3/lib" /> <root url="file://$USER_HOME$/flutter/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0-nullsafety.3/lib" />

View File

@ -1,3 +1,10 @@
## 5.1.0
- Added support for pull-to-refresh feature [#395](https://github.com/pichillilorenzo/flutter_inappwebview/issues/395)
- Fixed issue not rendering WebView content when scrolling on iOS [#703](https://github.com/pichillilorenzo/flutter_inappwebview/issues/703)
- Fixed `InAppBrowser.openData` method
- `InAppBrowser.initialUserScripts`, `InAppBrowser.id`, `HeadlessInAppWebView.id` properties are `final` now
## 5.0.5+3 ## 5.0.5+3
- Fixed Android `evaluateJavascript` method when using `contentWorld: ContentWorld.PAGE` - Fixed Android `evaluateJavascript` method when using `contentWorld: ContentWorld.PAGE`

167
README.md
View File

@ -302,15 +302,21 @@ Use `InAppWebViewController` to control the WebView instance.
Example: Example:
```dart ```dart
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';
Future main() async { Future main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (Platform.isAndroid) { if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true); await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
} }
runApp(new MyApp()); runApp(new MyApp());
} }
@ -321,13 +327,42 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
InAppWebViewController? webView; final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webViewController;
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
));
late PullToRefreshController pullToRefreshController;
String url = ""; String url = "";
double progress = 0; double progress = 0;
final urlController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.blue,
),
onRefresh: () async {
if (Platform.isAndroid) {
webViewController?.reload();
} else if (Platform.isIOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(url: await webViewController?.getUrl()));
}
},
);
} }
@override @override
@ -339,60 +374,105 @@ class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
home: Scaffold( home: Scaffold(
appBar: AppBar( appBar: AppBar(title: Text("Official InAppWebView website")),
title: const Text('InAppWebView Example'), body: SafeArea(
),
body: Container(
child: Column(children: <Widget>[ child: Column(children: <Widget>[
Container( TextField(
padding: EdgeInsets.all(20.0), decoration: InputDecoration(
child: Text( prefixIcon: Icon(Icons.search)
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"), ),
controller: urlController,
keyboardType: TextInputType.url,
onSubmitted: (value) {
var url = Uri.parse(value);
if (url.scheme.isEmpty) {
url = Uri.parse("https://www.google.com/search?q=" + value);
}
webViewController?.loadUrl(
urlRequest: URLRequest(url: url));
},
), ),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0
? LinearProgressIndicator(value: progress)
: Container()),
Expanded( Expanded(
child: Container( child: Stack(
margin: const EdgeInsets.all(10.0), children: [
decoration: InAppWebView(
BoxDecoration(border: Border.all(color: Colors.blueAccent)), key: webViewKey,
child: InAppWebView( initialUrlRequest:
initialUrlRequest: URLRequest( URLRequest(url: Uri.parse("https://inappwebview.dev/")),
url: Uri.parse("https://flutter.dev/") initialOptions: options,
), pullToRefreshController: pullToRefreshController,
initialOptions: InAppWebViewGroupOptions( onWebViewCreated: (controller) {
crossPlatform: InAppWebViewOptions( webViewController = controller;
),
ios: IOSInAppWebViewOptions(
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true
)
),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
}, },
onLoadStart: (controller, url) { onLoadStart: (controller, url) {
setState(() { setState(() {
this.url = url?.toString() ?? ''; this.url = url.toString();
urlController.text = this.url;
}); });
}, },
androidOnPermissionRequest: (InAppWebViewController controller,
String origin, List<String> resources) async {
return PermissionRequestResponse(
resources: resources,
action: PermissionRequestResponseAction.GRANT);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (![
"http",
"https",
"file",
"chrome",
"data",
"javascript",
"about"
].contains(uri.scheme)) {
if (await canLaunch(url)) {
// Launch the App
await launch(
url,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
pullToRefreshController.endRefreshing();
setState(() { setState(() {
this.url = url?.toString() ?? ''; this.url = url.toString();
urlController.text = this.url;
}); });
}, },
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();
},
onProgressChanged: (controller, progress) { onProgressChanged: (controller, progress) {
if (progress == 100) {
pullToRefreshController.endRefreshing();
}
setState(() { setState(() {
this.progress = progress / 100; this.progress = progress / 100;
urlController.text = this.url;
}); });
}, },
onUpdateVisitedHistory: (controller, url, androidIsReload) {
setState(() {
this.url = url.toString();
urlController.text = this.url;
});
},
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
},
), ),
progress < 1.0
? LinearProgressIndicator(value: progress)
: Container(),
],
), ),
), ),
ButtonBar( ButtonBar(
@ -401,25 +481,24 @@ class _MyAppState extends State<MyApp> {
ElevatedButton( ElevatedButton(
child: Icon(Icons.arrow_back), child: Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
webView?.goBack(); webViewController?.goBack();
}, },
), ),
ElevatedButton( ElevatedButton(
child: Icon(Icons.arrow_forward), child: Icon(Icons.arrow_forward),
onPressed: () { onPressed: () {
webView?.goForward(); webViewController?.goForward();
}, },
), ),
ElevatedButton( ElevatedButton(
child: Icon(Icons.refresh), child: Icon(Icons.refresh),
onPressed: () { onPressed: () {
webView?.reload(); webViewController?.reload();
}, },
), ),
], ],
), ),
])), ]))),
),
); );
} }
} }
@ -428,11 +507,11 @@ class _MyAppState extends State<MyApp> {
Screenshots: Screenshots:
- Android: - Android:
![android](https://user-images.githubusercontent.com/5956938/47271038-7aebda80-d574-11e8-98fd-41e6bbc9fe2d.gif) ![android](https://user-images.githubusercontent.com/5956938/110179602-a18ad300-7e08-11eb-849b-2c7f1af28155.gif)
- iOS: - iOS:
![ios](https://user-images.githubusercontent.com/5956938/54096363-e1e72000-43ab-11e9-85c2-983a830ab7a0.gif) ![ios](https://user-images.githubusercontent.com/5956938/110179614-a8194a80-7e08-11eb-85f9-3da10acbbcb2.gif)
#### `InAppWebViewController` Methods #### `InAppWebViewController` Methods

View File

@ -49,5 +49,6 @@ android {
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.appcompat:appcompat:1.2.0-rc02' implementation 'androidx.appcompat:appcompat:1.2.0-rc02'
implementation 'com.squareup.okhttp3:mockwebserver:3.14.7' implementation 'com.squareup.okhttp3:mockwebserver:3.14.7'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
} }
} }

View File

@ -29,6 +29,8 @@ import com.pichillilorenzo.flutter_inappwebview.in_app_webview.InAppWebViewOptio
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler;
import com.pichillilorenzo.flutter_inappwebview.R; import com.pichillilorenzo.flutter_inappwebview.R;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshOptions;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript; import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.Util; import com.pichillilorenzo.flutter_inappwebview.Util;
@ -48,6 +50,7 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
public Integer windowId; public Integer windowId;
public String id; public String id;
public InAppWebView webView; public InAppWebView webView;
public PullToRefreshLayout pullToRefreshLayout;
public ActionBar actionBar; public ActionBar actionBar;
public Menu menu; public Menu menu;
public SearchView searchView; public SearchView searchView;
@ -74,6 +77,15 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
setContentView(R.layout.activity_web_view); setContentView(R.layout.activity_web_view);
Map<String, Object> pullToRefreshInitialOptions = (Map<String, Object>) b.getSerializable("pullToRefreshInitialOptions");
MethodChannel pullToRefreshLayoutChannel = new MethodChannel(Shared.messenger, "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_" + id);
PullToRefreshOptions pullToRefreshOptions = new PullToRefreshOptions();
pullToRefreshOptions.parse(pullToRefreshInitialOptions);
pullToRefreshLayout = findViewById(R.id.pullToRefresh);
pullToRefreshLayout.channel = pullToRefreshLayoutChannel;
pullToRefreshLayout.options = pullToRefreshOptions;
pullToRefreshLayout.prepare();
webView = findViewById(R.id.webView); webView = findViewById(R.id.webView);
webView.windowId = windowId; webView.windowId = windowId;
webView.inAppBrowserDelegate = this; webView.inAppBrowserDelegate = this;
@ -128,10 +140,10 @@ public class InAppBrowserActivity extends AppCompatActivity implements InAppBrow
} }
} }
else if (initialData != null) { else if (initialData != null) {
String mimeType = b.getString("mimeType"); String mimeType = b.getString("initialMimeType");
String encoding = b.getString("encoding"); String encoding = b.getString("initialEncoding");
String baseUrl = b.getString("baseUrl"); String baseUrl = b.getString("initialBaseUrl");
String historyUrl = b.getString("historyUrl"); String historyUrl = b.getString("initialHistoryUrl");
webView.loadDataWithBaseURL(baseUrl, initialData, mimeType, encoding, historyUrl); webView.loadDataWithBaseURL(baseUrl, initialData, mimeType, encoding, historyUrl);
} }
else if (initialUrlRequest != null) { else if (initialUrlRequest != null) {

View File

@ -62,44 +62,8 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
final Activity activity = Shared.activity; final Activity activity = Shared.activity;
switch (call.method) { switch (call.method) {
case "openUrlRequest": case "open":
{ open(activity, (Map<String, Object>) call.arguments());
String id = (String) call.argument("id");
Map<String, Object> urlRequest = (Map<String, Object>) call.argument("urlRequest");
Map<String, Object> options = (Map<String, Object>) call.argument("options");
Map<String, Object> contextMenu = (Map<String, Object>) call.argument("contextMenu");
Integer windowId = (Integer) call.argument("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) call.argument("initialUserScripts");
openUrlRequest(activity, id, urlRequest, options, contextMenu, windowId, initialUserScripts);
}
result.success(true);
break;
case "openFile":
{
String id = (String) call.argument("id");
String assetFilePath = (String) call.argument("assetFilePath");
Map<String, Object> options = (Map<String, Object>) call.argument("options");
Map<String, Object> contextMenu = (Map<String, Object>) call.argument("contextMenu");
Integer windowId = (Integer) call.argument("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) call.argument("initialUserScripts");
openFile(activity, id, assetFilePath, options, contextMenu, windowId, initialUserScripts);
}
result.success(true);
break;
case "openData":
{
String id = (String) call.argument("id");
Map<String, Object> options = (Map<String, Object>) call.argument("options");
String data = (String) call.argument("data");
String mimeType = (String) call.argument("mimeType");
String encoding = (String) call.argument("encoding");
String baseUrl = (String) call.argument("baseUrl");
String historyUrl = (String) call.argument("historyUrl");
Map<String, Object> contextMenu = (Map<String, Object>) call.argument("contextMenu");
Integer windowId = (Integer) call.argument("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) call.argument("initialUserScripts");
openData(activity, id, options, data, mimeType, encoding, baseUrl, historyUrl, contextMenu, windowId, initialUserScripts);
}
result.success(true); result.success(true);
break; break;
case "openWithSystemBrowser": case "openWithSystemBrowser":
@ -189,45 +153,36 @@ public class InAppBrowserManager implements MethodChannel.MethodCallHandler {
} }
} }
public void openUrlRequest(Activity activity, String id, Map<String, Object> urlRequest, Map<String, Object> options, public void open(Activity activity, Map<String, Object> arguments) {
Map<String, Object> contextMenu, Integer windowId, List<Map<String, Object>> initialUserScripts) { String id = (String) arguments.get("id");
Map<String, Object> urlRequest = (Map<String, Object>) arguments.get("urlRequest");
String assetFilePath = (String) arguments.get("assetFilePath");
String data = (String) arguments.get("data");
String mimeType = (String) arguments.get("mimeType");
String encoding = (String) arguments.get("encoding");
String baseUrl = (String) arguments.get("baseUrl");
String historyUrl = (String) arguments.get("historyUrl");
Map<String, Object> options = (Map<String, Object>) arguments.get("options");
Map<String, Object> contextMenu = (Map<String, Object>) arguments.get("contextMenu");
Integer windowId = (Integer) arguments.get("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) arguments.get("initialUserScripts");
Map<String, Object> pullToRefreshInitialOptions = (Map<String, Object>) arguments.get("pullToRefreshOptions");
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString("fromActivity", activity.getClass().getName()); extras.putString("fromActivity", activity.getClass().getName());
extras.putSerializable("initialUrlRequest", (Serializable) urlRequest); extras.putSerializable("initialUrlRequest", (Serializable) urlRequest);
extras.putString("id", id);
extras.putSerializable("options", (Serializable) options);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
extras.putInt("windowId", windowId != null ? windowId : -1);
extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts);
startInAppBrowserActivity(activity, extras);
}
public void openFile(Activity activity, String id, String assetFilePath, Map<String, Object> options,
Map<String, Object> contextMenu, Integer windowId, List<Map<String, Object>> initialUserScripts) {
Bundle extras = new Bundle();
extras.putString("fromActivity", activity.getClass().getName());
extras.putString("initialFile", assetFilePath); extras.putString("initialFile", assetFilePath);
extras.putString("id", id);
extras.putSerializable("options", (Serializable) options);
extras.putSerializable("contextMenu", (Serializable) contextMenu);
extras.putInt("windowId", windowId != null ? windowId : -1);
extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts);
startInAppBrowserActivity(activity, extras);
}
public void openData(Activity activity, String id, Map<String, Object> options, String data, String mimeType, String encoding,
String baseUrl, String historyUrl, Map<String, Object> contextMenu, Integer windowId, List<Map<String, Object>> initialUserScripts) {
Bundle extras = new Bundle();
extras.putString("id", id);
extras.putSerializable("options", (Serializable) options);
extras.putString("initialData", data); extras.putString("initialData", data);
extras.putString("initialMimeType", mimeType); extras.putString("initialMimeType", mimeType);
extras.putString("initialEncoding", encoding); extras.putString("initialEncoding", encoding);
extras.putString("initialBaseUrl", baseUrl); extras.putString("initialBaseUrl", baseUrl);
extras.putString("initialHistoryUrl", historyUrl); extras.putString("initialHistoryUrl", historyUrl);
extras.putString("id", id);
extras.putSerializable("options", (Serializable) options);
extras.putSerializable("contextMenu", (Serializable) contextMenu); extras.putSerializable("contextMenu", (Serializable) contextMenu);
extras.putInt("windowId", windowId != null ? windowId : -1); extras.putInt("windowId", windowId != null ? windowId : -1);
extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts); extras.putSerializable("initialUserScripts", (Serializable) initialUserScripts);
extras.putSerializable("pullToRefreshInitialOptions", (Serializable) pullToRefreshInitialOptions);
startInAppBrowserActivity(activity, extras); startInAppBrowserActivity(activity, extras);
} }

View File

@ -15,7 +15,7 @@ public class InAppBrowserOptions implements Options<InAppBrowserActivity> {
public Boolean hidden = false; public Boolean hidden = false;
public Boolean hideToolbarTop = false; public Boolean hideToolbarTop = false;
@Nullable @Nullable
public String toolbarTopBackgroundColor = null; public String toolbarTopBackgroundColor;
@Nullable @Nullable
public String toolbarTopFixedTitle; public String toolbarTopFixedTitle;
public Boolean hideUrlBar = false; public Boolean hideUrlBar = false;

View File

@ -35,7 +35,7 @@ public class ContextMenuOptions implements Options<Object> {
} }
@Override @Override
public Map<String, Object> getRealOptions(Object webView) { public Map<String, Object> getRealOptions(Object obj) {
Map<String, Object> realOptions = toMap(); Map<String, Object> realOptions = toMap();
return realOptions; return realOptions;
} }

View File

@ -16,9 +16,10 @@ import androidx.webkit.WebViewFeature;
import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler; import com.pichillilorenzo.flutter_inappwebview.InAppWebViewMethodHandler;
import com.pichillilorenzo.flutter_inappwebview.Shared; import com.pichillilorenzo.flutter_inappwebview.Shared;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout;
import com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshOptions;
import com.pichillilorenzo.flutter_inappwebview.types.URLRequest; import com.pichillilorenzo.flutter_inappwebview.types.URLRequest;
import com.pichillilorenzo.flutter_inappwebview.types.UserScript; import com.pichillilorenzo.flutter_inappwebview.types.UserScript;
import com.pichillilorenzo.flutter_inappwebview.Util;
import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS; import com.pichillilorenzo.flutter_inappwebview.plugin_scripts_js.JavaScriptBridgeJS;
import java.io.IOException; import java.io.IOException;
@ -38,6 +39,7 @@ public class FlutterWebView implements PlatformView {
public InAppWebView webView; public InAppWebView webView;
public final MethodChannel channel; public final MethodChannel channel;
public InAppWebViewMethodHandler methodCallDelegate; public InAppWebViewMethodHandler methodCallDelegate;
public PullToRefreshLayout pullToRefreshLayout;
public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, HashMap<String, Object> params, View containerView) { public FlutterWebView(BinaryMessenger messenger, final Context context, Object id, HashMap<String, Object> params, View containerView) {
channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id); channel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_" + id);
@ -53,6 +55,7 @@ public class FlutterWebView implements PlatformView {
Map<String, Object> contextMenu = (Map<String, Object>) params.get("contextMenu"); Map<String, Object> contextMenu = (Map<String, Object>) params.get("contextMenu");
Integer windowId = (Integer) params.get("windowId"); Integer windowId = (Integer) params.get("windowId");
List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) params.get("initialUserScripts"); List<Map<String, Object>> initialUserScripts = (List<Map<String, Object>>) params.get("initialUserScripts");
Map<String, Object> pullToRefreshInitialOptions = (Map<String, Object>) params.get("pullToRefreshOptions");
InAppWebViewOptions options = new InAppWebViewOptions(); InAppWebViewOptions options = new InAppWebViewOptions();
options.parse(initialOptions); options.parse(initialOptions);
@ -74,6 +77,13 @@ public class FlutterWebView implements PlatformView {
webView = new InAppWebView(context, channel, id, windowId, options, contextMenu, containerView, userScripts); webView = new InAppWebView(context, channel, id, windowId, options, contextMenu, containerView, userScripts);
displayListenerProxy.onPostWebViewInitialization(displayManager); displayListenerProxy.onPostWebViewInitialization(displayManager);
MethodChannel pullToRefreshLayoutChannel = new MethodChannel(messenger, "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_" + id);
PullToRefreshOptions pullToRefreshOptions = new PullToRefreshOptions();
pullToRefreshOptions.parse(pullToRefreshInitialOptions);
pullToRefreshLayout = new PullToRefreshLayout(context, pullToRefreshLayoutChannel, pullToRefreshOptions);
pullToRefreshLayout.addView(webView);
pullToRefreshLayout.prepare();
methodCallDelegate = new InAppWebViewMethodHandler(webView); methodCallDelegate = new InAppWebViewMethodHandler(webView);
channel.setMethodCallHandler(methodCallDelegate); channel.setMethodCallHandler(methodCallDelegate);
@ -117,7 +127,7 @@ public class FlutterWebView implements PlatformView {
@Override @Override
public View getView() { public View getView() {
return webView; return pullToRefreshLayout;
} }
@Override @Override
@ -145,6 +155,11 @@ public class FlutterWebView implements PlatformView {
webView.dispose(); webView.dispose();
webView.destroy(); webView.destroy();
webView = null; webView = null;
if (pullToRefreshLayout != null) {
pullToRefreshLayout.dispose();
pullToRefreshLayout = null;
}
} }
}); });
WebSettings settings = webView.getSettings(); WebSettings settings = webView.getSettings();

View File

@ -175,7 +175,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
channel.invokeMethod("onJsAlert", obj, new MethodChannel.Result() { channel.invokeMethod("onJsAlert", obj, new MethodChannel.Result() {
@Override @Override
public void success(Object response) { public void success(@Nullable Object response) {
String responseMessage = null; String responseMessage = null;
String confirmButtonTitle = null; String confirmButtonTitle = null;
@ -203,8 +203,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
result.cancel(); result.cancel();
} }
@ -290,8 +290,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
result.cancel(); result.cancel();
} }
@ -393,8 +393,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
result.cancel(); result.cancel();
} }
@ -507,8 +507,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
result.cancel(); result.cancel();
} }
@ -599,6 +599,7 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
@Override @Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
if (InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) { if (InAppWebViewChromeClient.windowWebViewMessages.containsKey(windowId)) {
InAppWebViewChromeClient.windowWebViewMessages.remove(windowId); InAppWebViewChromeClient.windowWebViewMessages.remove(windowId);
} }
@ -638,7 +639,8 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
callback.invoke(origin, false, false); callback.invoke(origin, false, false);
} }
@ -1124,8 +1126,9 @@ public class InAppWebViewChromeClient extends WebChromeClient implements PluginR
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
request.deny();
} }
@Override @Override

View File

@ -150,8 +150,8 @@ public class InAppWebViewClient extends WebViewClient {
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, "ERROR: " + s + " " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
allowShouldOverrideUrlLoading(webView, url, headers, isForMainFrame); allowShouldOverrideUrlLoading(webView, url, headers, isForMainFrame);
} }
@ -375,8 +375,8 @@ public class InAppWebViewClient extends WebViewClient {
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override
@ -428,8 +428,8 @@ public class InAppWebViewClient extends WebViewClient {
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override
@ -493,8 +493,8 @@ public class InAppWebViewClient extends WebViewClient {
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override
@ -553,8 +553,8 @@ public class InAppWebViewClient extends WebViewClient {
} }
@Override @Override
public void error(String s, String s1, Object o) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.e(LOG_TAG, s + ", " + s1); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override
@ -736,7 +736,7 @@ public class InAppWebViewClient extends WebViewClient {
@Override @Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.d(LOG_TAG, "ERROR: " + errorCode + " " + errorMessage); Log.e(LOG_TAG, "ERROR: " + errorCode + " " + errorMessage);
} }
@Override @Override

View File

@ -47,7 +47,7 @@ public class InAppWebViewRenderProcessClient extends WebViewRenderProcessClient
@Override @Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.d(LOG_TAG, "ERROR: " + errorCode + " " + errorMessage); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override
@ -79,7 +79,7 @@ public class InAppWebViewRenderProcessClient extends WebViewRenderProcessClient
@Override @Override
public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
Log.d(LOG_TAG, "ERROR: " + errorCode + " " + errorMessage); Log.e(LOG_TAG, errorCode + ", " + ((errorMessage != null) ? errorMessage : ""));
} }
@Override @Override

View File

@ -0,0 +1,132 @@
package com.pichillilorenzo.flutter_inappwebview.pull_to_refresh;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class PullToRefreshLayout extends SwipeRefreshLayout implements MethodChannel.MethodCallHandler {
static final String LOG_TAG = "PullToRefreshLayout";
public MethodChannel channel;
public PullToRefreshOptions options;
public PullToRefreshLayout(@NonNull Context context, @NonNull MethodChannel channel, @NonNull PullToRefreshOptions options) {
super(context);
this.channel = channel;
this.options = options;
}
public PullToRefreshLayout(@NonNull Context context) {
super(context);
this.channel = null;
this.options = null;
}
public PullToRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.channel = null;
this.options = null;
}
public void prepare() {
final PullToRefreshLayout self = this;
if (channel != null) {
this.channel.setMethodCallHandler(this);
}
setEnabled(options.enabled);
setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (channel == null) {
self.setRefreshing(false);
return;
}
Map<String, Object> obj = new HashMap<>();
channel.invokeMethod("onRefresh", obj);
}
});
if (options.color != null)
setColorSchemeColors(Color.parseColor(options.color));
if (options.backgroundColor != null)
setProgressBackgroundColorSchemeColor(Color.parseColor(options.backgroundColor));
if (options.distanceToTriggerSync != null)
setDistanceToTriggerSync(options.distanceToTriggerSync);
if (options.slingshotDistance != null)
setSlingshotDistance(options.slingshotDistance);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final MethodChannel.Result result) {
switch (call.method) {
case "setEnabled":
{
Boolean enabled = (Boolean) call.argument("enabled");
setEnabled(enabled);
}
result.success(true);
break;
case "setRefreshing":
{
Boolean refreshing = (Boolean) call.argument("refreshing");
setRefreshing(refreshing);
}
result.success(true);
break;
case "isRefreshing":
result.success(isRefreshing());
break;
case "setColor":
{
String color = (String) call.argument("color");
setColorSchemeColors(Color.parseColor(color));
}
result.success(true);
break;
case "setBackgroundColor":
{
String color = (String) call.argument("color");
setProgressBackgroundColorSchemeColor(Color.parseColor(color));
}
result.success(true);
break;
case "setDistanceToTriggerSync":
{
Integer distanceToTriggerSync = (Integer) call.argument("distanceToTriggerSync");
setDistanceToTriggerSync(distanceToTriggerSync);
}
result.success(true);
break;
case "setSlingshotDistance":
{
Integer slingshotDistance = (Integer) call.argument("slingshotDistance");
setSlingshotDistance(slingshotDistance);
}
result.success(true);
break;
case "getDefaultSlingshotDistance":
result.success(SwipeRefreshLayout.DEFAULT_SLINGSHOT_DISTANCE);
break;
default:
result.notImplemented();
}
}
public void dispose() {
removeAllViews();
if (channel != null) {
channel.setMethodCallHandler(null);
}
}
}

View File

@ -0,0 +1,69 @@
package com.pichillilorenzo.flutter_inappwebview.pull_to_refresh;
import androidx.annotation.Nullable;
import com.pichillilorenzo.flutter_inappwebview.Options;
import java.util.HashMap;
import java.util.Map;
public class PullToRefreshOptions implements Options<PullToRefreshLayout> {
public static final String LOG_TAG = "PullToRefreshOptions";
public Boolean enabled = true;
@Nullable
public String color;
@Nullable
public String backgroundColor;
@Nullable
public Integer distanceToTriggerSync;
@Nullable
public Integer slingshotDistance;
public PullToRefreshOptions parse(Map<String, Object> options) {
for (Map.Entry<String, Object> pair : options.entrySet()) {
String key = pair.getKey();
Object value = pair.getValue();
if (value == null) {
continue;
}
switch (key) {
case "enabled":
enabled = (Boolean) value;
break;
case "color":
color = (String) value;
break;
case "backgroundColor":
backgroundColor = (String) value;
break;
case "distanceToTriggerSync":
distanceToTriggerSync = (Integer) value;
break;
case "slingshotDistance":
slingshotDistance = (Integer) value;
break;
}
}
return this;
}
public Map<String, Object> toMap() {
Map<String, Object> options = new HashMap<>();
options.put("enabled", enabled);
options.put("color", color);
options.put("backgroundColor", backgroundColor);
options.put("distanceToTriggerSync", distanceToTriggerSync);
options.put("slingshotDistance", slingshotDistance);
return options;
}
@Override
public Map<String, Object> getRealOptions(PullToRefreshLayout pullToRefreshLayout) {
Map<String, Object> realOptions = toMap();
return realOptions;
}
}

View File

@ -10,10 +10,15 @@
tools:context=".in_app_browser.InAppBrowserActivity" tools:context=".in_app_browser.InAppBrowserActivity"
android:focusable="true"> android:focusable="true">
<com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout
android:id="@+id/pullToRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.pichillilorenzo.flutter_inappwebview.in_app_webview.InAppWebView <com.pichillilorenzo.flutter_inappwebview.in_app_webview.InAppWebView
android:id="@+id/webView" android:id="@+id/webView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</com.pichillilorenzo.flutter_inappwebview.pull_to_refresh.PullToRefreshLayout>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"

View File

@ -1 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-02 01:01:13.621928","version":"1.27.0-4.0.pre"} {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"android":[{"name":"flutter_downloader","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_downloader-1.5.2/","dependencies":[]},{"name":"flutter_inappwebview","path":"/Users/lorenzopichilli/Desktop/flutter_inappwebview/","dependencies":[]},{"name":"integration_test","path":"/Users/lorenzopichilli/flutter/packages/integration_test/","dependencies":[]},{"name":"path_provider","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.0-nullsafety/","dependencies":[]},{"name":"permission_handler","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/permission_handler-5.1.0+2/","dependencies":[]},{"name":"url_launcher","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher-6.0.0-nullsafety.6/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.5-nullsafety/","dependencies":[]},{"name":"url_launcher_macos","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_macos-0.1.0-nullsafety.2/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.2.0-nullsafety/","dependencies":[]},{"name":"url_launcher_linux","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_linux-0.1.0-nullsafety.3/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.1.0-nullsafety.3/","dependencies":[]},{"name":"url_launcher_windows","path":"/Users/lorenzopichilli/flutter/.pub-cache/hosted/pub.dartlang.org/url_launcher_windows-0.1.0-nullsafety.2/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_downloader","dependencies":[]},{"name":"flutter_inappwebview","dependencies":[]},{"name":"integration_test","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"url_launcher","dependencies":["url_launcher_linux","url_launcher_macos","url_launcher_windows"]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]}],"date_created":"2021-03-05 22:47:49.023558","version":"2.1.0-10.0.pre"}

View File

@ -86,7 +86,7 @@ void main() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -96,7 +96,7 @@ void main() {
final InAppWebViewController controller = final InAppWebViewController controller =
await controllerCompleter.future; await controllerCompleter.future;
final String? currentUrl = (await controller.getUrl())?.toString(); final String? currentUrl = (await controller.getUrl())?.toString();
expect(currentUrl, 'https://flutter.dev/'); expect(currentUrl, 'https://github.com/flutter');
}); });
testWidgets('set/get options', (WidgetTester tester) async { testWidgets('set/get options', (WidgetTester tester) async {
@ -109,7 +109,7 @@ void main() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(javaScriptEnabled: false)), crossPlatform: InAppWebViewOptions(javaScriptEnabled: false)),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
@ -318,7 +318,7 @@ void main() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -331,7 +331,7 @@ void main() {
final InAppWebViewController controller = final InAppWebViewController controller =
await controllerCompleter.future; await controllerCompleter.future;
var url = await pageLoads.stream.first; var url = await pageLoads.stream.first;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
await controller.loadUrl( await controller.loadUrl(
urlRequest: URLRequest(url: Uri.parse('https://www.google.com/'))); urlRequest: URLRequest(url: Uri.parse('https://www.google.com/')));
@ -353,7 +353,7 @@ void main() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -1528,7 +1528,7 @@ void main() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
ios: IOSInAppWebViewOptions( ios: IOSInAppWebViewOptions(
allowsBackForwardNavigationGestures: true)), allowsBackForwardNavigationGestures: true)),
@ -1542,8 +1542,8 @@ void main() {
final InAppWebViewController controller = final InAppWebViewController controller =
await controllerCompleter.future; await controllerCompleter.future;
final String? currentUrl = (await controller.getUrl())?.toString(); final String? currentUrl = (await controller.getUrl())?.toString();
expect(currentUrl, contains('flutter.dev')); expect(currentUrl, 'https://github.com/flutter');
}); }, skip: !Platform.isIOS);
testWidgets('target _blank opens in same window', testWidgets('target _blank opens in same window',
(WidgetTester tester) async { (WidgetTester tester) async {
@ -2497,7 +2497,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions( crossPlatform: InAppWebViewOptions(
clearCache: true, clearCache: true,
@ -2560,7 +2560,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -2634,7 +2634,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
await controller.evaluateJavascript(source: "window.print();"); await controller.evaluateJavascript(source: "window.print();");
}, },
@ -2645,7 +2645,7 @@ setTimeout(function() {
), ),
); );
final String url = await onPrintCompleter.future; final String url = await onPrintCompleter.future;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
}, skip: true); }, skip: true);
testWidgets('onWindowFocus', (WidgetTester tester) async { testWidgets('onWindowFocus', (WidgetTester tester) async {
@ -2656,7 +2656,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
await controller.evaluateJavascript( await controller.evaluateJavascript(
source: 'window.dispatchEvent(new Event("focus"));'); source: 'window.dispatchEvent(new Event("focus"));');
@ -2678,7 +2678,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
await controller.evaluateJavascript( await controller.evaluateJavascript(
source: 'window.dispatchEvent(new Event("blur"));'); source: 'window.dispatchEvent(new Event("blur"));');
@ -2703,7 +2703,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -2715,7 +2715,7 @@ setTimeout(function() {
); );
final String? url = await onPageCommitVisibleCompleter.future; final String? url = await onPageCommitVisibleCompleter.future;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
}); });
testWidgets('onTitleChanged', (WidgetTester tester) async { testWidgets('onTitleChanged', (WidgetTester tester) async {
@ -2729,7 +2729,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -2903,7 +2903,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -2973,7 +2973,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -3019,7 +3019,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
ios: IOSInAppWebViewOptions(useOnNavigationResponse: true)), ios: IOSInAppWebViewOptions(useOnNavigationResponse: true)),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
@ -3039,7 +3039,7 @@ setTimeout(function() {
await pageLoaded.future; await pageLoaded.future;
final String url = await onNavigationResponseCompleter.future; final String url = await onNavigationResponseCompleter.future;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
}, skip: !Platform.isIOS); }, skip: !Platform.isIOS);
testWidgets('cancel navigation', (WidgetTester tester) async { testWidgets('cancel navigation', (WidgetTester tester) async {
@ -3055,7 +3055,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
ios: IOSInAppWebViewOptions(useOnNavigationResponse: true)), ios: IOSInAppWebViewOptions(useOnNavigationResponse: true)),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
@ -3074,7 +3074,7 @@ setTimeout(function() {
); );
final String url = await onNavigationResponseCompleter.future; final String url = await onNavigationResponseCompleter.future;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
expect(pageLoaded.future, doesNotComplete); expect(pageLoaded.future, doesNotComplete);
}, skip: !Platform.isIOS); }, skip: !Platform.isIOS);
}, skip: !Platform.isIOS); }, skip: !Platform.isIOS);
@ -3374,7 +3374,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev/')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -3387,11 +3387,11 @@ setTimeout(function() {
final InAppWebViewController controller = final InAppWebViewController controller =
await controllerCompleter.future; await controllerCompleter.future;
String? url = await pageLoads.stream.first; String? url = await pageLoads.stream.first;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
await controller.reload(); await controller.reload();
url = await pageLoads.stream.first; url = await pageLoads.stream.first;
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
pageLoads.close(); pageLoads.close();
}); });
@ -3443,6 +3443,7 @@ setTimeout(function() {
expect(webHistory.list![0].url.toString(), 'https://flutter.dev/'); expect(webHistory.list![0].url.toString(), 'https://flutter.dev/');
expect(webHistory.list![1].url.toString(), 'https://github.com/flutter'); expect(webHistory.list![1].url.toString(), 'https://github.com/flutter');
await Future.delayed(Duration(seconds: 1));
await controller.goBack(); await controller.goBack();
url = await pageLoads.stream.first; url = await pageLoads.stream.first;
webHistory = await controller.getCopyBackForwardList(); webHistory = await controller.getCopyBackForwardList();
@ -3456,6 +3457,7 @@ setTimeout(function() {
expect(webHistory.list![0].url.toString(), 'https://flutter.dev/'); expect(webHistory.list![0].url.toString(), 'https://flutter.dev/');
expect(webHistory.list![1].url.toString(), 'https://github.com/flutter'); expect(webHistory.list![1].url.toString(), 'https://github.com/flutter');
await Future.delayed(Duration(seconds: 1));
await controller.goForward(); await controller.goForward();
url = await pageLoads.stream.first; url = await pageLoads.stream.first;
webHistory = await controller.getCopyBackForwardList(); webHistory = await controller.getCopyBackForwardList();
@ -3469,6 +3471,7 @@ setTimeout(function() {
expect(webHistory.list![0].url.toString(), 'https://flutter.dev/'); expect(webHistory.list![0].url.toString(), 'https://flutter.dev/');
expect(webHistory.list![1].url.toString(), 'https://github.com/flutter'); expect(webHistory.list![1].url.toString(), 'https://github.com/flutter');
await Future.delayed(Duration(seconds: 1));
await controller.goTo(historyItem: webHistory.list![0]); await controller.goTo(historyItem: webHistory.list![0]);
url = await pageLoads.stream.first; url = await pageLoads.stream.first;
webHistory = await controller.getCopyBackForwardList(); webHistory = await controller.getCopyBackForwardList();
@ -4392,6 +4395,36 @@ setTimeout(function() {
expect(await InAppWebViewController.getDefaultUserAgent(), isNotNull); expect(await InAppWebViewController.getDefaultUserAgent(), isNotNull);
}); });
testWidgets('launches with pull-to-refresh feature', (WidgetTester tester) async {
final Completer controllerCompleter = Completer<InAppWebViewController>();
final pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.blue,
),
onRefresh: () {
},
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: InAppWebView(
key: GlobalKey(),
initialUrlRequest: URLRequest(url: Uri.parse('https://github.com/flutter')),
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) {
controllerCompleter.complete(controller);
},
),
),
);
final InAppWebViewController controller =
await controllerCompleter.future;
final String? currentUrl = (await controller.getUrl())?.toString();
expect(currentUrl, 'https://github.com/flutter');
});
group('android methods', () { group('android methods', () {
testWidgets('clearSslPreferences', (WidgetTester tester) async { testWidgets('clearSslPreferences', (WidgetTester tester) async {
final Completer controllerCompleter = final Completer controllerCompleter =
@ -4462,7 +4495,7 @@ setTimeout(function() {
child: InAppWebView( child: InAppWebView(
key: GlobalKey(), key: GlobalKey(),
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse('https://flutter.dev')), URLRequest(url: Uri.parse('https://github.com/flutter')),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -4477,7 +4510,7 @@ setTimeout(function() {
await controllerCompleter.future; await controllerCompleter.future;
await pageLoaded.future; await pageLoaded.future;
var originUrl = (await controller.android.getOriginalUrl())?.toString(); var originUrl = (await controller.android.getOriginalUrl())?.toString();
expect(originUrl, 'https://flutter.dev/'); expect(originUrl, 'https://github.com/flutter');
}, skip: !Platform.isAndroid); }, skip: !Platform.isAndroid);
testWidgets('pageDown/pageUp', (WidgetTester tester) async { testWidgets('pageDown/pageUp', (WidgetTester tester) async {
@ -4759,7 +4792,7 @@ setTimeout(function() {
final Completer<void> pageLoaded = Completer<void>(); final Completer<void> pageLoaded = Completer<void>();
var headlessWebView = new HeadlessInAppWebView( var headlessWebView = new HeadlessInAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse("https://flutter.dev")), initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
controllerCompleter.complete(controller); controllerCompleter.complete(controller);
}, },
@ -4774,7 +4807,7 @@ setTimeout(function() {
await pageLoaded.future; await pageLoaded.future;
final String? url = (await controller.getUrl())?.toString(); final String? url = (await controller.getUrl())?.toString();
expect(url, 'https://flutter.dev/'); expect(url, 'https://github.com/flutter');
await headlessWebView.dispose(); await headlessWebView.dispose();
@ -4787,7 +4820,7 @@ setTimeout(function() {
final Completer<void> pageLoaded = Completer<void>(); final Completer<void> pageLoaded = Completer<void>();
var headlessWebView = new HeadlessInAppWebView( var headlessWebView = new HeadlessInAppWebView(
initialUrlRequest: URLRequest(url: Uri.parse("https://flutter.dev")), initialUrlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")),
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(javaScriptEnabled: false)), crossPlatform: InAppWebViewOptions(javaScriptEnabled: false)),
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
@ -4818,7 +4851,7 @@ setTimeout(function() {
}); });
group('InAppBrowser', () { group('InAppBrowser', () {
test('open and close', () async { test('openUrlRequest and close', () async {
var inAppBrowser = new MyInAppBrowser(); var inAppBrowser = new MyInAppBrowser();
expect(inAppBrowser.isOpened(), false); expect(inAppBrowser.isOpened(), false);
expect(() async { expect(() async {
@ -4826,7 +4859,81 @@ setTimeout(function() {
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>())); }, throwsA(isInstanceOf<InAppBrowserNotOpenedException>()));
await inAppBrowser.openUrlRequest( await inAppBrowser.openUrlRequest(
urlRequest: URLRequest(url: Uri.parse("https://flutter.dev"))); urlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")));
await inAppBrowser.browserCreated.future;
expect(inAppBrowser.isOpened(), true);
expect(() async {
await inAppBrowser.openUrlRequest(
urlRequest:
URLRequest(url: Uri.parse("https://flutter.dev")));
}, throwsA(isInstanceOf<InAppBrowserAlreadyOpenedException>()));
await inAppBrowser.firstPageLoaded.future;
var controller = inAppBrowser.webViewController;
final String? url = (await controller.getUrl())?.toString();
expect(url, 'https://github.com/flutter');
await inAppBrowser.close();
expect(inAppBrowser.isOpened(), false);
expect(() async => await inAppBrowser.webViewController.getUrl(),
throwsA(isInstanceOf<MissingPluginException>()));
});
test('openFile and close', () async {
var inAppBrowser = new MyInAppBrowser();
expect(inAppBrowser.isOpened(), false);
expect(() async {
await inAppBrowser.show();
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>()));
await inAppBrowser.openFile(assetFilePath: "test_assets/in_app_webview_initial_file_test.html");
await inAppBrowser.browserCreated.future;
expect(inAppBrowser.isOpened(), true);
expect(() async {
await inAppBrowser.openUrlRequest(
urlRequest:
URLRequest(url: Uri.parse("https://github.com/flutter")));
}, throwsA(isInstanceOf<InAppBrowserAlreadyOpenedException>()));
await inAppBrowser.firstPageLoaded.future;
var controller = inAppBrowser.webViewController;
final String? url = (await controller.getUrl())?.toString();
expect(url, endsWith("in_app_webview_initial_file_test.html"));
await inAppBrowser.close();
expect(inAppBrowser.isOpened(), false);
expect(() async => await inAppBrowser.webViewController.getUrl(),
throwsA(isInstanceOf<MissingPluginException>()));
});
test('openFile and close', () async {
var inAppBrowser = new MyInAppBrowser();
expect(inAppBrowser.isOpened(), false);
expect(() async {
await inAppBrowser.show();
}, throwsA(isInstanceOf<InAppBrowserNotOpenedException>()));
await inAppBrowser.openData(data: """
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://getbootstrap.com/docs/4.3/dist/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<img src="https://via.placeholder.com/100x50" alt="placeholder 100x50">
</body>
</html>
""",
encoding: 'utf-8',
mimeType: 'text/html',
androidHistoryUrl: Uri.parse("https://flutter.dev"),
baseUrl: Uri.parse("https://flutter.dev"));
await inAppBrowser.browserCreated.future; await inAppBrowser.browserCreated.future;
expect(inAppBrowser.isOpened(), true); expect(inAppBrowser.isOpened(), true);
expect(() async { expect(() async {
@ -4850,7 +4957,7 @@ setTimeout(function() {
test('set/get options', () async { test('set/get options', () async {
var inAppBrowser = new MyInAppBrowser(); var inAppBrowser = new MyInAppBrowser();
await inAppBrowser.openUrlRequest( await inAppBrowser.openUrlRequest(
urlRequest: URLRequest(url: Uri.parse("https://flutter.dev")), urlRequest: URLRequest(url: Uri.parse("https://github.com/flutter")),
options: InAppBrowserClassOptions( options: InAppBrowserClassOptions(
crossPlatform: InAppBrowserOptions(hideToolbarTop: true))); crossPlatform: InAppBrowserOptions(hideToolbarTop: true)));
await inAppBrowser.browserCreated.future; await inAppBrowser.browserCreated.future;
@ -4875,12 +4982,12 @@ setTimeout(function() {
var chromeSafariBrowser = new MyChromeSafariBrowser(); var chromeSafariBrowser = new MyChromeSafariBrowser();
expect(chromeSafariBrowser.isOpened(), false); expect(chromeSafariBrowser.isOpened(), false);
await chromeSafariBrowser.open(url: Uri.parse("https://flutter.dev")); await chromeSafariBrowser.open(url: Uri.parse("https://github.com/flutter"));
await chromeSafariBrowser.browserCreated.future; await chromeSafariBrowser.browserCreated.future;
expect(chromeSafariBrowser.isOpened(), true); expect(chromeSafariBrowser.isOpened(), true);
expect(() async { expect(() async {
await chromeSafariBrowser.open( await chromeSafariBrowser.open(
url: Uri.parse("https://github.com/flutter")); url: Uri.parse("https://flutter.dev"));
}, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>())); }, throwsA(isInstanceOf<ChromeSafariBrowserAlreadyOpenedException>()));
await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes); await expectLater(chromeSafariBrowser.firstPageLoaded.future, completes);

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
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';
@ -23,16 +24,21 @@ class MyInAppBrowser extends InAppBrowser {
@override @override
Future onLoadStop(url) async { Future onLoadStop(url) async {
pullToRefreshController?.endRefreshing();
print("\n\nStopped $url\n\n"); print("\n\nStopped $url\n\n");
} }
@override @override
void onLoadError(url, code, message) { void onLoadError(url, code, message) {
pullToRefreshController?.endRefreshing();
print("Can't load $url.. Error: $message"); print("Can't load $url.. Error: $message");
} }
@override @override
void onProgressChanged(progress) { void onProgressChanged(progress) {
if (progress == 100) {
pullToRefreshController?.endRefreshing();
}
print("Progress: $progress"); print("Progress: $progress");
} }
@ -77,9 +83,27 @@ class InAppBrowserExampleScreen extends StatefulWidget {
} }
class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> { class _InAppBrowserExampleScreenState extends State<InAppBrowserExampleScreen> {
late PullToRefreshController pullToRefreshController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.black,
),
onRefresh: () async {
if (Platform.isAndroid) {
widget.browser.webViewController.reload();
} else if (Platform.isIOS) {
widget.browser.webViewController.loadUrl(
urlRequest: URLRequest(url: await widget.browser.webViewController.getUrl()));
}
},
);
widget.browser.pullToRefreshController = pullToRefreshController;
} }
@override @override

View File

@ -17,13 +17,26 @@ class InAppWebViewExampleScreen extends StatefulWidget {
} }
class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> { class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
final GlobalKey webViewKey = GlobalKey(); final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webView; InAppWebViewController? webViewController;
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
));
late PullToRefreshController pullToRefreshController;
late ContextMenu contextMenu; late ContextMenu contextMenu;
String url = ""; String url = "";
double progress = 0; double progress = 0;
// CookieManager _cookieManager = CookieManager.instance(); final urlController = TextEditingController();
@override @override
void initState() { void initState() {
@ -37,15 +50,15 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
title: "Special", title: "Special",
action: () async { action: () async {
print("Menu item Special clicked!"); print("Menu item Special clicked!");
print(await webView?.getSelectedText()); print(await webViewController?.getSelectedText());
await webView?.clearFocus(); await webViewController?.clearFocus();
}) })
], ],
options: ContextMenuOptions(hideDefaultSystemContextMenuItems: false), options: ContextMenuOptions(hideDefaultSystemContextMenuItems: false),
onCreateContextMenu: (hitTestResult) async { onCreateContextMenu: (hitTestResult) async {
print("onCreateContextMenu"); print("onCreateContextMenu");
print(hitTestResult.extra); print(hitTestResult.extra);
print(await webView?.getSelectedText()); print(await webViewController?.getSelectedText());
}, },
onHideContextMenu: () { onHideContextMenu: () {
print("onHideContextMenu"); print("onHideContextMenu");
@ -59,6 +72,20 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
" " + " " +
contextMenuItemClicked.title); contextMenuItemClicked.title);
}); });
pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.blue,
),
onRefresh: () async {
if (Platform.isAndroid) {
webViewController?.reload();
} else if (Platform.isIOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(url: await webViewController?.getUrl()));
}
},
);
} }
@override @override
@ -66,18 +93,6 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
super.dispose(); super.dispose();
} }
var options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: false,
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -85,37 +100,40 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
drawer: myDrawer(context: context), drawer: myDrawer(context: context),
body: SafeArea( body: SafeArea(
child: Column(children: <Widget>[ child: Column(children: <Widget>[
Container( TextField(
padding: EdgeInsets.all(20.0), decoration: InputDecoration(
child: Text( prefixIcon: Icon(Icons.search)
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"), ),
controller: urlController,
keyboardType: TextInputType.url,
onSubmitted: (value) {
var url = Uri.parse(value);
if (url.scheme.isEmpty) {
url = Uri.parse("https://www.google.com/search?q=" + value);
}
webViewController?.loadUrl(
urlRequest: URLRequest(url: url));
},
), ),
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0
? LinearProgressIndicator(value: progress)
: Container()),
Expanded( Expanded(
child: Container( child: Stack(
margin: const EdgeInsets.all(10.0), children: [
decoration: InAppWebView(
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
key: webViewKey, key: webViewKey,
// contextMenu: contextMenu, // contextMenu: contextMenu,
initialUrlRequest: initialUrlRequest:
URLRequest(url: Uri.parse("https://flutter.dev")), URLRequest(url: Uri.parse("https://github.com/flutter")),
// initialFile: "assets/index.html", // initialFile: "assets/index.html",
initialUserScripts: UnmodifiableListView<UserScript>([]), initialUserScripts: UnmodifiableListView<UserScript>([]),
initialOptions: options, initialOptions: options,
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) { onWebViewCreated: (controller) {
webView = controller; webViewController = controller;
print("onWebViewCreated");
}, },
onLoadStart: (controller, url) { onLoadStart: (controller, url) {
print("onLoadStart $url");
setState(() { setState(() {
this.url = url.toString(); this.url = url.toString();
urlController.text = this.url;
}); });
}, },
androidOnPermissionRequest: (InAppWebViewController controller, androidOnPermissionRequest: (InAppWebViewController controller,
@ -149,27 +167,38 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
return NavigationActionPolicy.ALLOW; return NavigationActionPolicy.ALLOW;
}, },
onLoadStop: (controller, url) async { onLoadStop: (controller, url) async {
print("onLoadStop $url"); pullToRefreshController.endRefreshing();
setState(() { setState(() {
this.url = url.toString(); this.url = url.toString();
urlController.text = this.url;
}); });
webView = controller; },
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();
}, },
onProgressChanged: (controller, progress) { onProgressChanged: (controller, progress) {
if (progress == 100) {
pullToRefreshController.endRefreshing();
}
setState(() { setState(() {
this.progress = progress / 100; this.progress = progress / 100;
urlController.text = this.url;
}); });
}, },
onUpdateVisitedHistory: (controller, url, androidIsReload) { onUpdateVisitedHistory: (controller, url, androidIsReload) {
print("onUpdateVisitedHistory $url");
setState(() { setState(() {
this.url = url.toString(); this.url = url.toString();
urlController.text = this.url;
}); });
}, },
onConsoleMessage: (controller, consoleMessage) { onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage); print(consoleMessage);
}, },
), ),
progress < 1.0
? LinearProgressIndicator(value: progress)
: Container(),
],
), ),
), ),
ButtonBar( ButtonBar(
@ -178,19 +207,19 @@ class _InAppWebViewExampleScreenState extends State<InAppWebViewExampleScreen> {
ElevatedButton( ElevatedButton(
child: Icon(Icons.arrow_back), child: Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
webView?.goBack(); webViewController?.goBack();
}, },
), ),
ElevatedButton( ElevatedButton(
child: Icon(Icons.arrow_forward), child: Icon(Icons.arrow_forward),
onPressed: () { onPressed: () {
webView?.goForward(); webViewController?.goForward();
}, },
), ),
ElevatedButton( ElevatedButton(
child: Icon(Icons.refresh), child: Icon(Icons.refresh),
onPressed: () { onPressed: () {
webView?.reload(); webViewController?.reload();
}, },
), ),
], ],

View File

@ -22,7 +22,7 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
flutter_downloader: ^1.5.2 flutter_downloader: ^1.5.2
path_provider: ^2.0.0-nullsafety path_provider: ^2.0.0-nullsafety
permission_handler: ^5.0.1+1 permission_handler: ^5.1.0+2
url_launcher: ^6.0.0-nullsafety.4 url_launcher: ^6.0.0-nullsafety.4
# connectivity: ^0.4.5+6 # connectivity: ^0.4.5+6
flutter_inappwebview: flutter_inappwebview:

View File

@ -36,38 +36,8 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
let arguments = call.arguments as? NSDictionary let arguments = call.arguments as? NSDictionary
switch call.method { switch call.method {
case "openUrlRequest": case "open":
let id = arguments!["id"] as! String open(arguments: arguments!)
let urlRequest = arguments!["urlRequest"] as! [String:Any?]
let options = arguments!["options"] as! [String: Any?]
let contextMenu = arguments!["contextMenu"] as! [String: Any]
let windowId = arguments!["windowId"] as? Int64
let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]]
openUrlRequest(id: id, urlRequest: urlRequest, options: options, contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts)
result(true)
break
case "openFile":
let id = arguments!["id"] as! String
let assetFilePath = arguments!["assetFilePath"] as! String
let options = arguments!["options"] as! [String: Any?]
let contextMenu = arguments!["contextMenu"] as! [String: Any]
let windowId = arguments!["windowId"] as? Int64
let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]]
openFile(id: id, assetFilePath: assetFilePath, options: options, contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts)
result(true)
break
case "openData":
let id = arguments!["id"] as! String
let options = arguments!["options"] as! [String: Any?]
let data = arguments!["data"] as! String
let mimeType = arguments!["mimeType"] as! String
let encoding = arguments!["encoding"] as! String
let baseUrl = arguments!["baseUrl"] as! String
let contextMenu = arguments!["contextMenu"] as! [String: Any]
let windowId = arguments!["windowId"] as? Int64
let initialUserScripts = arguments!["initialUserScripts"] as? [[String: Any]]
openData(id: id, options: options, data: data, mimeType: mimeType, encoding: encoding, baseUrl: baseUrl,
contextMenu: contextMenu, windowId: windowId, initialUserScripts: initialUserScripts)
result(true) result(true)
break break
case "openWithSystemBrowser": case "openWithSystemBrowser":
@ -98,37 +68,25 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
return webViewController return webViewController
} }
public func openUrlRequest(id: String, urlRequest: [String:Any?], options: [String: Any?], public func open(arguments: NSDictionary) {
contextMenu: [String: Any], windowId: Int64?, initialUserScripts: [[String: Any]]?) { let id = arguments["id"] as! String
let webViewController = prepareInAppBrowserWebViewController(options: options) let urlRequest = arguments["urlRequest"] as? [String:Any?]
let assetFilePath = arguments["assetFilePath"] as? String
webViewController.id = id let data = arguments["data"] as? String
webViewController.initialUrlRequest = URLRequest.init(fromPluginMap: urlRequest) let mimeType = arguments["mimeType"] as? String
webViewController.contextMenu = contextMenu let encoding = arguments["encoding"] as? String
webViewController.windowId = windowId let baseUrl = arguments["baseUrl"] as? String
webViewController.initialUserScripts = initialUserScripts ?? [] let options = arguments["options"] as! [String: Any?]
let contextMenu = arguments["contextMenu"] as! [String: Any]
presentViewController(webViewController: webViewController) let windowId = arguments["windowId"] as? Int64
} let initialUserScripts = arguments["initialUserScripts"] as? [[String: Any]]
let pullToRefreshInitialOptions = arguments["pullToRefreshOptions"] as! [String: Any?]
public func openFile(id: String, assetFilePath: String, options: [String: Any?],
contextMenu: [String: Any], windowId: Int64?, initialUserScripts: [[String: Any]]?) {
let webViewController = prepareInAppBrowserWebViewController(options: options) let webViewController = prepareInAppBrowserWebViewController(options: options)
webViewController.id = id webViewController.id = id
webViewController.initialUrlRequest = urlRequest != nil ? URLRequest.init(fromPluginMap: urlRequest!) : nil
webViewController.initialFile = assetFilePath webViewController.initialFile = assetFilePath
webViewController.contextMenu = contextMenu
webViewController.windowId = windowId
webViewController.initialUserScripts = initialUserScripts ?? []
presentViewController(webViewController: webViewController)
}
public func openData(id: String, options: [String: Any?], data: String, mimeType: String, encoding: String,
baseUrl: String, contextMenu: [String: Any], windowId: Int64?, initialUserScripts: [[String: Any]]?) {
let webViewController = prepareInAppBrowserWebViewController(options: options)
webViewController.id = id
webViewController.initialData = data webViewController.initialData = data
webViewController.initialMimeType = mimeType webViewController.initialMimeType = mimeType
webViewController.initialEncoding = encoding webViewController.initialEncoding = encoding
@ -136,6 +94,7 @@ public class InAppBrowserManager: NSObject, FlutterPlugin {
webViewController.contextMenu = contextMenu webViewController.contextMenu = contextMenu
webViewController.windowId = windowId webViewController.windowId = windowId
webViewController.initialUserScripts = initialUserScripts ?? [] webViewController.initialUserScripts = initialUserScripts ?? []
webViewController.pullToRefreshInitialOptions = pullToRefreshInitialOptions
presentViewController(webViewController: webViewController) presentViewController(webViewController: webViewController)
} }

View File

@ -36,6 +36,7 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
var initialBaseUrl: String? var initialBaseUrl: String?
var previousStatusBarStyle = -1 var previousStatusBarStyle = -1
var initialUserScripts: [[String: Any]] = [] var initialUserScripts: [[String: Any]] = []
var pullToRefreshInitialOptions: [String: Any?] = [:]
var methodCallDelegate: InAppWebViewMethodHandler? var methodCallDelegate: InAppWebViewMethodHandler?
public override func loadView() { public override func loadView() {
@ -64,6 +65,15 @@ public class InAppBrowserWebViewController: UIViewController, InAppBrowserDelega
methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) methodCallDelegate = InAppWebViewMethodHandler(webView: webView!)
channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle)
let pullToRefreshLayoutChannel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_" + id,
binaryMessenger: SwiftFlutterPlugin.instance!.registrar!.messenger())
let pullToRefreshOptions = PullToRefreshOptions()
let _ = pullToRefreshOptions.parse(options: pullToRefreshInitialOptions)
let pullToRefreshControl = PullToRefreshControl(channel: pullToRefreshLayoutChannel, options: pullToRefreshOptions)
webView.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView
pullToRefreshControl.prepare()
prepareWebView() prepareWebView()
progressBar = UIProgressView(progressViewStyle: .bar) progressBar = UIProgressView(progressViewStyle: .bar)

View File

@ -23,13 +23,8 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
self.registrar = registrar self.registrar = registrar
self.viewId = viewId self.viewId = viewId
var channelName = "" channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_" + String(describing: viewId),
if let id = viewId as? Int64 { binaryMessenger: registrar.messenger())
channelName = "com.pichillilorenzo/flutter_inappwebview_" + String(id)
} else if let id = viewId as? String {
channelName = "com.pichillilorenzo/flutter_inappwebview_" + id
}
channel = FlutterMethodChannel(name: channelName, binaryMessenger: registrar.messenger())
myView = UIView(frame: frame) myView = UIView(frame: frame)
myView!.clipsToBounds = true myView!.clipsToBounds = true
@ -41,6 +36,7 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
let contextMenu = args["contextMenu"] as? [String: Any] let contextMenu = args["contextMenu"] as? [String: Any]
let windowId = args["windowId"] as? Int64 let windowId = args["windowId"] as? Int64
let initialUserScripts = args["initialUserScripts"] as? [[String: Any]] let initialUserScripts = args["initialUserScripts"] as? [[String: Any]]
let pullToRefreshInitialOptions = args["pullToRefreshOptions"] as! [String: Any?]
var userScripts: [UserScript] = [] var userScripts: [UserScript] = []
if let initialUserScripts = initialUserScripts { if let initialUserScripts = initialUserScripts {
@ -70,6 +66,15 @@ public class FlutterWebViewController: NSObject, FlutterPlatformView {
methodCallDelegate = InAppWebViewMethodHandler(webView: webView!) methodCallDelegate = InAppWebViewMethodHandler(webView: webView!)
channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle) channel!.setMethodCallHandler(LeakAvoider(delegate: methodCallDelegate!).handle)
let pullToRefreshLayoutChannel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_" + String(describing: viewId),
binaryMessenger: registrar.messenger())
let pullToRefreshOptions = PullToRefreshOptions()
let _ = pullToRefreshOptions.parse(options: pullToRefreshInitialOptions)
let pullToRefreshControl = PullToRefreshControl(channel: pullToRefreshLayoutChannel, options: pullToRefreshOptions)
webView!.pullToRefreshControl = pullToRefreshControl
pullToRefreshControl.delegate = webView!
pullToRefreshControl.prepare()
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
myView!.autoresizesSubviews = true myView!.autoresizesSubviews = true
myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] myView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]

View File

@ -9,16 +9,20 @@ import Flutter
import Foundation import Foundation
import WebKit import WebKit
public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate { public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIGestureRecognizerDelegate, PullToRefreshDelegate {
var windowId: Int64? var windowId: Int64?
var inAppBrowserDelegate: InAppBrowserDelegate? var inAppBrowserDelegate: InAppBrowserDelegate?
var channel: FlutterMethodChannel? var channel: FlutterMethodChannel?
var options: InAppWebViewOptions? var options: InAppWebViewOptions?
var pullToRefreshControl: PullToRefreshControl?
static var sslCertificatesMap: [String: SslCertificate] = [:] // [URL host name : SslCertificate] static var sslCertificatesMap: [String: SslCertificate] = [:] // [URL host name : SslCertificate]
static var credentialsProposed: [URLCredential] = [] static var credentialsProposed: [URLCredential] = []
var lastScrollX: CGFloat = 0 var lastScrollX: CGFloat = 0
var lastScrollY: CGFloat = 0 var lastScrollY: CGFloat = 0
var isPausedTimers = false var isPausedTimers = false
var isPausedTimersCompletionHandler: (() -> Void)? var isPausedTimersCompletionHandler: (() -> Void)?
@ -251,9 +255,18 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
return super.canPerformAction(action, withSender: sender) return super.canPerformAction(action, withSender: sender)
} }
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// fix for pull-to-refresh jittering when the touch drag event is held
if let pullToRefreshControl = pullToRefreshControl,
pullToRefreshControl.shouldCallOnRefresh {
pullToRefreshControl.onRefresh()
}
}
public func prepare() { public func prepare() {
self.scrollView.addGestureRecognizer(self.longPressRecognizer) scrollView.addGestureRecognizer(self.longPressRecognizer)
self.scrollView.addGestureRecognizer(self.recognizerForDisablingContextMenuOnLinks) scrollView.addGestureRecognizer(self.recognizerForDisablingContextMenuOnLinks)
scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [.new, .old], context: nil)
addObserver(self, addObserver(self,
forKeyPath: #keyPath(WKWebView.estimatedProgress), forKeyPath: #keyPath(WKWebView.estimatedProgress),
@ -276,7 +289,6 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
name: UIMenuController.willShowMenuNotification, name: UIMenuController.willShowMenuNotification,
object: nil) object: nil)
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
selector: #selector(onHideContextMenu), selector: #selector(onHideContextMenu),
@ -566,6 +578,12 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
let newTitle = change?[NSKeyValueChangeKey.newKey] as? String let newTitle = change?[NSKeyValueChangeKey.newKey] as? String
onTitleChanged(title: newTitle) onTitleChanged(title: newTitle)
inAppBrowserDelegate?.didChangeTitle(title: newTitle) inAppBrowserDelegate?.didChangeTitle(title: newTitle)
} else if keyPath == #keyPath(UIScrollView.contentOffset) {
let newContentOffset = change?[NSKeyValueChangeKey.newKey] as? CGPoint
let oldContentOffset = change?[NSKeyValueChangeKey.oldKey] as? CGPoint
if scrollView.isDragging || scrollView.isDecelerating || newContentOffset != oldContentOffset {
onScrollChanged()
}
} }
replaceGestureHandlerIfNeeded() replaceGestureHandlerIfNeeded()
} }
@ -1956,7 +1974,15 @@ public class InAppWebView: WKWebView, UIScrollViewDelegate, WKUIDelegate, WKNavi
}) })
} }
public func scrollViewDidScroll(_ scrollView: UIScrollView) { /// UIScrollViewDelegate is somehow bugged:
/// if InAppWebView implements the UIScrollViewDelegate protocol and implement the scrollViewDidScroll event,
/// then, when the user scrolls the content, the webview content is not rendered (just white space).
/// Calling setNeedsLayout() resolves this problem, but, for some reason, the bounce effect is canceled.
///
/// So, to track the same event, without implementing the scrollViewDidScroll event, we create
/// an observer that observes the scrollView.contentOffset property.
/// This way, we don't need to call setNeedsLayout() and all works fine.
public func onScrollChanged() {
let disableVerticalScroll = options?.disableVerticalScroll ?? false let disableVerticalScroll = options?.disableVerticalScroll ?? false
let disableHorizontalScroll = options?.disableHorizontalScroll ?? false let disableHorizontalScroll = options?.disableHorizontalScroll ?? false
if disableVerticalScroll && disableHorizontalScroll { if disableVerticalScroll && disableHorizontalScroll {
@ -2635,6 +2661,23 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
} }
} }
public func enablePullToRefresh() {
if let pullToRefreshControl = pullToRefreshControl {
if #available(iOS 10.0, *) {
scrollView.refreshControl = pullToRefreshControl
} else {
scrollView.addSubview(pullToRefreshControl)
}
}
}
public func disablePullToRefresh() {
pullToRefreshControl?.removeFromSuperview()
if #available(iOS 10.0, *) {
scrollView.refreshControl = nil
}
}
public func dispose() { public func dispose() {
if isPausedTimers, let completionHandler = isPausedTimersCompletionHandler { if isPausedTimers, let completionHandler = isPausedTimersCompletionHandler {
isPausedTimersCompletionHandler = nil isPausedTimersCompletionHandler = nil
@ -2659,12 +2702,16 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) {
for imp in customIMPs { for imp in customIMPs {
imp_removeBlock(imp) imp_removeBlock(imp)
} }
scrollView.removeObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset))
longPressRecognizer.removeTarget(self, action: #selector(longPressGestureDetected)) longPressRecognizer.removeTarget(self, action: #selector(longPressGestureDetected))
longPressRecognizer.delegate = nil longPressRecognizer.delegate = nil
scrollView.removeGestureRecognizer(longPressRecognizer) scrollView.removeGestureRecognizer(longPressRecognizer)
recognizerForDisablingContextMenuOnLinks.removeTarget(self, action: #selector(longPressGestureDetected)) recognizerForDisablingContextMenuOnLinks.removeTarget(self, action: #selector(longPressGestureDetected))
recognizerForDisablingContextMenuOnLinks.delegate = nil recognizerForDisablingContextMenuOnLinks.delegate = nil
scrollView.removeGestureRecognizer(recognizerForDisablingContextMenuOnLinks) scrollView.removeGestureRecognizer(recognizerForDisablingContextMenuOnLinks)
disablePullToRefresh()
pullToRefreshControl?.dispose()
pullToRefreshControl = nil
uiDelegate = nil uiDelegate = nil
navigationDelegate = nil navigationDelegate = nil
scrollView.delegate = nil scrollView.delegate = nil

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
import WebKit import WebKit
class InAppWebViewMethodHandler: FlutterMethodCallDelegate { public class InAppWebViewMethodHandler: FlutterMethodCallDelegate {
var webView: InAppWebView? var webView: InAppWebView?
init(webView: InAppWebView) { init(webView: InAppWebView) {

View File

@ -11,8 +11,8 @@ public class LeakAvoider: NSObject {
weak var delegate : FlutterMethodCallDelegate? weak var delegate : FlutterMethodCallDelegate?
init(delegate: FlutterMethodCallDelegate) { init(delegate: FlutterMethodCallDelegate) {
self.delegate = delegate
super.init() super.init()
self.delegate = delegate
} }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

View File

@ -0,0 +1,113 @@
//
// File.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 03/03/21.
//
import Foundation
import Flutter
public class PullToRefreshControl : UIRefreshControl, FlutterPlugin {
var channel: FlutterMethodChannel?
var options: PullToRefreshOptions?
var shouldCallOnRefresh = false
var delegate: PullToRefreshDelegate?
public init(channel: FlutterMethodChannel?, options: PullToRefreshOptions?) {
super.init()
self.channel = channel
self.options = options
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public static func register(with registrar: FlutterPluginRegistrar) {
}
public func prepare() {
self.channel?.setMethodCallHandler(self.handle)
if let options = options {
if options.enabled {
delegate?.enablePullToRefresh()
}
if let color = options.color, !color.isEmpty {
tintColor = UIColor(hexString: color)
}
if let backgroundTintColor = options.backgroundColor, !backgroundTintColor.isEmpty {
backgroundColor = UIColor(hexString: backgroundTintColor)
}
if let attributedTitleMap = options.attributedTitle {
attributedTitle = NSAttributedString.fromMap(map: attributedTitleMap)
}
}
addTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary
switch call.method {
case "setEnabled":
let enabled = arguments!["enabled"] as! Bool
if enabled {
delegate?.enablePullToRefresh()
} else {
delegate?.disablePullToRefresh()
}
result(true)
break
case "setRefreshing":
let refreshing = arguments!["refreshing"] as! Bool
if refreshing {
self.beginRefreshing()
} else {
self.endRefreshing()
}
result(true)
break
case "setColor":
let color = arguments!["color"] as! String
tintColor = UIColor(hexString: color)
result(true)
break
case "setBackgroundColor":
let color = arguments!["color"] as! String
backgroundColor = UIColor(hexString: color)
result(true)
break
case "setAttributedTitle":
let attributedTitleMap = arguments!["attributedTitle"] as! [String: Any?]
attributedTitle = NSAttributedString.fromMap(map: attributedTitleMap)
result(true)
break
default:
result(FlutterMethodNotImplemented)
break
}
}
public func onRefresh() {
shouldCallOnRefresh = false
let arguments: [String: Any?] = [:]
self.channel?.invokeMethod("onRefresh", arguments: arguments)
}
@objc public func updateShouldCallOnRefresh() {
shouldCallOnRefresh = true
}
public func dispose() {
channel?.setMethodCallHandler(nil)
removeTarget(self, action: #selector(updateShouldCallOnRefresh), for: .valueChanged)
delegate = nil
}
deinit {
print("PullToRefreshControl - dealloc")
}
}

View File

@ -0,0 +1,13 @@
//
// PullToRefreshDelegate.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 04/03/21.
//
import Foundation
public protocol PullToRefreshDelegate {
func enablePullToRefresh()
func disablePullToRefresh()
}

View File

@ -0,0 +1,33 @@
//
// PullToRefreshOptions.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 03/03/21.
//
import Foundation
public class PullToRefreshOptions : Options<PullToRefreshControl> {
var enabled = true
var color: String?
var backgroundColor: String?
var attributedTitle: [String: Any?]?
override init(){
super.init()
}
override func parse(options: [String: Any?]) -> PullToRefreshOptions {
let _ = super.parse(options: options)
if let attributedTitle = options["attributedTitle"] as? [String: Any?] {
self.attributedTitle = attributedTitle
}
return self
}
override func getRealOptions(obj: PullToRefreshControl?) -> [String: Any?] {
let realOptions: [String: Any?] = toMap()
return realOptions
}
}

View File

@ -0,0 +1,63 @@
//
// NSAttributedString.swift
// flutter_inappwebview
//
// Created by Lorenzo Pichilli on 05/03/21.
//
import Foundation
extension NSAttributedString {
public static func fromMap(map: [String:Any?]?) -> NSAttributedString? {
guard let map = map, let string = map["string"] as? String else {
return nil
}
var attributes: [NSAttributedString.Key : Any] = [:]
if let backgroundColor = map["backgroundColor"] as? String {
attributes[.backgroundColor] = UIColor(hexString: backgroundColor)
}
if let baselineOffset = map["baselineOffset"] as? Double {
attributes[.baselineOffset] = baselineOffset
}
if let expansion = map["expansion"] as? Double {
attributes[.expansion] = expansion
}
if let foregroundColor = map["foregroundColor"] as? String {
attributes[.foregroundColor] = UIColor(hexString: foregroundColor)
}
if let kern = map["kern"] as? Double {
attributes[.kern] = kern
}
if let ligature = map["ligature"] as? Int64 {
attributes[.ligature] = ligature
}
if let obliqueness = map["obliqueness"] as? Double {
attributes[.obliqueness] = obliqueness
}
if let strikethroughColor = map["strikethroughColor"] as? String {
attributes[.strikethroughColor] = UIColor(hexString: strikethroughColor)
}
if let strikethroughStyle = map["strikethroughStyle"] as? Int64 {
attributes[.strikethroughStyle] = strikethroughStyle
}
if let strokeColor = map["strokeColor"] as? String {
attributes[.strokeColor] = UIColor(hexString: strokeColor)
}
if let strokeWidth = map["strokeWidth"] as? Double {
attributes[.strokeWidth] = strokeWidth
}
if let textEffect = map["textEffect"] as? String {
attributes[.textEffect] = textEffect
}
if let underlineColor = map["underlineColor"] as? String {
attributes[.underlineColor] = UIColor(hexString: underlineColor)
}
if let underlineStyle = map["underlineStyle"] as? Int64 {
attributes[.underlineStyle] = underlineStyle
}
return NSAttributedString(string: string, attributes: attributes)
}
}

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_inappwebview/src/util.dart'; import 'package:flutter_inappwebview/src/util.dart';
import '../context_menu.dart'; import '../context_menu.dart';
@ -40,14 +41,17 @@ class InAppBrowserNotOpenedException implements Exception {
///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. ///View ID.
late String id; late final String id;
///Context menu used by the browser. It should be set before opening the browser. ///Context menu used by the browser. It should be set before opening the browser.
ContextMenu? contextMenu; ContextMenu? contextMenu;
///Represents the pull-to-refresh feature controller.
PullToRefreshController? pullToRefreshController;
///Initial list of user scripts to be loaded at start or end of a page loading. ///Initial list of user scripts to be loaded at start or end of a page loading.
UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
bool _isOpened = false; bool _isOpened = false;
late MethodChannel _channel; late MethodChannel _channel;
@ -68,13 +72,14 @@ class InAppBrowser {
this._channel.setMethodCallHandler(handleMethod); this._channel.setMethodCallHandler(handleMethod);
_isOpened = false; _isOpened = false;
webViewController = new InAppWebViewController.fromInAppBrowser( webViewController = new InAppWebViewController.fromInAppBrowser(
id, this._channel, this, this.initialUserScripts); this._channel, this, this.initialUserScripts);
} }
Future<dynamic> handleMethod(MethodCall call) async { Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) { switch (call.method) {
case "onBrowserCreated": case "onBrowserCreated":
this._isOpened = true; this._isOpened = true;
this.pullToRefreshController?.initMethodChannel(id);
onBrowserCreated(); onBrowserCreated();
break; break;
case "onExit": case "onExit":
@ -106,7 +111,8 @@ class InAppBrowser {
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
await _sharedChannel.invokeMethod('openUrlRequest', args); args.putIfAbsent('pullToRefreshOptions', () => pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap());
await _sharedChannel.invokeMethod('open', args);
} }
///Opens the given [assetFilePath] file in a new [InAppBrowser] instance. ///Opens the given [assetFilePath] file in a new [InAppBrowser] instance.
@ -159,7 +165,8 @@ class InAppBrowser {
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
await _sharedChannel.invokeMethod('openFile', args); args.putIfAbsent('pullToRefreshOptions', () => pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap());
await _sharedChannel.invokeMethod('open', args);
} }
///Opens a new [InAppBrowser] instance with [data] as a content, using [baseUrl] as the base URL for it. ///Opens a new [InAppBrowser] instance with [data] as a content, using [baseUrl] as the base URL for it.
@ -194,7 +201,8 @@ class InAppBrowser {
args.putIfAbsent('windowId', () => windowId); args.putIfAbsent('windowId', () => windowId);
args.putIfAbsent('initialUserScripts', args.putIfAbsent('initialUserScripts',
() => initialUserScripts?.map((e) => e.toMap()).toList() ?? []); () => initialUserScripts?.map((e) => e.toMap()).toList() ?? []);
await _sharedChannel.invokeMethod('openData', args); args.putIfAbsent('pullToRefreshOptions', () => pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap());
await _sharedChannel.invokeMethod('open', args);
} }
///This is a static method that opens an [url] in the system browser. You wont be able to use the [InAppBrowser] methods here! ///This is a static method that opens an [url] in the system browser. You wont be able to use the [InAppBrowser] methods here!

View File

@ -9,13 +9,17 @@ import '../types.dart';
import 'webview.dart'; import 'webview.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
import 'in_app_webview_options.dart'; import 'in_app_webview_options.dart';
import '../pull_to_refresh/pull_to_refresh_controller.dart';
import '../pull_to_refresh/pull_to_refresh_options.dart';
///Class that represents a WebView in headless mode. ///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. ///It can be used to run a WebView in background without attaching an `InAppWebView` to the widget tree.
/// ///
///Remember to dispose it when you don't need it anymore. ///Remember to dispose it when you don't need it anymore.
class HeadlessInAppWebView implements WebView { class HeadlessInAppWebView implements WebView {
late String id; ///View ID.
late final String id;
bool _isDisposed = true; bool _isDisposed = true;
static const MethodChannel _sharedChannel = static const MethodChannel _sharedChannel =
const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview'); const MethodChannel('com.pichillilorenzo/flutter_headless_inappwebview');
@ -85,7 +89,8 @@ class HeadlessInAppWebView implements WebView {
this.initialData, this.initialData,
this.initialOptions, this.initialOptions,
this.contextMenu, this.contextMenu,
this.initialUserScripts}) { this.initialUserScripts,
this.pullToRefreshController}) {
id = ViewIdGenerator.generateId(); id = ViewIdGenerator.generateId();
webViewController = new InAppWebViewController(id, this); webViewController = new InAppWebViewController(id, this);
} }
@ -93,6 +98,7 @@ class HeadlessInAppWebView implements WebView {
Future<dynamic> handleMethod(MethodCall call) async { Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) { switch (call.method) {
case "onHeadlessWebViewCreated": case "onHeadlessWebViewCreated":
pullToRefreshController?.initMethodChannel(id);
if (onWebViewCreated != null) { if (onWebViewCreated != null) {
onWebViewCreated!(webViewController); onWebViewCreated!(webViewController);
} }
@ -123,6 +129,7 @@ class HeadlessInAppWebView implements WebView {
'windowId': this.windowId, 'windowId': this.windowId,
'initialUserScripts': 'initialUserScripts':
this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], this.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshOptions': this.pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap()
}); });
await _sharedChannel.invokeMethod('createHeadlessWebView', args); await _sharedChannel.invokeMethod('createHeadlessWebView', args);
} }
@ -177,6 +184,9 @@ class HeadlessInAppWebView implements WebView {
@override @override
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
@override
final PullToRefreshController? pullToRefreshController;
@override @override
final void Function(InAppWebViewController controller, Uri? url)? final void Function(InAppWebViewController controller, Uri? url)?
onPageCommitVisible; onPageCommitVisible;

View File

@ -8,6 +8,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; 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 'package:flutter_inappwebview/flutter_inappwebview.dart';
import '../context_menu.dart'; import '../context_menu.dart';
import '../types.dart'; import '../types.dart';
@ -15,6 +16,7 @@ import '../types.dart';
import 'webview.dart'; import 'webview.dart';
import 'in_app_webview_controller.dart'; import 'in_app_webview_controller.dart';
import 'in_app_webview_options.dart'; import 'in_app_webview_options.dart';
import '../pull_to_refresh/pull_to_refresh_controller.dart';
///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree. ///Flutter Widget for adding an **inline native WebView** integrated in the flutter widget tree.
class InAppWebView extends StatefulWidget implements WebView { class InAppWebView extends StatefulWidget implements WebView {
@ -38,6 +40,7 @@ class InAppWebView extends StatefulWidget implements WebView {
this.initialData, this.initialData,
this.initialOptions, this.initialOptions,
this.initialUserScripts, this.initialUserScripts,
this.pullToRefreshController,
this.contextMenu, this.contextMenu,
this.onWebViewCreated, this.onWebViewCreated,
this.onLoadStart, this.onLoadStart,
@ -133,6 +136,9 @@ class InAppWebView extends StatefulWidget implements WebView {
@override @override
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
@override
final PullToRefreshController? pullToRefreshController;
@override @override
final ContextMenu? contextMenu; final ContextMenu? contextMenu;
@ -379,6 +385,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? widget.initialUserScripts?.map((e) => e.toMap()).toList() ??
[], [],
'pullToRefreshOptions': widget.pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap()
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
) )
@ -405,6 +412,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'windowId': widget.windowId, 'windowId': widget.windowId,
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshOptions': widget.pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap()
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
@ -425,6 +433,7 @@ class _InAppWebViewState extends State<InAppWebView> {
'windowId': widget.windowId, 'windowId': widget.windowId,
'initialUserScripts': 'initialUserScripts':
widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [], widget.initialUserScripts?.map((e) => e.toMap()).toList() ?? [],
'pullToRefreshOptions': widget.pullToRefreshController?.options.toMap() ?? PullToRefreshOptions(enabled: false).toMap()
}, },
creationParamsCodec: const StandardMessageCodec(), creationParamsCodec: const StandardMessageCodec(),
); );
@ -445,6 +454,7 @@ class _InAppWebViewState extends State<InAppWebView> {
void _onPlatformViewCreated(int id) { void _onPlatformViewCreated(int id) {
_controller = InAppWebViewController(id, widget); _controller = InAppWebViewController(id, widget);
widget.pullToRefreshController?.initMethodChannel(id);
if (widget.onWebViewCreated != null) { if (widget.onWebViewCreated != null) {
widget.onWebViewCreated!(_controller); widget.onWebViewCreated!(_controller);
} }

View File

@ -52,15 +52,9 @@ class InAppWebViewController {
HashMap<String, JavaScriptHandlerCallback>(); HashMap<String, JavaScriptHandlerCallback>();
List<UserScript> _userScripts = []; List<UserScript> _userScripts = [];
// ignore: unused_field
bool _isOpened = false;
// ignore: unused_field // ignore: unused_field
dynamic _id; dynamic _id;
// ignore: unused_field
String? _inAppBrowserUuid;
InAppBrowser? _inAppBrowser; InAppBrowser? _inAppBrowser;
///Android controller that contains only android-specific methods ///Android controller that contains only android-specific methods
@ -84,11 +78,9 @@ class InAppWebViewController {
} }
InAppWebViewController.fromInAppBrowser( InAppWebViewController.fromInAppBrowser(
String uuid,
MethodChannel channel, MethodChannel channel,
InAppBrowser inAppBrowser, InAppBrowser inAppBrowser,
UnmodifiableListView<UserScript>? initialUserScripts) { UnmodifiableListView<UserScript>? initialUserScripts) {
this._inAppBrowserUuid = uuid;
this._channel = channel; this._channel = channel;
this._inAppBrowser = inAppBrowser; this._inAppBrowser = inAppBrowser;
this._userScripts = this._userScripts =

View File

@ -157,7 +157,7 @@ class IOSInAppWebViewOptions
///Set to `true` to enable Apple Pay API for the [WebView] at its first page load or on the next page load (using [InAppWebViewController.setOptions]). The default value is `false`. ///Set to `true` to enable Apple Pay API for the [WebView] at its first page load or on the next page load (using [InAppWebViewController.setOptions]). The default value is `false`.
/// ///
///**IMPORTANT NOTE**: As written in the official [Safari 13 Release Notes](https://developer.apple.com/documentation/safari-release-notes/safari-13-release-notes#Payment-Request-API), ///**IMPORTANT NOTE**: As written in the official [Safari 13 Release Notes](https://developer.apple.com/documentation/safari-release-notes/safari-13-release-notes#Payment-Request-API),
///it won't work if any script injection APIs is used (such as [InAppWebViewController.evaluateJavascript] or [UserScript]). ///it won't work if any script injection APIs are used (such as [InAppWebViewController.evaluateJavascript] or [UserScript]).
///So, when this attribute is `true`, all the methods, options, and events implemented using JavaScript won't be called or won't do anything and the result will always be `null`. ///So, when this attribute is `true`, all the methods, options, and events implemented using JavaScript won't be called or won't do anything and the result will always be `null`.
/// ///
///Methods affected: ///Methods affected:

View File

@ -3,5 +3,6 @@ export 'in_app_webview.dart';
export 'in_app_webview_controller.dart'; export 'in_app_webview_controller.dart';
export 'in_app_webview_options.dart'; export 'in_app_webview_options.dart';
export 'headless_in_app_webview.dart'; export 'headless_in_app_webview.dart';
export '../pull_to_refresh/pull_to_refresh_controller.dart';
export 'android/main.dart'; export 'android/main.dart';
export 'ios/main.dart'; export 'ios/main.dart';

View File

@ -1,6 +1,8 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data'; import 'dart:typed_data';
import '../pull_to_refresh/pull_to_refresh_controller.dart';
import '../context_menu.dart'; import '../context_menu.dart';
import '../types.dart'; import '../types.dart';
@ -642,6 +644,9 @@ abstract class WebView {
///This is a limitation of the native iOS WebKit APIs. ///This is a limitation of the native iOS WebKit APIs.
final UnmodifiableListView<UserScript>? initialUserScripts; final UnmodifiableListView<UserScript>? initialUserScripts;
///Represents the pull-to-refresh feature controller.
final PullToRefreshController? pullToRefreshController;
WebView( WebView(
{this.windowId, {this.windowId,
this.onWebViewCreated, this.onWebViewCreated,
@ -701,5 +706,6 @@ abstract class WebView {
this.initialData, this.initialData,
this.initialOptions, this.initialOptions,
this.contextMenu, this.contextMenu,
this.initialUserScripts}); this.initialUserScripts,
this.pullToRefreshController});
} }

View File

@ -14,3 +14,4 @@ export 'in_app_localhost_server.dart';
export 'content_blocker.dart'; export 'content_blocker.dart';
export 'http_auth_credentials_database.dart'; export 'http_auth_credentials_database.dart';
export 'context_menu.dart'; export 'context_menu.dart';
export 'pull_to_refresh/main.dart';

View File

@ -0,0 +1,2 @@
export 'pull_to_refresh_controller.dart';
export 'pull_to_refresh_options.dart';

View File

@ -0,0 +1,130 @@
import 'dart:ui';
import 'package:flutter/services.dart';
import '../in_app_webview/webview.dart';
import '../in_app_browser/in_app_browser.dart';
import '../util.dart';
import '../types.dart';
import 'pull_to_refresh_options.dart';
///A standard controller that can initiate the refreshing of a scroll views contents.
///This should be used whenever the user can refresh the contents of a WebView via a vertical swipe gesture.
///
///All the methods should be called only when the WebView has been created or is already running
///(for example [WebView.onWebViewCreated] or [InAppBrowser.onBrowserCreated]).
class PullToRefreshController {
late PullToRefreshOptions options;
MethodChannel? _channel;
///Event called when a swipe gesture triggers a refresh.
final void Function()? onRefresh;
PullToRefreshController({PullToRefreshOptions? options, this.onRefresh}) {
this.options = options ?? PullToRefreshOptions();
}
Future<dynamic> handleMethod(MethodCall call) async {
switch (call.method) {
case "onRefresh":
if (onRefresh != null)
onRefresh!();
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
return null;
}
///Sets whether the pull-to-refresh feature is enabled or not.
Future<void> setEnabled(bool enabled) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('enabled', () => enabled);
await _channel?.invokeMethod('setEnabled', args);
}
Future<void> _setRefreshing(bool refreshing) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('refreshing', () => refreshing);
await _channel?.invokeMethod('setRefreshing', args);
}
///Tells the controller that a refresh operation was started programmatically.
///
///Call this method when an external event source triggers a programmatic refresh of your scrolling view.
///This method updates the state of the refresh control to reflect the in-progress refresh operation.
///When the refresh operation ends, be sure to call the [endRefreshing] method to return the controller to its default state.
Future<void> beginRefreshing() async {
return await _setRefreshing(true);
}
///Tells the controller that a refresh operation has ended.
///
///Call this method at the end of any refresh operation (whether it was initiated programmatically or by the user)
///to return the refresh control to its default state.
///If the refresh control is at least partially visible, calling this method also hides it.
///If animations are also enabled, the control is hidden using an animation.
Future<void> endRefreshing() async {
await _setRefreshing(false);
}
///Returns whether a refresh operation has been triggered and is in progress.
Future<bool> isRefreshing() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel?.invokeMethod('isRefreshing', args);
}
///Sets the color of the refresh control.
Future<void> setColor(Color color) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('color', () => color.toHex());
await _channel?.invokeMethod('setColor', args);
}
///Sets the background color of the refresh control.
Future<void> setBackgroundColor(Color color) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('color', () => color.toHex());
await _channel?.invokeMethod('setBackgroundColor', args);
}
///Set the distance to trigger a sync in dips.
///
///**NOTE**: Available only on Android.
Future<void> setDistanceToTriggerSync(int distanceToTriggerSync) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('distanceToTriggerSync', () => distanceToTriggerSync);
await _channel?.invokeMethod('setDistanceToTriggerSync', args);
}
///Sets the distance that the refresh indicator can be pulled beyond its resting position during a swipe gesture.
///
///**NOTE**: Available only on Android.
Future<void> setSlingshotDistance(int slingshotDistance) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('slingshotDistance', () => slingshotDistance);
await _channel?.invokeMethod('setSlingshotDistance', args);
}
///Gets the default distance that the refresh indicator can be pulled beyond its resting position during a swipe gesture.
///
///**NOTE**: Available only on Android.
Future<int> getDefaultSlingshotDistance() async {
Map<String, dynamic> args = <String, dynamic>{};
return await _channel?.invokeMethod('getDefaultSlingshotDistance', args);
}
///Sets the styled title text to display in the refresh control.
///
///**NOTE**: Available only on iOS.
Future<void> setAttributedTitle(IOSNSAttributedString attributedTitle) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('attributedTitle', () => attributedTitle.toMap());
await _channel?.invokeMethod('setAttributedTitle', args);
}
void initMethodChannel(dynamic id) {
this._channel =
MethodChannel('com.pichillilorenzo/flutter_inappwebview_pull_to_refresh_$id');
this._channel?.setMethodCallHandler(handleMethod);
}
}

View File

@ -0,0 +1,58 @@
import 'dart:ui';
import '../util.dart';
import '../types.dart';
class PullToRefreshOptions {
///Sets whether the pull-to-refresh feature is enabled or not.
bool enabled;
///The color of the refresh control.
Color? color;
///The background color of the refresh control.
Color? backgroundColor;
///The distance to trigger a sync in dips.
///
///**NOTE**: Available only on Android.
int? distanceToTriggerSync;
///The distance in pixels that the refresh indicator can be pulled beyond its resting position.
///
///**NOTE**: Available only on Android.
int? slingshotDistance;
///The title text to display in the refresh control.
///
///**NOTE**: Available only on iOS.
IOSNSAttributedString? attributedTitle;
PullToRefreshOptions({
this.enabled = true,
this.color,
this.backgroundColor,
this.distanceToTriggerSync,
this.slingshotDistance,
this.attributedTitle
});
Map<String, dynamic> toMap() {
return {
"enabled": enabled,
"color": color?.toHex(),
"backgroundColor": backgroundColor?.toHex(),
"distanceToTriggerSync": distanceToTriggerSync,
"slingshotDistance": slingshotDistance,
"attributedTitle": attributedTitle?.toMap() ?? {}
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}

View File

@ -2,6 +2,7 @@ import 'dart:collection';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:convert'; import 'dart:convert';
import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -13,6 +14,9 @@ import 'in_app_webview/in_app_webview_controller.dart';
import 'http_auth_credentials_database.dart'; import 'http_auth_credentials_database.dart';
import 'cookie_manager.dart'; import 'cookie_manager.dart';
import 'web_storage/web_storage.dart'; import 'web_storage/web_storage.dart';
import 'pull_to_refresh/pull_to_refresh_controller.dart';
import 'pull_to_refresh/pull_to_refresh_options.dart';
import 'util.dart';
///This type represents a callback, added with [InAppWebViewController.addJavaScriptHandler], that listens to post messages sent from JavaScript. ///This type represents a callback, added with [InAppWebViewController.addJavaScriptHandler], that listens to post messages sent from JavaScript.
/// ///
@ -6495,3 +6499,289 @@ class IOSWKWindowFeatures {
return toMap().toString(); return toMap().toString();
} }
} }
///An iOS-specific class that represents a string with associated attributes
///used by the [PullToRefreshController] and [PullToRefreshOptions] classes.
class IOSNSAttributedString {
///The characters for the new object.
String string;
///The color of the background behind the text.
///
///The value of this attribute is a [Color] object.
///Use this attribute to specify the color of the background area behind the text.
///If you do not specify this attribute, no background color is drawn.
Color? backgroundColor;
///The vertical offset for the position of the text.
///
///The value of this attribute is a number containing a floating point value indicating the characters offset from the baseline, in points.
///The default value is `0`.
double? baselineOffset;
///The expansion factor of the text.
///
///The value of this attribute is a number containing a floating point value indicating the log of the expansion factor to be applied to glyphs.
///The default value is `0`, indicating no expansion.
double? expansion;
///The color of the text.
///
///The value of this attribute is a [Color] object.
///Use this attribute to specify the color of the text during rendering.
///If you do not specify this attribute, the text is rendered in black.
Color? foregroundColor;
///The kerning of the text.
///
///The value of this attribute is a number containing a floating-point value.
///This value specifies the number of points by which to adjust kern-pair characters.
///Kerning prevents unwanted space from occurring between specific characters and depends on the font.
///The value `0` means kerning is disabled. The default value for this attribute is `0`.
double? kern;
///The ligature of the text.
///
///The value of this attribute is a number containing an integer.
///Ligatures cause specific character combinations to be rendered using a single custom glyph that corresponds to those characters.
///The value `0` indicates no ligatures. The value `1` indicates the use of the default ligatures.
///The value `2` indicates the use of all ligatures.
///The default value for this attribute is `1`. (Value `2` is unsupported on iOS.)
int? ligature;
///The obliqueness of the text.
///
///The value of this attribute is a number containing a floating point value indicating skew to be applied to glyphs.
///The default value is `0`, indicating no skew.
double? obliqueness;
///The color of the strikethrough.
///
///The value of this attribute is a [Color] object. The default value is `null`, indicating same as foreground color.
Color? strikethroughColor;
///The strikethrough style of the text.
///
///This value indicates whether the text has a line through it and corresponds to one of the constants described in [IOSNSUnderlineStyle].
///The default value for this attribute is [IOSNSUnderlineStyle.STYLE_NONE].
IOSNSUnderlineStyle? strikethroughStyle;
///The color of the stroke.
///
///The value of this parameter is a [Color] object.
///If it is not defined (which is the case by default), it is assumed to be the same as the value of foregroundColor;
///otherwise, it describes the outline color.
Color? strokeColor;
///The width of the stroke.
///
///The value of this attribute is a number containing a floating-point value.
///This value represents the amount to change the stroke width and is specified as a percentage of the font point size.
///Specify `0` (the default) for no additional changes.
///Specify positive values to change the stroke width alone.
///Specify negative values to stroke and fill the text.
///For example, a typical value for outlined text would be `3.0`.
double? strokeWidth;
///The text effect.
///
///The value of this attribute is a [IOSNSAttributedStringTextEffectStyle] object.
///The default value of this property is `null`, indicating no text effect.
IOSNSAttributedStringTextEffectStyle? textEffect;
///The color of the underline.
///
///The value of this attribute is a [Color] object.
///The default value is `null`, indicating same as foreground color.
Color? underlineColor;
///The underline style of the text.
///
///This value indicates whether the text is underlined and corresponds to one of the constants described in [IOSNSUnderlineStyle].
///The default value for this attribute is [IOSNSUnderlineStyle.STYLE_NONE].
IOSNSUnderlineStyle? underlineStyle;
IOSNSAttributedString({
required this.string,
this.backgroundColor,
this.baselineOffset,
this.expansion,
this.foregroundColor,
this.kern,
this.ligature,
this.obliqueness,
this.strikethroughColor,
this.strikethroughStyle,
this.strokeColor,
this.strokeWidth,
this.textEffect,
this.underlineColor,
this.underlineStyle,
});
Map<String, dynamic> toMap() {
return {
"string": this.string,
"backgroundColor": this.backgroundColor?.toHex(),
"baselineOffset": this.baselineOffset,
"expansion": this.expansion,
"foregroundColor": this.foregroundColor?.toHex(),
"kern": this.kern,
"ligature": this.ligature,
"obliqueness": this.obliqueness,
"strikethroughColor": this.strikethroughColor?.toHex(),
"strikethroughStyle": this.strikethroughStyle?.toValue(),
"strokeColor": this.strokeColor?.toHex(),
"strokeWidth": this.strokeWidth,
"textEffect": this.textEffect?.toValue(),
"underlineColor": this.underlineColor?.toHex(),
"underlineStyle": this.underlineStyle?.toValue(),
};
}
Map<String, dynamic> toJson() {
return this.toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///An iOS-specific Class that represents the constants for the underline style and strikethrough style attribute keys.
class IOSNSUnderlineStyle {
final int _value;
const IOSNSUnderlineStyle._internal(this._value);
static final Set<IOSNSUnderlineStyle> values = [
IOSNSUnderlineStyle.STYLE_NONE,
IOSNSUnderlineStyle.SINGLE,
IOSNSUnderlineStyle.THICK,
IOSNSUnderlineStyle.DOUBLE,
IOSNSUnderlineStyle.PATTERN_DOT,
IOSNSUnderlineStyle.PATTERN_DASH,
IOSNSUnderlineStyle.PATTERN_DASH_DOT,
IOSNSUnderlineStyle.PATTERN_DASH_DOT_DOT,
IOSNSUnderlineStyle.BY_WORD,
].toSet();
static IOSNSUnderlineStyle? fromValue(int? value) {
if (value != null) {
try {
return IOSNSUnderlineStyle.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
int toValue() => _value;
@override
String toString() {
switch (_value) {
case 1:
return "SINGLE";
case 2:
return "THICK";
case 9:
return "DOUBLE";
case 256:
return "PATTERN_DOT";
case 512:
return "PATTERN_DASH";
case 768:
return "PATTERN_DASH_DOT";
case 1024:
return "PATTERN_DASH_DOT_DOT";
case 32768:
return "BY_WORD";
case 0:
default:
return "STYLE_NONE";
}
}
///Do not draw a line.
static const STYLE_NONE =
const IOSNSUnderlineStyle._internal(0);
///Draw a single line.
static const SINGLE =
const IOSNSUnderlineStyle._internal(1);
///Draw a thick line.
static const THICK =
const IOSNSUnderlineStyle._internal(2);
///Draw a double line.
static const DOUBLE =
const IOSNSUnderlineStyle._internal(9);
///Draw a line of dots.
static const PATTERN_DOT =
const IOSNSUnderlineStyle._internal(256);
///Draw a line of dashes.
static const PATTERN_DASH =
const IOSNSUnderlineStyle._internal(512);
///Draw a line of alternating dashes and dots.
static const PATTERN_DASH_DOT =
const IOSNSUnderlineStyle._internal(768);
///Draw a line of alternating dashes and two dots.
static const PATTERN_DASH_DOT_DOT =
const IOSNSUnderlineStyle._internal(1024);
///Draw the line only beneath or through words, not whitespace.
static const BY_WORD =
const IOSNSUnderlineStyle._internal(32768);
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
///An iOS-specific Class that represents the supported proxy types.
class IOSNSAttributedStringTextEffectStyle {
final String _value;
const IOSNSAttributedStringTextEffectStyle._internal(this._value);
static final Set<IOSNSAttributedStringTextEffectStyle> values = [
IOSNSAttributedStringTextEffectStyle.LETTERPRESS_STYLE,
].toSet();
static IOSNSAttributedStringTextEffectStyle? fromValue(String? value) {
if (value != null) {
try {
return IOSNSAttributedStringTextEffectStyle.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
String toValue() => _value;
@override
String toString() => _value;
///A graphical text effect that gives glyphs the appearance of letterpress printing, which involves pressing the type into the paper.
static const LETTERPRESS_STYLE =
const IOSNSAttributedStringTextEffectStyle._internal(
"letterpressStyle");
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}

View File

@ -1,6 +1,6 @@
name: flutter_inappwebview name: flutter_inappwebview
description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window. description: A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
version: 5.0.5+3 version: 5.1.0
homepage: https://github.com/pichillilorenzo/flutter_inappwebview homepage: https://github.com/pichillilorenzo/flutter_inappwebview
environment: environment: