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 ## 1.3.0
- 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 - Merge "Remove async call in close" [#119](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/119) (thanks to [benfingo](https://github.com/benfingo))
- Merge - Merge "Android takeScreenshot does not work properly." [#122](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/122) (thanks to [PauloMelo](https://github.com/PauloMelo))
- Merge - Merge "Resolving gradle error." [#144](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/144) (thanks to [Klingens13](https://github.com/Klingens13))
- Merge - Merge "Create issue and pull request templates" [#150](https://github.com/pichillilorenzo/flutter_inappbrowser/pull/150) (thanks to [deandreamatias](https://github.com/deandreamatias))
- Merge - 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 `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 ## 1.2.1
......
...@@ -36,7 +36,7 @@ public class ChromeCustomTabsActivity extends Activity { ...@@ -36,7 +36,7 @@ public class ChromeCustomTabsActivity extends Activity {
options = new ChromeCustomTabsOptions(); options = new ChromeCustomTabsOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options")); options.parse((HashMap<String, Object>) b.getSerializable("options"));
InAppBrowserFlutterPlugin.chromeCustomTabsActivities.put(uuid, this); InAppBrowserFlutterPlugin.instance.chromeCustomTabsActivities.put(uuid, this);
customTabActivityHelper = new CustomTabActivityHelper(); customTabActivityHelper = new CustomTabActivityHelper();
builder = new CustomTabsIntent.Builder(); builder = new CustomTabsIntent.Builder();
...@@ -49,8 +49,8 @@ public class ChromeCustomTabsActivity extends Activity { ...@@ -49,8 +49,8 @@ public class ChromeCustomTabsActivity extends Activity {
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid); obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", obj); InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserOpened", obj);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", obj); InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onChromeSafariBrowserLoaded", obj);
} }
private void prepareCustomTabs() { private void prepareCustomTabs() {
...@@ -86,7 +86,7 @@ public class ChromeCustomTabsActivity extends Activity { ...@@ -86,7 +86,7 @@ public class ChromeCustomTabsActivity extends Activity {
finish(); finish();
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid); 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 ...@@ -13,6 +13,7 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
......
...@@ -24,6 +24,7 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions ...@@ -24,6 +24,7 @@ import com.pichillilorenzo.flutter_inappbrowser.InAppWebView.InAppWebViewOptions
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -67,7 +68,7 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -67,7 +68,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
webViewOptions.parse(optionsMap); webViewOptions.parse(optionsMap);
webView.options = webViewOptions; webView.options = webViewOptions;
InAppBrowserFlutterPlugin.webViewActivities.put(uuid, this); InAppBrowserFlutterPlugin.instance.webViewActivities.put(uuid, this);
actionBar = getSupportActionBar(); actionBar = getSupportActionBar();
...@@ -89,7 +90,7 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -89,7 +90,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid); obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onBrowserCreated", obj); InAppBrowserFlutterPlugin.instance.channel.invokeMethod("onBrowserCreated", obj);
} }
...@@ -253,7 +254,7 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -253,7 +254,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
if (canGoBack()) if (canGoBack())
goBack(); goBack();
else if (options.closeOnCannotGoBack) else if (options.closeOnCannotGoBack)
InAppBrowserFlutterPlugin.close(this, uuid, null); InAppBrowserFlutterPlugin.instance.close(this, uuid, null);
return true; return true;
} }
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
...@@ -352,7 +353,7 @@ public class InAppBrowserActivity extends AppCompatActivity { ...@@ -352,7 +353,7 @@ public class InAppBrowserActivity extends AppCompatActivity {
} }
public void closeButtonClicked(MenuItem item) { public void closeButtonClicked(MenuItem item) {
InAppBrowserFlutterPlugin.close(this, uuid, null); InAppBrowserFlutterPlugin.instance.close(this, uuid, null);
} }
public byte[] takeScreenshot() { public byte[] takeScreenshot() {
......
...@@ -57,16 +57,18 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; ...@@ -57,16 +57,18 @@ import io.flutter.plugin.common.PluginRegistry.Registrar;
*/ */
public class InAppBrowserFlutterPlugin implements MethodCallHandler { public class InAppBrowserFlutterPlugin implements MethodCallHandler {
public static InAppBrowserFlutterPlugin instance;
public Registrar registrar; public Registrar registrar;
public static MethodChannel channel; public MethodChannel channel;
public static Map<String, InAppBrowserActivity> webViewActivities = new HashMap<>(); public Map<String, InAppBrowserActivity> webViewActivities = new HashMap<>();
public static Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>(); public Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>();
protected static final String LOG_TAG = "IABFlutterPlugin"; protected static final String LOG_TAG = "IABFlutterPlugin";
public InAppBrowserFlutterPlugin(Registrar r) { public InAppBrowserFlutterPlugin(Registrar r) {
registrar = r; registrar = r;
channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser");
channel.setMethodCallHandler(this);
} }
/** /**
...@@ -77,8 +79,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { ...@@ -77,8 +79,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
// registrar.activity() may return null because of Flutter's background execution feature // 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 // described here: https://medium.com/flutter-io/executing-dart-in-the-background-with-flutter-plugins-and-geofencing-2b3e40a1a124
if (activity != null) { if (activity != null) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); instance = new InAppBrowserFlutterPlugin(registrar);
channel.setMethodCallHandler(new InAppBrowserFlutterPlugin(registrar));
new MyCookieManager(registrar); new MyCookieManager(registrar);
...@@ -599,7 +600,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { ...@@ -599,7 +600,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
return false; 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); final InAppBrowserActivity inAppBrowserActivity = webViewActivities.get(uuid);
if (inAppBrowserActivity != null) { if (inAppBrowserActivity != null) {
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(new Runnable() {
......
...@@ -6,6 +6,7 @@ import android.graphics.BitmapFactory; ...@@ -6,6 +6,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.util.Log;
import android.view.View; import android.view.View;
import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
...@@ -84,6 +85,19 @@ public class InAppWebChromeClient extends WebChromeClient { ...@@ -84,6 +85,19 @@ public class InAppWebChromeClient extends WebChromeClient {
decorView.setSystemUiVisibility(3846 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 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 @Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) { public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Map<String, Object> obj = new HashMap<>(); Map<String, Object> obj = new HashMap<>();
...@@ -190,6 +204,6 @@ public class InAppWebChromeClient extends WebChromeClient { ...@@ -190,6 +204,6 @@ public class InAppWebChromeClient extends WebChromeClient {
} }
private MethodChannel getChannel() { 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; ...@@ -2,25 +2,17 @@ package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.JsonReader; import android.util.JsonReader;
import android.util.JsonToken; import android.util.JsonToken;
import android.util.Log; import android.util.Log;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.DownloadListener; import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.webkit.ValueCallback; import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList; import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem; import android.webkit.WebHistoryItem;
...@@ -47,8 +39,6 @@ import io.flutter.plugin.common.PluginRegistry; ...@@ -47,8 +39,6 @@ import io.flutter.plugin.common.PluginRegistry;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import static android.content.Context.DOWNLOAD_SERVICE;
public class InAppWebView extends WebView { public class InAppWebView extends WebView {
static final String LOG_TAG = "InAppWebView"; static final String LOG_TAG = "InAppWebView";
...@@ -56,7 +46,7 @@ public class InAppWebView extends WebView { ...@@ -56,7 +46,7 @@ public class InAppWebView extends WebView {
public PluginRegistry.Registrar registrar; public PluginRegistry.Registrar registrar;
public InAppBrowserActivity inAppBrowserActivity; public InAppBrowserActivity inAppBrowserActivity;
public FlutterWebView flutterWebView; public FlutterWebView flutterWebView;
int id; public int id;
InAppWebViewClient inAppWebViewClient; InAppWebViewClient inAppWebViewClient;
InAppWebChromeClient inAppWebChromeClient; InAppWebChromeClient inAppWebChromeClient;
public InAppWebViewOptions options; public InAppWebViewOptions options;
...@@ -137,39 +127,19 @@ public class InAppWebView extends WebView { ...@@ -137,39 +127,19 @@ public class InAppWebView extends WebView {
inAppWebViewClient = new InAppWebViewClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView); inAppWebViewClient = new InAppWebViewClient((isFromInAppBrowserActivity) ? inAppBrowserActivity : flutterWebView);
setWebViewClient(inAppWebViewClient); setWebViewClient(inAppWebViewClient);
activity.registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
setDownloadListener(new DownloadListener() { setDownloadListener(new DownloadListener() {
@Override @Override
public void onDownloadStart(final String url, final String userAgent, public void onDownloadStart(final String url, final String userAgent,
final String contentDisposition, final String mimetype, final String contentDisposition, final String mimetype,
final long contentLength) { final long contentLength) {
RequestPermissionHandler.checkAndRun(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, RequestPermissionHandler.REQUEST_CODE_WRITE_EXTERNAL_STORAGE, new Runnable(){ RequestPermissionHandler.checkAndRun(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, RequestPermissionHandler.REQUEST_CODE_WRITE_EXTERNAL_STORAGE, new Runnable(){
@Override @Override
public void run(){ public void run(){
Map<String, Object> obj = new HashMap<>();
Log.e(LOG_TAG, url); if (inAppBrowserActivity != null)
Log.e(LOG_TAG, contentDisposition); obj.put("uuid", inAppBrowserActivity.uuid);
Log.e(LOG_TAG, mimetype); obj.put("url", url);
getChannel().invokeMethod("onDownloadStart", obj);
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();
}
} }
}); });
} }
...@@ -181,6 +151,7 @@ public class InAppWebView extends WebView { ...@@ -181,6 +151,7 @@ public class InAppWebView extends WebView {
settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically); settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically);
settings.setBuiltInZoomControls(options.builtInZoomControls); settings.setBuiltInZoomControls(options.builtInZoomControls);
settings.setDisplayZoomControls(options.displayZoomControls); settings.setDisplayZoomControls(options.displayZoomControls);
settings.setSupportMultipleWindows(options.useOnTargetBlank);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
settings.setSafeBrowsingEnabled(options.safeBrowsingEnabled); settings.setSafeBrowsingEnabled(options.safeBrowsingEnabled);
...@@ -215,30 +186,21 @@ public class InAppWebView extends WebView { ...@@ -215,30 +186,21 @@ public class InAppWebView extends WebView {
if (!options.mixedContentMode.isEmpty()) { if (!options.mixedContentMode.isEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (options.mixedContentMode.equals("MIXED_CONTENT_COMPATIBILITY_MODE")) { switch (options.mixedContentMode) {
case "MIXED_CONTENT_COMPATIBILITY_MODE":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
} else if (options.mixedContentMode.equals("MIXED_CONTENT_ALWAYS_ALLOW")) { break;
case "MIXED_CONTENT_ALWAYS_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if (options.mixedContentMode.equals("MIXED_CONTENT_NEVER_ALLOW")) { break;
case "MIXED_CONTENT_NEVER_ALLOW":
settings.setMixedContentMode(WebSettings.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) { public void loadUrl(String url, MethodChannel.Result result) {
if (!url.isEmpty()) { if (!url.isEmpty()) {
loadUrl(url); loadUrl(url);
...@@ -332,7 +294,7 @@ public class InAppWebView extends WebView { ...@@ -332,7 +294,7 @@ public class InAppWebView extends WebView {
} }
public byte[] takeScreenshot() { public byte[] takeScreenshot() {
float scale = getScale(); float scale = getResources().getDisplayMetrics().density; // getScale();
int height = (int) (getContentHeight() * scale + 0.5); int height = (int) (getContentHeight() * scale + 0.5);
Bitmap b = Bitmap.createBitmap( getWidth(), Bitmap b = Bitmap.createBitmap( getWidth(),
...@@ -384,7 +346,7 @@ public class InAppWebView extends WebView { ...@@ -384,7 +346,7 @@ public class InAppWebView extends WebView {
if (newOptionsMap.get("domStorageEnabled") != null && options.domStorageEnabled != newOptions.domStorageEnabled) if (newOptionsMap.get("domStorageEnabled") != null && options.domStorageEnabled != newOptions.domStorageEnabled)
settings.setDomStorageEnabled(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); settings.setUserAgentString(newOptions.userAgent);
if (newOptionsMap.get("clearCache") != null && newOptions.clearCache) if (newOptionsMap.get("clearCache") != null && newOptions.clearCache)
...@@ -415,18 +377,25 @@ public class InAppWebView extends WebView { ...@@ -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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (newOptions.mixedContentMode.equals("MIXED_CONTENT_COMPATIBILITY_MODE")) { switch (newOptions.mixedContentMode) {
case "MIXED_CONTENT_COMPATIBILITY_MODE":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
} else if (newOptions.mixedContentMode.equals("MIXED_CONTENT_ALWAYS_ALLOW")) { break;
case "MIXED_CONTENT_ALWAYS_ALLOW":
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if (newOptions.mixedContentMode.equals("MIXED_CONTENT_NEVER_ALLOW")) { break;
case "MIXED_CONTENT_NEVER_ALLOW":
settings.setMixedContentMode(WebSettings.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; options = newOptions;
} }
...@@ -562,13 +531,11 @@ public class InAppWebView extends WebView { ...@@ -562,13 +531,11 @@ public class InAppWebView extends WebView {
} }
private MethodChannel getChannel() { private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel; return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
} }
@Override @Override
public void destroy() { public void destroy() {
final Activity activity = (inAppBrowserActivity != null) ? inAppBrowserActivity : registrar.activity();
activity.unregisterReceiver(onDownloadComplete);
super.destroy(); super.destroy();
} }
} }
...@@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi; ...@@ -9,6 +9,7 @@ import androidx.annotation.RequiresApi;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
...@@ -24,6 +25,7 @@ import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView; ...@@ -24,6 +25,7 @@ import com.pichillilorenzo.flutter_inappbrowser.FlutterWebView;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserActivity;
import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin;
import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface; import com.pichillilorenzo.flutter_inappbrowser.JavaScriptBridgeInterface;
import com.pichillilorenzo.flutter_inappbrowser.Util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
...@@ -116,6 +118,7 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -116,6 +118,7 @@ public class InAppWebViewClient extends WebViewClient {
return true; return true;
} }
if (url != null) {
if (url.startsWith(WebView.SCHEME_TEL)) { if (url.startsWith(WebView.SCHEME_TEL)) {
try { try {
Intent intent = new Intent(Intent.ACTION_DIAL); Intent intent = new Intent(Intent.ACTION_DIAL);
...@@ -166,6 +169,7 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -166,6 +169,7 @@ public class InAppWebViewClient extends WebViewClient {
Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString()); Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
} }
} }
}
return super.shouldOverrideUrlLoading(webView, url); return super.shouldOverrideUrlLoading(webView, url);
...@@ -300,13 +304,36 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -300,13 +304,36 @@ public class InAppWebViewClient extends WebViewClient {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 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") || if (!request.getMethod().toLowerCase().equals("get") ||
!(((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).options.useOnLoadResource)) { !(((inAppBrowserActivity != null) ? inAppBrowserActivity.webView : flutterWebView.webView).options.useOnLoadResource)) {
return null; return null;
} }
final String url = request.getUrl().toString();
try { try {
Request mRequest = new Request.Builder().url(url).build(); Request mRequest = new Request.Builder().url(url).build();
...@@ -399,7 +426,7 @@ public class InAppWebViewClient extends WebViewClient { ...@@ -399,7 +426,7 @@ public class InAppWebViewClient extends WebViewClient {
} }
private MethodChannel getChannel() { 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; ...@@ -2,12 +2,16 @@ package com.pichillilorenzo.flutter_inappbrowser.InAppWebView;
import com.pichillilorenzo.flutter_inappbrowser.Options; import com.pichillilorenzo.flutter_inappbrowser.Options;
import java.util.ArrayList;
import java.util.List;
public class InAppWebViewOptions extends Options { public class InAppWebViewOptions extends Options {
static final String LOG_TAG = "InAppWebViewOptions"; static final String LOG_TAG = "InAppWebViewOptions";
public boolean useShouldOverrideUrlLoading = false; public boolean useShouldOverrideUrlLoading = false;
public boolean useOnLoadResource = false; public boolean useOnLoadResource = false;
public boolean useOnTargetBlank = false;
public boolean clearCache = false; public boolean clearCache = false;
public String userAgent = ""; public String userAgent = "";
public boolean javaScriptEnabled = true; public boolean javaScriptEnabled = true;
...@@ -16,6 +20,7 @@ public class InAppWebViewOptions extends Options { ...@@ -16,6 +20,7 @@ public class InAppWebViewOptions extends Options {
public int textZoom = 100; public int textZoom = 100;
public boolean verticalScrollBarEnabled = true; public boolean verticalScrollBarEnabled = true;
public boolean horizontalScrollBarEnabled = true; public boolean horizontalScrollBarEnabled = true;
public List<String> resourceCustomSchemes = new ArrayList<>();
public boolean clearSessionCache = false; public boolean clearSessionCache = false;
public boolean builtInZoomControls = false; public boolean builtInZoomControls = false;
......
...@@ -51,15 +51,17 @@ public class JavaScriptBridgeInterface { ...@@ -51,15 +51,17 @@ public class JavaScriptBridgeInterface {
getChannel().invokeMethod("onCallJsHandler", obj, new MethodChannel.Result() { getChannel().invokeMethod("onCallJsHandler", obj, new MethodChannel.Result() {
@Override @Override
public void success(Object json) { 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. // The webview has already been disposed, ignore.
return; return;
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 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 { 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 { ...@@ -79,6 +81,6 @@ public class JavaScriptBridgeInterface {
} }
private MethodChannel getChannel() { private MethodChannel getChannel() {
return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.channel : flutterWebView.channel; return (inAppBrowserActivity != null) ? InAppBrowserFlutterPlugin.instance.channel : flutterWebView.channel;
} }
} }
package com.pichillilorenzo.flutter_inappbrowser; package com.pichillilorenzo.flutter_inappbrowser;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
public class Util { public class Util {
...@@ -37,4 +44,53 @@ public class Util { ...@@ -37,4 +44,53 @@ public class Util {
return ANDROID_ASSET_URL + key; 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 @@ ...@@ -6,6 +6,8 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <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 <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
...@@ -13,7 +15,7 @@ ...@@ -13,7 +15,7 @@
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<application <application
android:name="io.flutter.app.FlutterApplication" android:name="com.pichillilorenzo.flutterwebviewexample.MyApplication"
android:label="flutter_inappbrowser_example" android:label="flutter_inappbrowser_example"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
...@@ -35,5 +37,16 @@ ...@@ -35,5 +37,16 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </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> </application>
</manifest> </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 @@ ...@@ -24,7 +24,7 @@
<main role="main" class="inner cover"> <main role="main" class="inner cover">
<h1 class="cover-heading">Inline WebView</h1> <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">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"> <p class="lead">
<a href="#" class="btn btn-lg btn-secondary">Learn more</a> <a href="#" class="btn btn-lg btn-secondary">Learn more</a>
...@@ -33,7 +33,9 @@ ...@@ -33,7 +33,9 @@
<footer class="mastfoot mt-auto"> <footer class="mastfoot mt-auto">
<div class="inner"> <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> </div>
</footer> </footer>
</div> </div>
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 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, ); }; }; 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 */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 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, ); }; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
...@@ -40,6 +41,8 @@ ...@@ -40,6 +41,8 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
...@@ -61,6 +64,7 @@ ...@@ -61,6 +64,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
61FF730023634CA10069C557 /* libsqlite3.tbd in Frameworks */,
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
25A517508F43E58C47090625 /* Pods_Runner.framework in Frameworks */, 25A517508F43E58C47090625 /* Pods_Runner.framework in Frameworks */,
...@@ -73,6 +77,8 @@ ...@@ -73,6 +77,8 @@
50BAF1DF19E018DDF2B149B9 /* Frameworks */ = { 50BAF1DF19E018DDF2B149B9 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
61FF730123634DD10069C557 /* flutter_downloader.framework */,
61FF72FF23634CA10069C557 /* libsqlite3.tbd */,
E8D91E403808A7540F18B75D /* Pods_Runner.framework */, E8D91E403808A7540F18B75D /* Pods_Runner.framework */,
); );
name = Frameworks; name = Frameworks;
...@@ -180,6 +186,11 @@ ...@@ -180,6 +186,11 @@
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = PFP8UV45Y6; DevelopmentTeam = PFP8UV45Y6;
LastSwiftMigration = 1020; LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
}; };
}; };
}; };
...@@ -257,12 +268,16 @@ ...@@ -257,12 +268,16 @@
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", "${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}/flutter_inappbrowser/flutter_inappbrowser.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", "${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}/flutter_inappbrowser.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
......
import UIKit import UIKit
import Flutter import Flutter
import flutter_downloader
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback({(registry: FlutterPluginRegistry) in
})
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
} }
...@@ -4,11 +4,6 @@ ...@@ -4,11 +4,6 @@
<dict> <dict>
<key></key> <key></key>
<string></string> <string></string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
...@@ -29,6 +24,16 @@ ...@@ -29,6 +24,16 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <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> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
...@@ -46,9 +51,9 @@ ...@@ -46,9 +51,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict> </dict>
</plist> </plist>
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.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 { class InlineExampleScreen extends StatefulWidget {
@override @override
...@@ -55,13 +62,15 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -55,13 +62,15 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
BoxDecoration(border: Border.all(color: Colors.blueAccent)), BoxDecoration(border: Border.all(color: Colors.blueAccent)),
child: InAppWebView( child: InAppWebView(
//initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1", //initialUrl: "https://www.youtube.com/embed/M7lc1UVf-VE?playsinline=1",
initialUrl: "https://flutter.dev/", //initialUrl: "https://flutter.dev/",
//initialFile: "assets/index.html", initialFile: "assets/index.html",
initialHeaders: {}, initialHeaders: {},
initialOptions: { initialOptions: {
//"mediaPlaybackRequiresUserGesture": false, //"mediaPlaybackRequiresUserGesture": false,
//"allowsInlineMediaPlayback": true, //"allowsInlineMediaPlayback": true,
//"useShouldOverrideUrlLoading": true, "useShouldOverrideUrlLoading": true,
"useOnTargetBlank": true,
"resourceCustomSchemes": ["my-special-custom-scheme"],
//"useOnLoadResource": true //"useOnLoadResource": true
}, },
onWebViewCreated: (InAppWebViewController controller) { onWebViewCreated: (InAppWebViewController controller) {
...@@ -104,13 +113,34 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> { ...@@ -104,13 +113,34 @@ class _InlineExampleScreenState extends State<InlineExampleScreen> {
response.url); response.url);
}, },
onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) { onConsoleMessage: (InAppWebViewController controller, ConsoleMessage consoleMessage) {
print(""" // print("""
console output: // console output:
sourceURL: ${consoleMessage.sourceURL} // sourceURL: ${consoleMessage.sourceURL}
lineNumber: ${consoleMessage.lineNumber} // lineNumber: ${consoleMessage.lineNumber}
message: ${consoleMessage.message} // message: ${consoleMessage.message}
messageLevel: ${consoleMessage.messageLevel} // 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> { ...@@ -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'; ...@@ -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/chrome_safari_example.screen.dart';
import 'package:flutter_inappbrowser_example/inline_example.screen.dart'; import 'package:flutter_inappbrowser_example/inline_example.screen.dart';
import 'package:flutter_inappbrowser_example/webview_example.screen.dart'; import 'package:flutter_inappbrowser_example/webview_example.screen.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
// InAppLocalhostServer localhostServer = new InAppLocalhostServer(); // InAppLocalhostServer localhostServer = new InAppLocalhostServer();
Future main() async { Future main() async {
// await localhostServer.start(); // await localhostServer.start();
await FlutterDownloader.initialize();
runApp(new MyApp()); runApp(new MyApp());
} }
......
...@@ -67,6 +67,15 @@ class MyInappBrowser extends InAppBrowser { ...@@ -67,6 +67,15 @@ class MyInappBrowser extends InAppBrowser {
"""); """);
} }
@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 { class WebviewExampleScreen extends StatefulWidget {
...@@ -87,7 +96,7 @@ class _WebviewExampleScreenState extends State<WebviewExampleScreen> { ...@@ -87,7 +96,7 @@ class _WebviewExampleScreenState extends State<WebviewExampleScreen> {
child: new RaisedButton( child: new RaisedButton(
onPressed: () { onPressed: () {
widget.browser.open( widget.browser.open(
url: "https://google.com", url: "https://www.google.com/",
options: { options: {
"useShouldOverrideUrlLoading": true, "useShouldOverrideUrlLoading": true,
"useOnLoadResource": true, "useOnLoadResource": true,
......
...@@ -19,6 +19,8 @@ dependencies: ...@@ -19,6 +19,8 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2 cupertino_icons: ^0.1.2
flutter_downloader: ^1.3.2
path_provider: ^1.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
...@@ -21,7 +21,6 @@ ...@@ -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/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/build" /> <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/App.framework/flutter_assets/packages" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/flutter_assets/packages" />
</content> </content>
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" /> <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 ...@@ -9,7 +9,7 @@ import Flutter
import Foundation import Foundation
public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
private weak var registrar: FlutterPluginRegistrar? private var registrar: FlutterPluginRegistrar?
init(registrar: FlutterPluginRegistrar?) { init(registrar: FlutterPluginRegistrar?) {
super.init() super.init()
...@@ -22,7 +22,10 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory { ...@@ -22,7 +22,10 @@ public class FlutterWebViewFactory: NSObject, FlutterPlatformViewFactory {
public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
let arguments = args as? NSDictionary 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 return webviewController
} }
} }
This diff is collapsed.
...@@ -12,6 +12,7 @@ public class InAppWebViewOptions: Options { ...@@ -12,6 +12,7 @@ public class InAppWebViewOptions: Options {
var useShouldOverrideUrlLoading = false var useShouldOverrideUrlLoading = false
var useOnLoadResource = false var useOnLoadResource = false
var useOnTargetBlank = false
var clearCache = false var clearCache = false
var userAgent = "" var userAgent = ""
var javaScriptEnabled = true var javaScriptEnabled = true
...@@ -19,6 +20,7 @@ public class InAppWebViewOptions: Options { ...@@ -19,6 +20,7 @@ public class InAppWebViewOptions: Options {
var mediaPlaybackRequiresUserGesture = true var mediaPlaybackRequiresUserGesture = true
var verticalScrollBarEnabled = true var verticalScrollBarEnabled = true
var horizontalScrollBarEnabled = true var horizontalScrollBarEnabled = true
var resourceCustomSchemes: [String] = []
var disallowOverScroll = false var disallowOverScroll = false
var enableViewportScale = false var enableViewportScale = false
......
...@@ -35,8 +35,9 @@ extension Dictionary where Key: ExpressibleByStringLiteral { ...@@ -35,8 +35,9 @@ extension Dictionary where Key: ExpressibleByStringLiteral {
public class SwiftFlutterPlugin: NSObject, FlutterPlugin { public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
static var registrar: FlutterPluginRegistrar? static var instance: SwiftFlutterPlugin?
static var channel: FlutterMethodChannel? var registrar: FlutterPluginRegistrar?
var channel: FlutterMethodChannel?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:] var safariViewControllers: [String: Any?] = [:]
...@@ -45,17 +46,14 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -45,17 +46,14 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
private var previousStatusBarStyle = -1 private var previousStatusBarStyle = -1
public init(with registrar: FlutterPluginRegistrar) { 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) { public static func register(with registrar: FlutterPluginRegistrar) {
SwiftFlutterPlugin.instance = SwiftFlutterPlugin(with: registrar)
SwiftFlutterPlugin.registrar = registrar
let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPlugin(with: registrar)
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: "com.pichillilorenzo/flutter_inappwebview") registrar.register(FlutterWebViewFactory(registrar: registrar) as FlutterPlatformViewFactory, withId: "com.pichillilorenzo/flutter_inappwebview")
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
...@@ -288,7 +286,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -288,7 +286,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
var openWithSystemBrowser = (arguments["openWithSystemBrowser"] as? Bool)! var openWithSystemBrowser = (arguments["openWithSystemBrowser"] as? Bool)!
if isLocalFile { 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) let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
if assetURL == nil { if assetURL == nil {
result(FlutterError(code: "InAppBrowserFlutterPlugin", message: url + " asset file cannot be found!", details: nil)) result(FlutterError(code: "InAppBrowserFlutterPlugin", message: url + " asset file cannot be found!", details: nil))
...@@ -677,28 +675,28 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { ...@@ -677,28 +675,28 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
func onBrowserCreated(uuid: String, webView: WKWebView) { func onBrowserCreated(uuid: String, webView: WKWebView) {
if let webViewController = self.webViewControllers[uuid] { if let webViewController = self.webViewControllers[uuid] {
SwiftFlutterPlugin.channel!.invokeMethod("onBrowserCreated", arguments: ["uuid": uuid]) self.channel!.invokeMethod("onBrowserCreated", arguments: ["uuid": uuid])
} }
} }
func onExit(uuid: String) { func onExit(uuid: String) {
SwiftFlutterPlugin.channel!.invokeMethod("onExit", arguments: ["uuid": uuid]) self.channel!.invokeMethod("onExit", arguments: ["uuid": uuid])
} }
func onChromeSafariBrowserOpened(uuid: String) { func onChromeSafariBrowserOpened(uuid: String) {
if self.safariViewControllers[uuid] != nil { if self.safariViewControllers[uuid] != nil {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid]) self.channel!.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid])
} }
} }
func onChromeSafariBrowserLoaded(uuid: String) { func onChromeSafariBrowserLoaded(uuid: String) {
if self.safariViewControllers[uuid] != nil { if self.safariViewControllers[uuid] != nil {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid]) self.channel!.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid])
} }
} }
func onChromeSafariBrowserClosed(uuid: String) { func onChromeSafariBrowserClosed(uuid: String) {
SwiftFlutterPlugin.channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid]) self.channel!.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid])
} }
func safariExit(uuid: String) { 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