Commit 49983cfd authored by Lorenzo Pichilli's avatar Lorenzo Pichilli

added onDownloadStart event, onLoadResourceCustomScheme event and...

added onDownloadStart event, onLoadResourceCustomScheme event and resourceCustomSchemes option, onTargetBlank event and useOnTargetBlank option, code refactoring
parent 5fc1d405
This diff is collapsed.
## 1.3.0
- Merge
- Merge
- Merge
- Merge
- Merge
- Merge
- Merge "Avoid null pointer exception after webview is disposed" [#116](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/116) (thanks to [robsonfingo](https://github.com/robsonfingo))
- Merge "Remove async call in close" [#119](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/119) (thanks to [benfingo](https://github.com/benfingo))
- Merge "Android takeScreenshot does not work properly." [#122](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/122) (thanks to [PauloMelo](https://github.com/PauloMelo))
- Merge "Resolving gradle error." [#144](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/144) (thanks to [Klingens13](https://github.com/Klingens13))
- Merge "Create issue and pull request templates" [#150](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/150) (thanks to [deandreamatias](https://github.com/deandreamatias))
- Merge "Fix abstract method error && swift version error" [#155](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/155) (thanks to [AlexVincent525](https://github.com/AlexVincent525))
- Merge "migrating to swift 5.0" [#162](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/162) (thanks to [fattiger00](https://github.com/fattiger00))
- Merge "Update readme example" [#178](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/178) (thanks to [SebastienBtr](https://github.com/SebastienBtr))
- Added `horizontalScrollBarEnabled` and `verticalScrollBarEnabled` options to enable/disable the corresponding scrollbar of the WebView [#165](https://github.com/pichillilorenzo/flutter_inappbrowser/issues/165)
- Added `onDownloadStart` event: event fires when the WebView recognizes and starts a downloadable file.
- Added `onLoadResourceCustomScheme` event and `resourceCustomSchemes` option to set custom schemes that WebView must handle to load resources
- Added `onTargetBlank` event and `useOnTargetBlank` option to manage links with `target="_blank"`
## 1.2.1
......
......@@ -36,7 +36,7 @@ public class ChromeCustomTabsActivity extends Activity {
options = new ChromeCustomTabsOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options"));
InAppBrowserFlutterPlugin.chromeCustomTabsActivities.put(uuid, this);
InAppBrowserFlutterPlugin.instance.chromeCustomTabsActivities.put(uuid, this);
customTabActivityHelper = new CustomTabActivityHelper();
builder = new CustomTabsIntent.Builder();
......@@ -49,8 +49,8 @@ public class ChromeCustomTabsActivity extends Activity {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", obj);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", obj);
InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserOpened", obj);
InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserLoaded", obj);
}
private void prepareCustomTabs() {
......@@ -86,7 +86,7 @@ public class ChromeCustomTabsActivity extends Activity {
finish();
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserClosed", obj);
InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserClosed", obj);
}
}
......
......@@ -13,6 +13,7 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.flutter.plugin.common.MethodCall;
......
......@@ -24,6 +24,7 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
......@@ -67,7 +68,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
webViewOptions.parse(optionsMap);
webView.options = webViewOptions;
InAppBrowserFlutterPlugin.webViewActivities.put(uuid, this);
InAppBrowserFlutterPlugin.instance.webViewActivities.put(uuid, this);
actionBar = getSupportActionBar();
......@@ -89,7 +90,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onBrowserCreated", obj);
InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onBrowserCreated", obj);
}
......@@ -253,7 +254,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
if (canGoBack())
goBack();
else if (options.closeOnCannotGoBack)
InAppBrowserFlutterPlugin.close(this, uuid, null);
InAppBrowserFlutterPlugin.instance.close(this, uuid, null);
return true;
}
return super.onKeyDown(keyCode, event);
......@@ -352,7 +353,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
}
public void closeButtonClicked(MenuItem item) {
InAppBrowserFlutterPlugin.close(this, uuid, null);
InAppBrowserFlutterPlugin.instance.close(this, uuid, null);
}
public byte[] takeScreenshot() {
......
......@@ -57,16 +57,18 @@ import io.flutter.plugin.common.PluginRegistry.Registrar;
*/
public class InAppBrowserFlutterPlugin implements MethodCallHandler {
public static InAppBrowserFlutterPlugin instance;
public Registrar registrar;
public static MethodChannel channel;
public static Map<String, InAppBrowserActivity> webViewActivities = new HashMap<>();
public static Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>();
public MethodChannel channel;
public Map<String, InAppBrowserActivity> webViewActivities = new HashMap<>();
public Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>();
protected static final String LOG_TAG = "IABFlutterPlugin";
public InAppBrowserFlutterPlugin(Registrar r) {
registrar = r;
channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser");
channel.setMethodCallHandler(this);
}
/**
......@@ -77,8 +79,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
// registrar.activity() may return null because of Flutter's background execution feature
// described here: https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124
if (activity != null) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser");
channel.setMethodCallHandler(new InAppBrowserFlutterPlugin(registrar));
instance = new InAppBrowserFlutterPlugin(registrar);
new MyCookieManager(registrar);
......@@ -599,7 +600,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
return false;
}
public static void close(Activity activity, final String uuid, final Result result) {
public void close(Activity activity, final String uuid, final Result result) {
final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
if (inAppBrowserActivity != null) {
activity.runOnUiThread(new Runnable() {
......
......@@ -6,6 +6,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback;
......@@ -84,6 +85,19 @@ public class InAppWebChromeClient extends WebChromeClient {
decorView.setSystemUiVisibility(3846 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
@Override
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, android.os.Message resultMsg)
{
WebView.HitTestResult result = view.getHitTestResult();
String data = result.getExtra();
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", data);
getChannel().invokeMethod("onTargetBlank", obj);
return false;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Map<String, Object> obj = new HashMap<>();
......@@ -190,6 +204,6 @@ public class InAppWebChromeClient extends WebChromeClient {
}
private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel;
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
}
}
......@@ -2,25 +2,17 @@ package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
......@@ -47,8 +39,6 @@ import io.flutter.plugin.common.PluginRegistry;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import static android.content.Context.DOWNLOAD_SERVICE;
public class InAppWebView extends WebView {
static final String LOG_TAG = "InAppWebView";
......@@ -56,7 +46,7 @@ public class InAppWebView extends WebView {
public PluginRegistry.Registrar registrar;
public InAppBrowserActivity inAppBrowserActivity;
public FlutterWebView flutterWebView;
int id;
public int id;
InAppWebViewClient inAppWebViewClient;
InAppWebChromeClient inAppWebChromeClient;
public InAppWebViewOptions options;
......@@ -137,39 +127,19 @@ public class InAppWebView extends WebView {
inAppWebViewClient = new InAppWebViewClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView);
setWebViewClient(inAppWebViewClient);
activity.registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(final String url, final String userAgent,
final String contentDisposition, final String mimetype,
final long contentLength) {
RequestPermissionHandler.checkAndRun(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, RequestPermissionHandler.REQUEST_CODE_WRITE_EXTERNAL_STORAGE, new Runnable(){
@Override
public void run(){
Log.e(LOG_TAG, url);
Log.e(LOG_TAG, contentDisposition);
Log.e(LOG_TAG, mimetype);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); //Notify client once download is completed!
request.setVisibleInDownloadsUi(true);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
DownloadManager dm = (DownloadManager) activity.getSystemService(DOWNLOAD_SERVICE);
if (dm != null) {
dm.enqueue(request);
//Toast.makeText(getApplicationContext(), "Downloading File: " + filename, //To notify the Client that the file is being downloaded
// Toast.LENGTH_LONG).show();
}
else {
//Toast.makeText(getApplicationContext(), "Cannot Download File: " + filename, //To notify the Client that the file cannot be downloaded
// Toast.LENGTH_LONG).show();
}
Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", url);
getChannel().invokeMethod("onDownloadStart", obj);
}
});
}
......@@ -181,6 +151,7 @@ public class InAppWebView extends WebView {
settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically);
settings.setBuiltInZoomControls(options.builtInZoomControls);
settings.setDisplayZoomControls(options.displayZoomControls);
settings.setSupportMultipleWindows(options.useOnTargetBlank);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
settings.setSafeBrowsingEnabled(options.safeBrowsingEnabled);
......@@ -215,30 +186,21 @@ public class InAppWebView extends WebView {
if (!options.mixedContentMode.isEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (options.mixedContentMode.equals("MIXED_CONTENT_COMPATIBILITY_MODE")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
} else if (options.mixedContentMode.equals("MIXED_CONTENT_ALWAYS_ALLOW")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if (options.mixedContentMode.equals("MIXED_CONTENT_NEVER_ALLOW")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
switch (options.mixedContentMode) {
case "MIXED_CONTENT_COMPATIBILITY_MODE":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
break;
case "MIXED_CONTENT_ALWAYS_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
break;
case "MIXED_CONTENT_NEVER_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
break;
}
}
}
}
private BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//Fetching the download id received with the broadcast
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
//Checking if the received broadcast is for our enqueued download by matching download id
if (1 == id) { // test
//if (downloadID == id) {
//Toast.makeText(MainActivity.this, "Download Completed", Toast.LENGTH_SHORT).show();
}
}
};
public void loadUrl(String url, MethodChannel.Result result) {
if (!url.isEmpty()) {
loadUrl(url);
......@@ -332,7 +294,7 @@ public class InAppWebView extends WebView {
}
public byte[] takeScreenshot() {
float scale = getScale();
float scale = getResources().getDisplayMetrics().density; // getScale();
int height = (int) (getContentHeight() * scale + 0.5);
Bitmap b = Bitmap.createBitmap( getWidth(),
......@@ -384,7 +346,7 @@ public class InAppWebView extends WebView {
if (newOptionsMap.get("domStorageEnabled") != null && options.domStorageEnabled != newOptions.domStorageEnabled)
settings.setDomStorageEnabled(newOptions.domStorageEnabled);
if (newOptionsMap.get("userAgent") != null && options.userAgent != newOptions.userAgent && !newOptions.userAgent.isEmpty())
if (newOptionsMap.get("userAgent") != null && !options.userAgent.equals(newOptions.userAgent) && !newOptions.userAgent.isEmpty())
settings.setUserAgentString(newOptions.userAgent);
if (newOptionsMap.get("clearCache") != null && newOptions.clearCache)
......@@ -415,18 +377,25 @@ public class InAppWebView extends WebView {
}
}
if (newOptionsMap.get("mixedContentMode") != null && options.mixedContentMode != newOptions.mixedContentMode) {
if (newOptionsMap.get("mixedContentMode") != null && !options.mixedContentMode.equals(newOptions.mixedContentMode)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (newOptions.mixedContentMode.equals("MIXED_CONTENT_COMPATIBILITY_MODE")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
} else if (newOptions.mixedContentMode.equals("MIXED_CONTENT_ALWAYS_ALLOW")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if (newOptions.mixedContentMode.equals("MIXED_CONTENT_NEVER_ALLOW")) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
switch (newOptions.mixedContentMode) {
case "MIXED_CONTENT_COMPATIBILITY_MODE":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
break;
case "MIXED_CONTENT_ALWAYS_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
break;
case "MIXED_CONTENT_NEVER_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
break;
}
}
}
if (newOptionsMap.get("useOnTargetBlank") != null && options.useOnTargetBlank != newOptions.useOnTargetBlank)
settings.setSupportMultipleWindows(newOptions.useOnTargetBlank);
options = newOptions;
}
......@@ -562,13 +531,11 @@ public class InAppWebView extends WebView {
}
private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel;
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
}
@Override
public void destroy() {
final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity();
activity.unregisterReceiver(onDownloadComplete);
super.destroy();
}
}
......@@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
......@@ -24,6 +25,7 @@ import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
......@@ -116,54 +118,56 @@ public class InAppWebViewClient extends WebViewClient {
return true;
}
if (url.startsWith(WebView.SCHEME_TEL)) {
try {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(url));
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
}
} else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error with " + url + ": " + e.toString());
if (url != null) {
if (url.startsWith(WebView.SCHEME_TEL)) {
try {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(url));
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
}
} else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error with " + url + ": " + e.toString());
}
}
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
} else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
} else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:" + address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
}
intent.setData(Uri.parse("sms:" + address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
((inAppBrowserActivity != null) ? inAppBrowserActivity : flutterWebView.activity).startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
}
}
......@@ -300,13 +304,36 @@ public class InAppWebViewClient extends WebViewClient {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
InAppWebView webView = (InAppWebView) view;
final String url = request.getUrl().toString();
String scheme = request.getUrl().getScheme();
if (webView.options.resourceCustomSchemes != null && webView.options.resourceCustomSchemes.contains(scheme)) {
final Map<String, Object> obj = new HashMap<>();
if (inAppBrowserActivity != null)
obj.put("uuid", inAppBrowserActivity.uuid);
obj.put("url", url);
obj.put("scheme", scheme);
Util.WaitFlutterResult flutterResult = Util.invokeMethodAndWait(getChannel(), "onLoadResourceCustomScheme", obj);
if (flutterResult.error != null) {
Log.d(LOG_TAG, flutterResult.error);
}
else if (flutterResult.result != null) {
Map<String, String> res = (Map<String, String>) flutterResult.result;
byte[] data = Base64.decode(res.get("base64data"), Base64.DEFAULT);
return new WebResourceResponse(res.get("content-type"), res.get("content-encoding"), new ByteArrayInputStream(data));
}
}
if (!request.getMethod().toLowerCase().equals("get") ||
!(((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).options.useOnLoadResource)) {
return null;
}
final String url = request.getUrl().toString();
try {
Request mRequest = new Request.Builder().url(url).build();
......@@ -399,7 +426,7 @@ public class InAppWebViewClient extends WebViewClient {
}
private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel;
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
}
}
......@@ -2,12 +2,16 @@ package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import com.pichillilorenzo.flutter_inappbrowser.Options;
import java.util.ArrayList;
import java.util.List;
public class InAppWebViewOptions extends Options {
static final String LOG_TAG = "InAppWebViewOptions";
public boolean useShouldOverrideUrlLoading = false;
public boolean useOnLoadResource = false;
public boolean useOnTargetBlank = false;
public boolean clearCache = false;
public String userAgent = "";
public boolean javaScriptEnabled = true;
......@@ -16,6 +20,7 @@ public class InAppWebViewOptions extends Options {
public int textZoom = 100;
public boolean verticalScrollBarEnabled = true;
public boolean horizontalScrollBarEnabled = true;
public List<String> resourceCustomSchemes = new ArrayList<>();
public boolean clearSessionCache = false;
public boolean builtInZoomControls = false;
......
......@@ -51,15 +51,17 @@ public class JavaScriptBridgeInterface {
getChannel().invokeMethod("onCallJsHandler", obj, new MethodChannel.Result() {
@Override
public void success(Object json) {
if (flutterWebView.webView == null) {
InAppWebView webView = (inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView;
if (webView == null) {
// The webview has already been disposed, ignore.
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
flutterWebView.webView.evaluateJavascript("window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];", null);
webView.evaluateJavascript("window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];", null);
}
else {
flutterWebView.webView.loadUrl("javascript:window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];");
webView.loadUrl("javascript:window." + name + "[" + _callHandlerID + "](" + json + "); delete window." + name + "[" + _callHandlerID + "];");
}
}
......@@ -79,6 +81,6 @@ public class JavaScriptBridgeInterface {
}
private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel;
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
}
}
package com.pichillilorenzo.flutter_inappbrowser;
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
public class Util {
......@@ -37,4 +44,53 @@ public class Util {
return ANDROID_ASSET_URL + key;
}
public static WaitFlutterResult invokeMethodAndWait(final MethodChannel channel, final String method, final Object arguments) {
final Map<String, Object> flutterResultMap = new HashMap<>();
flutterResultMap.put("result", null);
flutterResultMap.put("error", null);
flutterResultMap.put("isBlocking", true);
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
channel.invokeMethod(method, arguments, new MethodChannel.Result() {
@Override
public void success(Object result) {
flutterResultMap.put("result", result);
flutterResultMap.put("isBlocking", false);
}
@Override
public void error(String s, String s1, Object o) {
flutterResultMap.put("error", "ERROR: " + s + " " + s1);
flutterResultMap.put("result", o);
flutterResultMap.put("isBlocking", false);
}
@Override
public void notImplemented() {
flutterResultMap.put("isBlocking", false);
}
});
}
});
while((Boolean) flutterResultMap.get("isBlocking")) {
// block until flutter side returns
}
return new WaitFlutterResult(flutterResultMap.get("result"), (String) flutterResultMap.get("error"));
}
public static class WaitFlutterResult {
public Object result;
public String error;
public WaitFlutterResult(Object r, String e) {
result = r;
error = e;
}
}
}
......@@ -6,6 +6,8 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
......@@ -13,7 +15,7 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:name="com.pichillilorenzo.flutterwebviewexample.MyApplication"
android:label="flutter_inappbrowser_example"
android:icon="@mipmap/ic_launcher">
<activity
......@@ -35,5 +37,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<provider
android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
android:authorities="${applicationId}.flutter_downloader.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
</application>
</manifest>
package com.pichillilorenzo.flutterwebviewexample;
import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MyApplication extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
@Override
public void registerWith(PluginRegistry registry) {
GeneratedPluginRegistrant.registerWith(registry);
}
}
......@@ -24,7 +24,7 @@
<main role="main" class="inner cover">
<h1 class="cover-heading">Inline WebView</h1>
<img src="images/flutter-logo.svg" alt="flutter logo">
<img src="my-special-custom-scheme://images/flutter-logo.svg" alt="flutter logo">
<p class="lead">Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.</p>
<p class="lead">
<a href="#" class="btn btn-lg btn-secondary">Learn more</a>
......@@ -33,7 +33,9 @@
<footer class="mastfoot mt-auto">
<div class="inner">
<p>Cover template for <a href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>Cover template for <a target="_blank" href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>Phone link example <a href="tel:1-408-555-5555">1-408-555-5555</a></p>
<p>Email link example <a href="mailto:example@gmail.com">example@gmail.com</a></p>
</div>
</footer>
</div>
......
......@@ -12,6 +12,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
61FF730023634CA10069C557 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 61FF72FF23634CA10069C557 /* libsqlite3.tbd */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
......@@ -40,6 +41,8 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
61FF72FF23634CA10069C557 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
61FF730123634DD10069C557 /* flutter_downloader.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = flutter_downloader.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
......@@ -61,6 +64,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
61FF730023634CA10069C557 /* libsqlite3.tbd in Frameworks */,
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
25A517508F43E58C47090625 /* Pods_Runner.framework in Frameworks */,
......@@ -73,6 +77,8 @@
50BAF1DF19E018DDF2B149B9 /* Frameworks */ = {
isa = PBXGroup;
children = (
61FF730123634DD10069C557 /* flutter_downloader.framework */,
61FF72FF23634CA10069C557 /* libsqlite3.tbd */,
E8D91E403808A7540F18B75D /* Pods_Runner.framework */,
);
name = Frameworks;
......@@ -180,6 +186,11 @@
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = PFP8UV45Y6;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
};
};
};
......@@ -257,12 +268,16 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/flutter_downloader/flutter_downloader.framework",
"${BUILT_PRODUCTS_DIR}/flutter_inappbrowser/flutter_inappbrowser.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_downloader.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_inappbrowser.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
......
import UIKit
import Flutter
import flutter_downloader
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback({(registry: FlutterPluginRegistry) in
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
......@@ -4,11 +4,6 @@
<dict>
<key></key>
<string></string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
......@@ -29,6 +24,16 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
......@@ -46,9 +51,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
\ No newline at end of file
#import "GeneratedPluginRegistrant.h"
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/services.dart' show rootBundle;
class InlineExampleScreen extends StatefulWidget {
@override
......@@ -44,7 +51,7 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
child: Text(
"CURRENT URL\n${(url.length > 50) ? url.substring(0, 50) + "..." : url}"),
),
Container(
Container(
padding: EdgeInsets.all(10.0),
child: progress < 1.0 ? LinearProgressIndicator(value: progress) : Container()
),
......@@ -55,13 +62,15 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView(
//initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1",
initialUrl: "https://flutter.dev/",
//initialFile: "assets/index.html",
//initialUrl: "https://flutter.dev/",
initialFile: "assets/index.html",
initialHeaders: {},
initialOptions: {
//"mediaPlaybackRequiresUserGesture": false,
//"allowsInlineMediaPlayback": true,
//"useShouldOverrideUrlLoading": true,
"useShouldOverrideUrlLoading": true,
"useOnTargetBlank": true,
"resourceCustomSchemes": ["my-special-custom-scheme"],
//"useOnLoadResource": true
},
onWebViewCreated: (InAppWebViewController controller) {
......@@ -104,13 +113,34 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
response.url);
},
onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
print("""
console output:
sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel}
""");
// print("""
// console output:
// sourceURL: ${consoleMessage.sourceURL}
// lineNumber: ${consoleMessage.lineNumber}
// message: ${consoleMessage.message}
// messageLevel: ${consoleMessage.messageLevel}
// """);
},
onDownloadStart: (InAppWebViewController controller, String url) async {
final taskId = await FlutterDownloader.enqueue(
url: url,
savedDir: await _findLocalPath(),
showNotification: true, // show download progress in status bar (for Android)
openFileFromNotification: true, // click on notification to open downloaded file (for Android)
);
},
onLoadResourceCustomScheme: (InAppWebViewController controller, String scheme, String url) async {
if (scheme == "my-special-custom-scheme") {
var bytes = await rootBundle.load("assets/" + url.replaceFirst("my-special-custom-scheme://", "", 0));
var asBase64 = base64.encode(bytes.buffer.asUint8List());
var response = new CustomSchemeResponse(asBase64, "image/svg+xml", "utf-8");
return response;
}
return null;
},
onTargetBlank: (InAppWebViewController controller, String url) {
print("target _blank: " + url);
controller.loadUrl(url);
},
),
),
......@@ -146,4 +176,11 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
),
]));
}
Future<String> _findLocalPath() async {
final directory = Platform.isAndroid
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
return directory.path;
}
}
......@@ -4,11 +4,13 @@ import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
import 'package:flutter_inappbrowser_example/chrome_safari_example.screen.dart';
import 'package:flutter_inappbrowser_example/inline_example.screen.dart';
import 'package:flutter_inappbrowser_example/webview_example.screen.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
// InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Future main() async {
// await localhostServer.start();
await FlutterDownloader.initialize();
runApp(new MyApp());
}
......
......@@ -66,7 +66,16 @@ class MyInappBrowser extends InAppBrowser {
messageLevel: ${consoleMessage.messageLevel}
""");
}
@override
void onDownloadStart(String url) {
print("Download of " + url);
}
@override
Future<CustomSchemeResponse> onLoadResourceCustomScheme(String scheme, String url) async {
print("custom scheme: " + scheme);
}
}
class WebviewExampleScreen extends StatefulWidget {
......@@ -87,7 +96,7 @@ class _WebviewExampleScreenState extends State<WebviewExampleScreen> {
child: new RaisedButton(
onPressed: () {
widget.browser.open(
url: "https://google.com",
url: "https://www.google.com/",
options: {
"useShouldOverrideUrlLoading": true,
"useOnLoadResource": true,
......
......@@ -19,6 +19,8 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
flutter_downloader: ^1.3.2
path_provider: ^1.4.0
dev_dependencies:
flutter_test:
......
......@@ -21,7 +21,6 @@
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/App.framework/flutter_assets/packages" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/flutter_assets/packages" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
......
//
// CustomeSchemeHandler.swift
// flutter_downloader
//
// Created by Lorenzo Pichilli on 25/10/2019.
//
import Flutter
import Foundation
import WebKit
@available(iOS 11.0, *)
class CustomeSchemeHandler : NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
let inAppWebView = webView as! InAppWebView
if let url = urlSchemeTask.request.url, let scheme = url.scheme {
inAppWebView.onLoadResourceCustomScheme(scheme: scheme, url: url.absoluteString, result: {(result) -> Void in
if result is FlutterError {
print((result as! FlutterError).message)
}
else if (result as? NSObject) == FlutterMethodNotImplemented {}
else {
let json: [String: String]
if let r = result {
json = r as! [String: String]
let urlResponse = URLResponse(url: url, mimeType: json["content-type"], expectedContentLength: -1, textEncodingName: json["content-encoding"])
let data = Data(base64Encoded: json["base64data"]!, options: .ignoreUnknownCharacters)
urlSchemeTask.didReceive(urlResponse)
urlSchemeTask.didReceive(data!)
urlSchemeTask.didFinish()
}
}
})
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
}
}
......@@ -9,7 +9,7 @@ import Flutter
import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
private weak var registrar: FlutterPluginRegistrar?
private var registrar: FlutterPluginRegistrar?
init(registrar: FlutterPluginRegistrar?) {
super.init()
......@@ -22,7 +22,10 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let arguments = args as? NSDictionary
let webviewController = FlutterWebViewController(registrar: registrar!, withFrame: frame, viewIdentifier: viewId, arguments: arguments!)
let webviewController = FlutterWebViewController(registrar: registrar!,
withFrame: frame,
viewIdentifier: viewId,
arguments: arguments!)
return webviewController
}
}
This diff is collapsed.
......@@ -12,6 +12,7 @@ public class InAppWebViewOptions: Options {
var useShouldOverrideUrlLoading = false
var useOnLoadResource = false
var useOnTargetBlank = false
var clearCache = false
var userAgent = ""
var javaScriptEnabled = true
......@@ -19,6 +20,7 @@ public class InAppWebViewOptions: Options {
var mediaPlaybackRequiresUserGesture = true
var verticalScrollBarEnabled = true
var horizontalScrollBarEnabled = true
var resourceCustomSchemes: [String] = []
var disallowOverScroll = false
var enableViewportScale = false
......
......@@ -35,8 +35,9 @@ extension Dictionary where Key: ExpressibleByStringLiteral {
public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
static var registrar: FlutterPluginRegistrar?
static var channel: FlutterMethodChannel?
static var instance: SwiftFlutterPlugin?
var registrar: FlutterPluginRegistrar?
var channel: FlutterMethodChannel?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:]
......@@ -45,17 +46,14 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
private var previousStatusBarStyle = -1
public init(with registrar: FlutterPluginRegistrar) {
SwiftFlutterPlugin.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
super.init()
self.registrar = registrar
self.channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(self, channel: channel!)
}
public static func register(with registrar: FlutterPluginRegistrar) {
SwiftFlutterPlugin.registrar = registrar
let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPlugin(with: registrar)
registrar.addMethodCallDelegate(instance, channel: channel)
SwiftFlutterPlugin.instance = SwiftFlutterPlugin(with: registrar)
registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: "com.pichillilorenzo/flutter_inappwebview")
if #available(iOS 11.0, *) {
......@@ -288,7 +286,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var openWithSystemBrowser = (arguments["openWithSystemBrowser"] as? Bool)!
if isLocalFile {
let key = SwiftFlutterPlugin.registrar!.lookupKey(forAsset: url)
let key = self.registrar!.lookupKey(forAsset: url)
let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
if assetURL == nil {
result(FlutterError(code: "InAppBrowserFlutterPlugin", message: url + " asset file cannot be found!", details: nil))
......@@ -677,28 +675,28 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
func onBrowserCreated(uuid: String, webView: WKWebView) {
if let webViewController = self.webViewControllers[uuid] {
SwiftFlutterPlugin.channel!.invokeMethod("onBrowserCreated", arguments: ["uuid": uuid])
self.channel!.invokeMethod("onBrowserCreated", arguments: ["uuid": uuid])
}
}
func onExit(uuid: String) {
SwiftFlutterPlugin.channel!.invokeMethod("onExit", arguments: ["uuid": uuid])
self.channel!.invokeMethod("onExit", arguments: ["uuid": uuid])
}
func onChromeSafariBrowserOpened(uuid: String) {
if self.safariViewControllers[uuid] != nil {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid])
self.channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid])
}
}
func onChromeSafariBrowserLoaded(uuid: String) {
if self.safariViewControllers[uuid] != nil {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid])
self.channel!.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid])
}
}
func onChromeSafariBrowserClosed(uuid: String) {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid])
self.channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid])
}
func safariExit(uuid: String) {
......
This diff is collapsed.
import 'dart:collection';
import 'package:flutter/services.dart';
import 'types.dart' show ListenerCallback;
class ChannelManager {
static const MethodChannel channel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser');
static bool initialized = false;
static final listeners = HashMap<String, ListenerCallback>();
static Future<dynamic> _handleMethod(MethodCall call) async {
String uuid = call.arguments["uuid"];
return await listeners[uuid](call);
}
static void addListener(String key, ListenerCallback callback) {
if (!initialized)
init();
listeners.putIfAbsent(key, () => callback);
}
static void init () {
channel.setMethodCallHandler(_handleMethod);
initialized = true;
}
}
\ No newline at end of file
import 'dart:async';
import 'package:flutter/services.dart';
import 'types.dart';
import 'channel_manager.dart';
import 'in_app_browser.dart';
///ChromeSafariBrowser class.
///
///This class uses native [Chrome Custom Tabs](https://developer.android.com/reference/android/support/customtabs/package-summary) on Android
///and [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) on iOS.
///
///[browserFallback] represents the [InAppBrowser] instance fallback in case [Chrome Custom Tabs]/[SFSafariViewController] is not available.
class ChromeSafariBrowser {
String uuid;
InAppBrowser browserFallback;
bool _isOpened = false;
///Initialize the [ChromeSafariBrowser] instance with an [InAppBrowser] fallback instance or `null`.
ChromeSafariBrowser (bf) {
uuid = uuidGenerator.v4();
browserFallback = bf;
ChannelManager.addListener(uuid, handleMethod);
_isOpened = false;
}
Future<dynamic> handleMethod(MethodCall call) async {
switch(call.method) {
case "onChromeSafariBrowserOpened":
onOpened();
break;
case "onChromeSafariBrowserLoaded":
onLoaded();
break;
case "onChromeSafariBrowserClosed":
onClosed();
this._isOpened = false;
break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
}
}
///Opens an [url] in a new [ChromeSafariBrowser] instance.
///
///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters.
///
///- [options]: Options for the [ChromeSafariBrowser].
///
///- [headersFallback]: The additional header of the [InAppBrowser] instance fallback to be used in the HTTP request for this URL, specified as a map from name to value.
///
///- [optionsFallback]: Options used by the [InAppBrowser] instance fallback.
///
///**Android** supports these options:
///
///- __addShareButton__: Set to `false` if you don't want the default share button. The default value is `true`.
///- __showTitle__: Set to `false` if the title shouldn't be shown in the custom tab. The default value is `true`.
///- __toolbarBackgroundColor__: Set the custom background color of the toolbar.
///- __enableUrlBarHiding__: Set to `true` to enable the url bar to hide as the user scrolls down on the page. The default value is `false`.
///- __instantAppsEnabled__: Set to `true` to enable Instant Apps. The default value is `false`.
///
///**iOS** supports these options:
///
///- __entersReaderIfAvailable__: Set to `true` if Reader mode should be entered automatically when it is available for the webpage. The default value is `false`.
///- __barCollapsingEnabled__: Set to `true` to enable bar collapsing. The default value is `false`.
///- __dismissButtonStyle__: Set the custom style for the dismiss button. The default value is `0 //done`. See [SFSafariViewController.DismissButtonStyle](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller/dismissbuttonstyle) for all the available styles.
///- __preferredBarTintColor__: Set the custom background color of the navigation bar and the toolbar.
///- __preferredControlTintColor__: Set the custom color of the control buttons on the navigation bar and the toolbar.
///- __presentationStyle__: Set the custom modal presentation style when presenting the WebView. The default value is `0 //fullscreen`. See [UIModalPresentationStyle](https://developer.apple.com/documentation/uikit/uimodalpresentationstyle) for all the available styles.
///- __transitionStyle__: Set to the custom transition style when presenting the WebView. The default value is `0 //crossDissolve`. See [UIModalTransitionStyle](https://developer.apple.com/documentation/uikit/uimodaltransitionStyle) for all the available styles.
Future<void> open(String url, {Map<String, dynamic> options = const {}, Map<String, String> headersFallback = const {}, Map<String, dynamic> optionsFallback = const {}}) async {
assert(url != null && url.isNotEmpty);
this.throwIsAlreadyOpened(message: 'Cannot open $url!');
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : '');
args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headersFallback);
args.putIfAbsent('options', () => options);
args.putIfAbsent('optionsFallback', () => optionsFallback);
args.putIfAbsent('isData', () => false);
args.putIfAbsent('useChromeSafariBrowser', () => true);
await ChannelManager.channel.invokeMethod('open', args);
this._isOpened = true;
}
///Event fires when the [ChromeSafariBrowser] is opened.
void onOpened() {
}
///Event fires when the [ChromeSafariBrowser] is loaded.
void onLoaded() {
}
///Event fires when the [ChromeSafariBrowser] is closed.
void onClosed() {
}
bool isOpened() {
return this._isOpened;
}
void throwIsAlreadyOpened({String message = ''}) {
if (this.isOpened()) {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is already opened.']);
}
}
void throwIsNotOpened({String message = ''}) {
if (!this.isOpened()) {
throw Exception(['Error: ${ (message.isEmpty) ? '' : message + ' '}The browser is not opened.']);
}
}
}
\ No newline at end of file
import 'package:flutter/services.dart';
///Manages the cookies used by WebView instances.
///
///**NOTE for iOS**: available from iOS 11.0+.
class CookieManager {
static bool _initialized = false;
static const MethodChannel _channel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser_cookiemanager');
static void _init () {
_channel.setMethodCallHandler(handleMethod);
_initialized = true;
}
static Future<dynamic> handleMethod(MethodCall call) async {
}
///Sets a cookie for the given [url]. Any existing cookie with the same [host], [path] and [name] will be replaced with the new cookie. The cookie being set will be ignored if it is expired.
///
///The default value of [path] is `"/"`.
///If [domain] is `null`, its default value will be the domain name of [url].
static Future<void> setCookie(String url, String name, String value,
{ String domain,
String path = "/",
int expiresDate,
int maxAge,
bool isSecure }) async {
if (!_initialized)
_init();
if (domain == null)
domain = getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);
assert(value != null && value.isNotEmpty);
assert(domain != null && domain.isNotEmpty);
assert(path != null && path.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('name', () => name);
args.putIfAbsent('value', () => value);
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
args.putIfAbsent('expiresDate', () => expiresDate?.toString());
args.putIfAbsent('maxAge', () => maxAge);
args.putIfAbsent('isSecure', () => isSecure);
await _channel.invokeMethod('setCookie', args);
}
///Gets all the cookies for the given [url].
static Future<List<Map<String, dynamic>>> getCookies(String url) async {
if (!_initialized)
_init();
assert(url != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
List<dynamic> cookies = await _channel.invokeMethod('getCookies', args);
cookies = cookies.cast<Map<dynamic, dynamic>>();
for(var i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].cast<String, dynamic>();
}
cookies = cookies.cast<Map<String, dynamic>>();
return cookies;
}
///Gets a cookie by its [name] for the given [url].
static Future<Map<String, dynamic>> getCookie(String url, String name) async {
if (!_initialized)
_init();
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
List<dynamic> cookies = await _channel.invokeMethod('getCookies', args);
cookies = cookies.cast<Map<dynamic, dynamic>>();
for(var i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].cast<String, dynamic>();
if (cookies[i]["name"] == name)
return cookies[i];
}
return null;
}
///Removes a cookie by its [name] for the given [url], [domain] and [path].
///
///The default value of [path] is `"/"`.
///If [domain] is `null` or empty, its default value will be the domain name of [url].
static Future<void> deleteCookie(String url, String name, {String domain = "", String path = "/"}) async {
if (!_initialized)
_init();
if (domain == null || domain.isEmpty)
domain = getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(name != null && name.isNotEmpty);
assert(domain != null && url.isNotEmpty);
assert(path != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('name', () => name);
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
await _channel.invokeMethod('deleteCookie', args);
}
///Removes all cookies for the given [url], [domain] and [path].
///
///The default value of [path] is `"/"`.
///If [domain] is `null` or empty, its default value will be the domain name of [url].
static Future<void> deleteCookies(String url, {String domain = "", String path = "/"}) async {
if (!_initialized)
_init();
if (domain == null || domain.isEmpty)
domain = getDomainName(url);
assert(url != null && url.isNotEmpty);
assert(domain != null && url.isNotEmpty);
assert(path != null && url.isNotEmpty);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('url', () => url);
args.putIfAbsent('domain', () => domain);
args.putIfAbsent('path', () => path);
await _channel.invokeMethod('deleteCookies', args);
}
///Removes all cookies.
static Future<void> deleteAllCookies() async {
if (!_initialized)
_init();
Map<String, dynamic> args = <String, dynamic>{};
await _channel.invokeMethod('deleteAllCookies', args);
}
static String getDomainName(String url) {
Uri uri = Uri.parse(url);
String domain = uri.host;
if (domain == null)
return "";
return domain.startsWith("www.") ? domain.substring(4) : domain;
}
}
\ No newline at end of file
This diff is collapsed.
import 'dart:io';
import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;
import 'package:mime/mime.dart';
///InAppLocalhostServer class.
///
///This class allows you to create a simple server on `http://localhost:[port]/` in order to be able to load your assets file on a server. The default [port] value is `8080`.
class InAppLocalhostServer {
HttpServer _server;
int _port = 8080;
InAppLocalhostServer({int port = 8080}) {
this._port = port;
}
///Starts a server on http://localhost:[port]/.
///
///**NOTE for iOS**: For the iOS Platform, you need to add the `NSAllowsLocalNetworking` key with `true` in the `Info.plist` file (See [ATS Configuration Basics](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35)):
///```xml
///<key>NSAppTransportSecurity</key>
///<dict>
/// <key>NSAllowsLocalNetworking</key>
/// <true/>
///</dict>
///```
///The `NSAllowsLocalNetworking` key is available since **iOS 10**.
Future<void> start() async {
if (this._server != null) {
throw Exception('Server already started on http://localhost:$_port');
}
var completer = new Completer();
runZoned(() {
HttpServer.bind('127.0.0.1', _port).then((server) {
print('Server running on http://localhost:' + _port.toString());
this._server = server;
server.listen((HttpRequest request) async {
var body = List<int>();
var path = request.requestedUri.path;
path = (path.startsWith('/')) ? path.substring(1) : path;
path += (path.endsWith('/')) ? 'index.html' : '';
try {
body = (await rootBundle.load(path))
.buffer.asUint8List();
} catch (e) {
print(e.toString());
request.response.close();
return;
}
var contentType = ['text', 'html'];
if (!request.requestedUri.path.endsWith('/') && request.requestedUri.pathSegments.isNotEmpty) {
var mimeType = lookupMimeType(request.requestedUri.path, headerBytes: body);
if (mimeType != null) {
contentType = mimeType.split('/');
}
}
request.response.headers.contentType = new ContentType(contentType[0], contentType[1], charset: 'utf-8');
request.response.add(body);
request.response.close();
});
completer.complete();
});
}, onError: (e, stackTrace) => print('Error: $e $stackTrace'));
return completer.future;
}
///Closes the server.
Future<void> close() async {
if (this._server != null) {
await this._server.close(force: true);
print('Server running on http://localhost:$_port closed');
this._server = null;
}
}
}
\ No newline at end of file
This diff is collapsed.
import 'dart:typed_data';
import 'package:uuid/uuid.dart';
import 'package:flutter/services.dart';
import 'in_app_webview.dart' show InAppWebViewController;
var uuidGenerator = new Uuid();
typedef Future<dynamic> ListenerCallback(MethodCall call);
///This type represents a callback, added with [addJavaScriptHandler], that listens to post messages sent from JavaScript.
///
///The Android implementation uses [addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)).
///The iOS implementation uses [addScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-addscriptmessagehandler?language=objc)
///
///The JavaScript function that can be used to call the handler is `window.flutter_inappbrowser.callHandler(handlerName <String>, ...args);`, where `args` are [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
///The `args` will be stringified automatically using `JSON.stringify(args)` method and then they will be decoded on the Dart side.
///
///Also, a [JavaScriptHandlerCallback] can return json data to the JavaScript side.
///In this case, simply return data that you want to send and it will be automatically json encoded using [jsonEncode] from the `dart:convert` library.
typedef dynamic JavaScriptHandlerCallback(List<dynamic> arguments);
///Enum representing the level of a console message.
enum ConsoleMessageLevel {
DEBUG, ERROR, LOG, TIP, WARNING
}
///Public class representing a resource request of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceRequest {
String url;
Map<String, String> headers;
String method;
WebResourceRequest(this.url, this.headers, this.method);
}
///Public class representing a resource response of the [InAppBrowser] WebView.
///It is used by the method [InAppBrowser.onLoadResource()].
class WebResourceResponse {
String url;
Map<String, String> headers;
int statusCode;
int startTime;
int duration;
Uint8List data;
WebResourceResponse(this.url, this.headers, this.statusCode, this.startTime, this.duration, this.data);
}
///Public class representing the response returned by the [onLoadResourceCustomScheme()] event of [InAppWebView].
///It allows to load a specific resource. The resource data must be encoded to `base64`.
class CustomSchemeResponse {
String base64data;
String contentType;
String contentEnconding;
CustomSchemeResponse(this.base64data, this.contentType, this.contentEnconding);
Map<String, dynamic> toJson() {
return {
'content-type': this.contentType,
'content-encoding': this.contentEnconding,
'base64data': this.base64data
};
}
}
///Public class representing a JavaScript console message from WebCore.
///This could be a issued by a call to one of the console logging functions (e.g. console.log('...')) or a JavaScript error on the page.
///
///To receive notifications of these messages, override the [InAppBrowser.onConsoleMessage()] function.
class ConsoleMessage {
String sourceURL = "";
int lineNumber = 1;
String message = "";
ConsoleMessageLevel messageLevel = ConsoleMessageLevel.LOG;
ConsoleMessage(this.sourceURL, this.lineNumber, this.message, this.messageLevel);
}
typedef onWebViewCreatedCallback = void Function(InAppWebViewController controller);
typedef onWebViewLoadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadStopCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadErrorCallback = void Function(InAppWebViewController controller, String url, int code, String message);
typedef onWebViewProgressChangedCallback = void Function(InAppWebViewController controller, int progress);
typedef onWebViewConsoleMessageCallback = void Function(InAppWebViewController controller, ConsoleMessage consoleMessage);
typedef shouldOverrideUrlLoadingCallback = void Function(InAppWebViewController controller, String url);
typedef onWebViewLoadResourceCallback = void Function(InAppWebViewController controller, WebResourceResponse response, WebResourceRequest request);
typedef onWebViewScrollChangedCallback = void Function(InAppWebViewController controller, int x, int y);
typedef onDownloadStartCallback = void Function(InAppWebViewController controller, String url);
typedef onLoadResourceCustomSchemeCallback = Future<CustomSchemeResponse> Function(InAppWebViewController controller, String scheme, String url);
typedef onTargetBlankCallback = void Function(InAppWebViewController controller, String url);
\ No newline at end of file
///WebHistory class.
///
///This class contains a snapshot of the current back/forward list for a WebView.
class WebHistory {
List<WebHistoryItem> _list;
///List of all [WebHistoryItem]s.
List<WebHistoryItem> get list => _list;
///Index of the current [WebHistoryItem].
int currentIndex;
WebHistory(this._list, this.currentIndex);
}
///WebHistoryItem class.
///
///A convenience class for accessing fields in an entry in the back/forward list of a WebView. Each WebHistoryItem is a snapshot of the requested history item.
class WebHistoryItem {
///Original url of this history item.
String originalUrl;
///Document title of this history item.
String title;
///Url of this history item.
String url;
///0-based position index in the back-forward [WebHistory.list].
int index;
///Position offset respect to the currentIndex of the back-forward [WebHistory.list].
int offset;
WebHistoryItem(this.originalUrl, this.title, this.url, this.index, this.offset);
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment