diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e955a1a4..f5bb0fce 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,20 +10,18 @@ - - - + - + - - + + @@ -40,11 +38,11 @@ - + - - + + @@ -52,11 +50,24 @@ + + + + + + + + + + + + + - - + + @@ -143,16 +154,16 @@ - @@ -388,20 +405,21 @@ - + + - + - + @@ -420,7 +438,6 @@ - @@ -733,24 +750,7 @@ - - - - - - - - - - - - - - - - - @@ -758,54 +758,38 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - + + @@ -814,14 +798,25 @@ - - + + + + + + + + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index e6d4e571..954a188a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 0.1.0 +## 0.2.0 + +- added support of Chrome CustomTabs for android +- added support of SFSafariViewController for iOS +- added the ability to create multiple instances of browsers + +## 0.1.1 - updated/added new methods - updated UI of android/iOS in-app browser diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java index 1d43afbb..c8defd55 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java @@ -61,8 +61,8 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { public static Registrar registrar; public Activity activity; public static MethodChannel channel; - public static WebViewActivity webViewActivity; - public static ChromeCustomTabsActivity chromeCustomTabsActivity; + public static Map webViewActivities = new HashMap<>(); + public static Map chromeCustomTabsActivities = new HashMap<>(); private static final String NULL = "null"; protected static final String LOG_TAG = "InAppBrowserFlutterP"; @@ -85,6 +85,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { String source; String jsWrapper; String urlFile; + final String uuid = (String) call.argument("uuid"); switch (call.method) { case "open": @@ -110,13 +111,15 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { if (useChromeSafariBrowser) { + final String uuidFallback = (String) call.argument("uuidFallback"); + final ChromeCustomTabsOptions options = new ChromeCustomTabsOptions(); options.parse((HashMap) call.argument("options")); final InAppBrowserOptions optionsFallback = new InAppBrowserOptions(); optionsFallback.parse((HashMap) call.argument("optionsFallback")); - open(url, options, headers,true, optionsFallback); + open(uuid, uuidFallback, url, options, headers,true, optionsFallback); } else { @@ -141,89 +144,90 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { // load in InAppBrowserFlutterPlugin else { Log.d(LOG_TAG, "loading in InAppBrowserFlutterPlugin"); - open(url, options, headers, false, null); + open(uuid, null, url, options, headers, false, null); } } // SYSTEM else if ("_system".equals(target)) { Log.d(LOG_TAG, "in system"); - openExternal(url, result); + openExternal(url); } // BLANK - or anything else else { Log.d(LOG_TAG, "in blank"); - open(url, options, headers, false, null); + open(uuid, null, url, options, headers, false, null); } } + result.success(true); } }); break; case "loadUrl": - loadUrl(call.argument("url").toString(), (Map) call.argument("headers"), result); + loadUrl(uuid, call.argument("url").toString(), (Map) call.argument("headers"), result); break; case "close": - close(); + close(uuid); result.success(true); break; case "injectScriptCode": source = call.argument("source").toString(); jsWrapper = "(function(){return JSON.stringify(eval(%s));})();"; - injectDeferredObject(source, jsWrapper, result); + injectDeferredObject(uuid, source, jsWrapper, result); break; case "injectScriptFile": urlFile = call.argument("urlFile").toString(); jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document);"; - injectDeferredObject(urlFile, jsWrapper, null); + injectDeferredObject(uuid, urlFile, jsWrapper, null); result.success(true); break; case "injectStyleCode": source = call.argument("source").toString(); jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document);"; - injectDeferredObject(source, jsWrapper, null); + injectDeferredObject(uuid, source, jsWrapper, null); result.success(true); break; case "injectStyleFile": urlFile = call.argument("urlFile").toString(); jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document);"; - injectDeferredObject(urlFile, jsWrapper, null); + injectDeferredObject(uuid, urlFile, jsWrapper, null); result.success(true); break; case "show": - show(); + show(uuid); result.success(true); break; case "hide": - hide(); + hide(uuid); result.success(true); break; case "reload": - reload(); + reload(uuid); result.success(true); break; case "goBack": - goBack(); + goBack(uuid); result.success(true); break; case "canGoBack": - result.success(canGoBack()); + result.success(canGoBack(uuid)); break; case "goForward": - goForward(); + goForward(uuid); result.success(true); break; case "canGoForward": - result.success(canGoForward()); + result.success(canGoForward(uuid)); break; case "stopLoading": - stopLoading(); + stopLoading(uuid); result.success(true); break; case "isLoading": - result.success(isLoading()); + result.success(isLoading(uuid)); break; case "isHidden": - result.success(isHidden()); + result.success(isHidden(uuid)); break; default: result.notImplemented(); @@ -240,13 +244,15 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { * If a wrapper string is supplied, then the source string will be JSON-encoded (adding * quotes) and wrapped using string formatting. (The wrapper string should have a single * '%s' marker) - * @param source The source object (filename or script/style text) to inject into + * @param uuid + * @param source The source object (filename or script/style text) to inject into * the document. * @param jsWrapper A JavaScript string to wrap the source string in, so that the object - * is properly injected, or null if the source string is JavaScript text + * is properly injected, or null if the source string is JavaScript text * @param result */ - private void injectDeferredObject(String source, String jsWrapper, final Result result) { + private void injectDeferredObject(String uuid, String source, String jsWrapper, final Result result) { + final WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) { String scriptToInject; if (jsWrapper != null) { @@ -323,7 +329,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { * @param url the url to load. * @return "" if ok, or error message. */ - public void openExternal(String url, Result result) { + public void openExternal(String url) { try { Intent intent; intent = new Intent(Intent.ACTION_VIEW); @@ -381,24 +387,29 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { } } - public static void open(final String url, Options options, Map headers, boolean useChromeSafariBrowser, InAppBrowserOptions optionsFallback) { + public static void open(String uuid, String uuidFallback, final String url, Options options, Map headers, boolean useChromeSafariBrowser, InAppBrowserOptions optionsFallback) { Intent intent = new Intent(registrar.activity(), (useChromeSafariBrowser) ? ChromeCustomTabsActivity.class : WebViewActivity.class); Bundle extras = new Bundle(); + extras.putString("uuid", uuid); extras.putString("url", url); extras.putSerializable("options", options.getHashMap()); extras.putSerializable("headers", (Serializable) headers); - if (useChromeSafariBrowser && optionsFallback != null) - extras.putSerializable("optionsFallback", optionsFallback.getHashMap()); - else - extras.putSerializable("optionsFallback", (new InAppBrowserOptions()).getHashMap()); + if (useChromeSafariBrowser) { + extras.putString("uuidFallback", uuidFallback); + if (optionsFallback != null) + extras.putSerializable("optionsFallback", optionsFallback.getHashMap()); + else + extras.putSerializable("optionsFallback", (new InAppBrowserOptions()).getHashMap()); + } intent.putExtras(extras); registrar.activity().startActivity(intent); } - public void loadUrl(String url, Map headers, Result result) { + public void loadUrl(String uuid, String url, Map headers, Result result) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) { if (headers != null) webViewActivity.loadUrl(url, headers, result); @@ -407,67 +418,79 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler { } } - public void show() { + public void show(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.show(); } - public void hide() { + public void hide(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.hide(); } - public void reload() { + public void reload(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.reload(); } - public boolean isLoading() { + public boolean isLoading(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) return webViewActivity.isLoading(); return false; } - public boolean isHidden() { + public boolean isHidden(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) return webViewActivity.isHidden; return false; } - public void stopLoading() { + public void stopLoading(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.stopLoading(); } - public void goBack() { + public void goBack(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.goBack(); } - public boolean canGoBack() { + public boolean canGoBack(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) return webViewActivity.canGoBack(); return false; } - public void goForward() { + public void goForward(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) webViewActivity.goForward(); } - public boolean canGoForward() { + public boolean canGoForward(String uuid) { + WebViewActivity webViewActivity = webViewActivities.get(uuid); if (webViewActivity != null) return webViewActivity.canGoForward(); return false; } - public static void close() { + public static void close(final String uuid) { + final WebViewActivity webViewActivity = webViewActivities.get(uuid); registrar.activity().runOnUiThread(new Runnable() { @Override public void run() { Map obj = new HashMap<>(); + obj.put("uuid", uuid); channel.invokeMethod("onExit", obj); // The JS protects against multiple calls, so this should happen only when diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java index 0d336caa..b2e60e8d 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java @@ -30,6 +30,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { if (activity.options.useShouldOverrideUrlLoading) { Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); obj.put("url", url); InAppBrowserFlutterPlugin.channel.invokeMethod("shouldOverrideUrlLoading", obj); return true; @@ -110,6 +111,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { } Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); obj.put("url", url); InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStart", obj); } @@ -133,6 +135,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { view.requestFocus(); Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); obj.put("url", url); InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStop", obj); } @@ -143,6 +146,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { activity.isLoading = false; Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); obj.put("url", failingUrl); obj.put("code", errorCode); obj.put("message", description); @@ -153,6 +157,7 @@ public class InAppBrowserWebViewClient extends WebViewClient { super.onReceivedSslError(view, handler, error); Map obj = new HashMap<>(); + obj.put("uuid", activity.uuid); obj.put("url", error.getUrl()); obj.put("code", error.getPrimaryError()); String message; diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java index 31d884b3..47b92956 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java @@ -7,7 +7,6 @@ import android.os.Build; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; -import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; @@ -27,6 +26,7 @@ import io.flutter.plugin.common.MethodChannel; public class WebViewActivity extends AppCompatActivity { + String uuid; WebView webView; ActionBar actionBar; InAppBrowserWebViewClient inAppBrowserWebViewClient; @@ -47,6 +47,7 @@ public class WebViewActivity extends AppCompatActivity { webView = findViewById(R.id.webView); Bundle b = getIntent().getExtras(); + uuid = b.getString("uuid"); String url = b.getString("url"); options = new InAppBrowserOptions(); @@ -54,7 +55,7 @@ public class WebViewActivity extends AppCompatActivity { headers = (HashMap) b.getSerializable("headers"); - InAppBrowserFlutterPlugin.webViewActivity = this; + InAppBrowserFlutterPlugin.webViewActivities.put(uuid, this); actionBar = getSupportActionBar(); @@ -243,7 +244,7 @@ public class WebViewActivity extends AppCompatActivity { if (canGoBack()) goBack(); else if (options.closeOnCannotGoBack) - InAppBrowserFlutterPlugin.close(); + InAppBrowserFlutterPlugin.close(uuid); return true; } return super.onKeyDown(keyCode, event); @@ -340,7 +341,7 @@ public class WebViewActivity extends AppCompatActivity { } public void closeButtonClicked(MenuItem item) { - InAppBrowserFlutterPlugin.close(); + InAppBrowserFlutterPlugin.close(uuid); } } diff --git a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java index 1b1aebbb..aa15beeb 100644 --- a/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java +++ b/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java @@ -6,6 +6,7 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.customtabs.CustomTabsIntent; +import android.util.Log; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserFlutterPlugin; import com.pichillilorenzo.flutter_inappbrowser.InAppBrowserOptions; @@ -16,6 +17,9 @@ import java.util.Map; public class ChromeCustomTabsActivity extends Activity { + protected static final String LOG_TAG = "CustomTabsActivity"; + String uuid; + String uuidFallback; CustomTabsIntent.Builder builder; ChromeCustomTabsOptions options; Map headersFallback; @@ -30,6 +34,8 @@ public class ChromeCustomTabsActivity extends Activity { setContentView(R.layout.chrome_custom_tabs_layout); Bundle b = getIntent().getExtras(); + uuid = b.getString("uuid"); + uuidFallback = b.getString("uuidFallback"); String url = b.getString("url"); options = new ChromeCustomTabsOptions(); @@ -40,8 +46,9 @@ public class ChromeCustomTabsActivity extends Activity { optionsFallback = new InAppBrowserOptions(); optionsFallback.parse((HashMap) b.getSerializable("optionsFallback")); - customTabActivityHelper = new CustomTabActivityHelper(); + InAppBrowserFlutterPlugin.chromeCustomTabsActivities.put(uuid, this); + customTabActivityHelper = new CustomTabActivityHelper(); builder = new CustomTabsIntent.Builder(); prepareCustomTabs(); @@ -52,13 +59,20 @@ public class ChromeCustomTabsActivity extends Activity { new CustomTabActivityHelper.CustomTabFallback() { @Override public void openUri(Activity activity, Uri uri) { - InAppBrowserFlutterPlugin.open(uri.toString(), optionsFallback, headersFallback, false, null); + if (!uuidFallback.isEmpty()) + InAppBrowserFlutterPlugin.open(uuidFallback, null, uri.toString(), optionsFallback, headersFallback, false, null); + else { + Log.d(LOG_TAG, "No WebView fallback declared."); + activity.finish(); + } } }); if (chromeCustomTabsOpened) { - InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", null); - InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", null); + Map obj = new HashMap<>(); + obj.put("uuid", uuid); + InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", obj); + InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", obj); } } @@ -93,7 +107,9 @@ public class ChromeCustomTabsActivity extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) { finish(); - InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserClosed", null); + Map obj = new HashMap<>(); + obj.put("uuid", uuid); + InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserClosed", obj); } } } diff --git a/example/lib/main.dart b/example/lib/main.dart index 0f7efc6a..d312c75e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,13 +6,17 @@ class MyInAppBrowser extends InAppBrowser { @override Future onLoadStart(String url) async { - print("\n\nStarted $url\n\n"); + print("\n\nStarted aaa $url\n\n"); + inAppBrowserFallback2.open("https://www.google.com", options: { + "hidden": true + }); //print("\n\n ${await this.isHidden()} \n\n"); } @override Future onLoadStop(String url) async { - print("\n\nStopped $url\n\n"); + print("\n\nStopped aaa $url\n\n"); + inAppBrowserFallback2.show(); // print(await this.injectScriptCode("document.body.innerHTML")); // print(await this.injectScriptCode("3")); // print(await this.injectScriptCode(""" @@ -61,7 +65,38 @@ class MyInAppBrowser extends InAppBrowser { } } +class MyInAppBrowser2 extends InAppBrowser { + + @override + Future onLoadStart(String url) async { + print("\n\nStarted $url\n\n"); + //print("\n\n ${await this.isHidden()} \n\n"); + } + + @override + Future onLoadStop(String url) async { + print("\n\nStopped $url\n\n"); + } + + @override + void onLoadError(String url, int code, String message) { + print("\n\nCan't load $url.. Error: $message\n\n"); + } + + @override + void onExit() { + print("\n\nBrowser closed!\n\n"); + } + + @override + void shouldOverrideUrlLoading(String url) { + print("\n\n override $url\n\n"); + this.loadUrl(url); + } +} + MyInAppBrowser inAppBrowserFallback = new MyInAppBrowser(); +MyInAppBrowser2 inAppBrowserFallback2 = new MyInAppBrowser2(); class MyChromeSafariBrowser extends ChromeSafariBrowser { MyChromeSafariBrowser(browserFallback) : super(browserFallback); @@ -79,6 +114,7 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser { @override void onClosed() { print("ChromeSafari browser closed"); + inAppBrowserFallback.open("https://flutter.io/"); } } diff --git a/ios/Classes/InAppBrowserWebViewController.swift b/ios/Classes/InAppBrowserWebViewController.swift index 9fe57adc..dfd427bf 100644 --- a/ios/Classes/InAppBrowserWebViewController.swift +++ b/ios/Classes/InAppBrowserWebViewController.swift @@ -80,6 +80,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio var browserOptions: InAppBrowserOptions? var initHeaders: [String: String]? var isHidden = false + var uuid: String = "" required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! @@ -323,7 +324,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio currentURL = nil if (navigationDelegate != nil) { - navigationDelegate?.browserExit() + navigationDelegate?.browserExit(uuid: self.uuid) } weak var weakSelf = self @@ -419,7 +420,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio let url = navigationAction.request.url if (url != nil && navigationAction.navigationType == .linkActivated && (browserOptions?.useShouldOverrideUrlLoading)!) { - navigationDelegate?.shouldOverrideUrlLoading(webView, url: url!) + navigationDelegate?.shouldOverrideUrlLoading(uuid: self.uuid, webView: webView, url: url!) decisionHandler(.cancel) return } @@ -475,7 +476,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio spinner.startAnimating() } - return (navigationDelegate?.onLoadStart(webView))! + return (navigationDelegate?.onLoadStart(uuid: self.uuid, webView: webView))! } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { @@ -485,7 +486,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio backButton.isEnabled = webView.canGoBack forwardButton.isEnabled = webView.canGoForward spinner.stopAnimating() - navigationDelegate?.onLoadStop(webView) + navigationDelegate?.onLoadStop(uuid: self.uuid, webView: webView) } // func webView(_ webView: WKWebView, @@ -495,7 +496,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio // backButton.isEnabled = webView.canGoBack // forwardButton.isEnabled = webView.canGoForward // spinner.stopAnimating() -// navigationDelegate?.webView(webView, didFailLoadWithError: error) +// navigationDelegate?.webView(uuid: self.uuid, webView: webView, didFailLoadWithError: error) // } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { @@ -503,6 +504,6 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio backButton.isEnabled = webView.canGoBack forwardButton.isEnabled = webView.canGoForward spinner.stopAnimating() - navigationDelegate?.onLoadError(webView, error: error) + navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error) } } diff --git a/ios/Classes/SafariViewController.swift b/ios/Classes/SafariViewController.swift index e5b411e4..bc35e4bb 100644 --- a/ios/Classes/SafariViewController.swift +++ b/ios/Classes/SafariViewController.swift @@ -14,6 +14,7 @@ class SafariViewController: SFSafariViewController, SFSafariViewControllerDelega weak var statusDelegate: SwiftFlutterPlugin? var tmpWindow: UIWindow? var safariOptions: SafariBrowserOptions? + var uuid: String = "" override func viewWillAppear(_ animated: Bool) { prepareSafariBrowser() @@ -37,7 +38,7 @@ class SafariViewController: SFSafariViewController, SFSafariViewControllerDelega func close() { if (statusDelegate != nil) { - statusDelegate?.safariExit() + statusDelegate?.safariExit(uuid: self.uuid) } dismiss(animated: true) @@ -55,7 +56,7 @@ class SafariViewController: SFSafariViewController, SFSafariViewControllerDelega func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) { if didLoadSuccessfully { - statusDelegate?.onChromeSafariBrowserLoaded() + statusDelegate?.onChromeSafariBrowserLoaded(uuid: self.uuid) } else { print("Cant load successfully the 'SafariViewController'.") diff --git a/ios/Classes/SwiftFlutterPlugin.swift b/ios/Classes/SwiftFlutterPlugin.swift index 3c803d32..73893686 100644 --- a/ios/Classes/SwiftFlutterPlugin.swift +++ b/ios/Classes/SwiftFlutterPlugin.swift @@ -26,8 +26,8 @@ let WEBVIEW_STORYBOARD = "WebView" let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" public class SwiftFlutterPlugin: NSObject, FlutterPlugin { - var webViewController: InAppBrowserWebViewController? - var safariViewController: Any? + var webViewControllers: [String: InAppBrowserWebViewController?] = [:] + var safariViewControllers: [String: Any?] = [:] var tmpWindow: UIWindow? var channel: FlutterMethodChannel @@ -45,66 +45,96 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary + let uuid: String = (arguments!["uuid"] as? String)! + switch call.method { case "open": - self.open(arguments: arguments!, result: result) + self.open(uuid: uuid, arguments: arguments!, result: result) break case "loadUrl": - self.loadUrl(arguments: arguments!, result: result) + self.loadUrl(uuid: uuid, arguments: arguments!, result: result) break case "close": - self.close() + self.close(uuid: uuid) result(true) break case "show": - self.show() + self.show(uuid: uuid) result(true) break case "hide": - self.hide() + self.hide(uuid: uuid) result(true) break case "reload": - self.webViewController?.reload() + if let webViewController = self.webViewControllers[uuid] { + webViewController?.reload() + } result(true) break case "goBack": - self.webViewController?.goBack() + if let webViewController = self.webViewControllers[uuid] { + webViewController?.goBack() + } result(true) break case "canGoBack": - result(self.webViewController?.canGoBack() ?? false) + if let webViewController = self.webViewControllers[uuid] { + result(webViewController?.canGoBack() ?? false) + } + else { + result(false) + } break case "goForward": - self.webViewController?.goForward() + if let webViewController = self.webViewControllers[uuid] { + webViewController?.goForward() + } result(true) break case "canGoForward": - result(self.webViewController?.canGoForward() ?? false) + if let webViewController = self.webViewControllers[uuid] { + result(webViewController?.canGoForward() ?? false) + } + else { + result(false) + } break case "isLoading": - result((self.webViewController?.webView.isLoading ?? false) == true) + if let webViewController = self.webViewControllers[uuid] { + result((webViewController?.webView.isLoading ?? false) == true) + } + else { + result(false) + } break case "stopLoading": - self.webViewController?.webView.stopLoading() + if let webViewController = self.webViewControllers[uuid] { + webViewController?.webView.stopLoading() + } result(true) break case "isHidden": - result((self.webViewController?.isHidden ?? false) == true) + if let webViewController = self.webViewControllers[uuid] { + result((webViewController?.isHidden ?? false) == true) + } + else { + result(false) + } break case "injectScriptCode": - self.injectScriptCode(arguments: arguments!, result: result) + self.injectScriptCode(uuid: uuid, arguments: arguments!, result: result) break case "injectScriptFile": - self.injectScriptFile(arguments: arguments!, result: nil) + self.injectScriptFile(uuid: uuid, arguments: arguments!, result: nil) result(true) break case "injectStyleCode": - self.injectStyleCode(arguments: arguments!, result: nil) + self.injectStyleCode(uuid: uuid, arguments: arguments!, result: nil) result(true) break case "injectStyleFile": - self.injectStyleFile(arguments: arguments!, result: nil) + self.injectStyleFile(uuid: uuid, arguments: arguments!, result: nil) result(true) break default: @@ -113,13 +143,15 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { } } - func close() { - if webViewController == nil { + func close(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + // Things are cleaned up in browserExit. + webViewController?.close() + } + else { print("IAB.close() called but it was already closed.") return } - // Things are cleaned up in browserExit. - webViewController?.close() } func isSystemUrl(_ url: URL) -> Bool { @@ -129,7 +161,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { return false } - public func open(arguments: NSDictionary, result: @escaping FlutterResult) { + public func open(uuid: String, arguments: NSDictionary, result: @escaping FlutterResult) { let url: String = (arguments["url"] as? String)! let headers = (arguments["headers"] as? [String: String])! @@ -140,56 +172,47 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { let useChromeSafariBrowser = (arguments["useChromeSafariBrowser"] as? Bool) if useChromeSafariBrowser! { + let uuidFallback = (arguments["uuidFallback"] as? String)! let options = (arguments["options"] as? [String: Any])! let optionsFallback = (arguments["optionsFallback"] as? [String: Any])! - open(inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: true, withOptionsFallback: optionsFallback); + open(uuid: uuid, uuidFallback: uuidFallback, inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: true, withOptionsFallback: optionsFallback); } else { let options = (arguments["options"] as? [String: Any])! + if isSystemUrl(absoluteUrl!) { target = "_system" } if (target == "_self" || target == "_target") { - open(inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil) + open(uuid: uuid, uuidFallback: nil, inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil) } else if (target == "_system") { open(inSystem: absoluteUrl!) } else { // anything else - open(inAppBrowser: absoluteUrl!, headers: headers,withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil) + open(uuid: uuid, uuidFallback: nil, inAppBrowser: absoluteUrl!, headers: headers,withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil) } } result(true) } - public func loadUrl(arguments: NSDictionary, result: @escaping FlutterResult) { - let url: String? = (arguments["url"] as? String)! - let headers = (arguments["headers"] as? [String: String])! + func open(uuid: String, uuidFallback: String?, inAppBrowser url: URL, headers: [String: String], withOptions options: [String: Any], useChromeSafariBrowser: Bool, withOptionsFallback optionsFallback: [String: Any]?) { - if url != nil { - let absoluteUrl = URL(string: url!)!.absoluteURL - webViewController?.loadUrl(url: absoluteUrl, headers: headers) - } - else { - print("url is empty") - result(FlutterError(code: "InAppBrowserFlutterPlugin", message: "url is empty", details: nil)) - } - result(true) - } - - func open(inAppBrowser url: URL, headers: [String: String], withOptions options: [String: Any], useChromeSafariBrowser: Bool, withOptionsFallback optionsFallback: [String: Any]?) { + var uuid = uuid - if webViewController != nil { - close() + if self.webViewControllers[uuid] != nil { + close(uuid: uuid) } + let safariViewController = self.safariViewControllers[uuid] + if safariViewController != nil { if #available(iOS 9.0, *) { (safariViewController! as! SafariViewController).close() - safariViewController = nil + self.safariViewControllers[uuid] = nil } else { // Fallback on earlier versions } @@ -230,19 +253,25 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { safari = SafariViewController(url: url) } + safari.uuid = uuid safari.delegate = safari safari.statusDelegate = self safari.tmpWindow = tmpWindow safari.safariOptions = safariOptions - safariViewController = safari + self.safariViewControllers[uuid] = safari - tmpController.present(safariViewController! as! SFSafariViewController, animated: true) - onChromeSafariBrowserOpened() + tmpController.present(self.safariViewControllers[uuid]! as! SFSafariViewController, animated: true) + onChromeSafariBrowserOpened(uuid: uuid) return } else { + if uuidFallback == nil { + print("No WebView fallback declared.") + return + } + uuid = uuidFallback! browserOptions = InAppBrowserOptions() browserOptions.parse(options: optionsFallback!) } @@ -254,17 +283,19 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { let storyboard = UIStoryboard(name: WEBVIEW_STORYBOARD, bundle: nil) let vc = storyboard.instantiateViewController(withIdentifier: WEBVIEW_STORYBOARD_CONTROLLER_ID) - webViewController = vc as? InAppBrowserWebViewController - webViewController?.browserOptions = browserOptions - webViewController?.isHidden = browserOptions.hidden - webViewController?.tmpWindow = tmpWindow - webViewController?.currentURL = url - webViewController?.initHeaders = headers - webViewController?.navigationDelegate = self + self.webViewControllers[uuid] = vc as? InAppBrowserWebViewController + let webViewController: InAppBrowserWebViewController = self.webViewControllers[uuid] as! InAppBrowserWebViewController + webViewController.uuid = uuid + webViewController.browserOptions = browserOptions + webViewController.isHidden = browserOptions.hidden + webViewController.tmpWindow = tmpWindow + webViewController.currentURL = url + webViewController.initHeaders = headers + webViewController.navigationDelegate = self if browserOptions.hidden { - webViewController!.view.isHidden = true - tmpController.present(self.webViewController!, animated: false, completion: {() -> Void in + webViewController.view.isHidden = true + tmpController.present(webViewController, animated: false, completion: {() -> Void in // if self.previousStatusBarStyle != -1 { // UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: self.previousStatusBarStyle)! // } @@ -272,65 +303,89 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { // if self.previousStatusBarStyle != -1 { // UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: self.previousStatusBarStyle)! // } - webViewController?.presentingViewController?.dismiss(animated: false, completion: {() -> Void in + webViewController.presentingViewController?.dismiss(animated: false, completion: {() -> Void in self.tmpWindow?.windowLevel = 0.0 UIApplication.shared.delegate?.window??.makeKeyAndVisible() }) } else { - tmpController.present(webViewController!, animated: true, completion: nil) + tmpController.present(webViewController, animated: true, completion: nil) } } - public func show() { + public func loadUrl(uuid: String, arguments: NSDictionary, result: @escaping FlutterResult) { + let webViewController: InAppBrowserWebViewController = self.webViewControllers[uuid] as! InAppBrowserWebViewController + let url: String? = (arguments["url"] as? String)! + let headers = (arguments["headers"] as? [String: String])! - if webViewController == nil { - print("Tried to hide IAB after it was closed.") - return + if url != nil { + let absoluteUrl = URL(string: url!)!.absoluteURL + webViewController.loadUrl(url: absoluteUrl, headers: headers) } - - self.webViewController?.isHidden = false - self.webViewController!.view.isHidden = false - - // Run later to avoid the "took a long time" log message. - DispatchQueue.main.async(execute: {() -> Void in - if self.webViewController != nil { - let baseWindowLevel = UIApplication.shared.keyWindow?.windowLevel - self.tmpWindow?.windowLevel = UIWindowLevel(baseWindowLevel! + 1) - self.tmpWindow?.makeKeyAndVisible() - UIApplication.shared.delegate?.window??.makeKeyAndVisible() - self.tmpWindow?.rootViewController?.present(self.webViewController!, animated: true, completion: nil) - } - }) + else { + print("url is empty") + result(FlutterError(code: "InAppBrowserFlutterPlugin", message: "url is empty", details: nil)) + } + result(true) } - - public func hide() { - if webViewController == nil { - print("Tried to hide IAB after it was closed.") - return - } - - if self.webViewController != nil { - self.webViewController?.isHidden = true - } - - // Run later to avoid the "took a long time" log message. - DispatchQueue.main.async(execute: {() -> Void in - if self.webViewController != nil { - self.webViewController?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in - self.tmpWindow?.windowLevel = 0.0 - UIApplication.shared.delegate?.window??.makeKeyAndVisible() - if self.previousStatusBarStyle != -1 { - UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: self.previousStatusBarStyle)! + + public func show(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + if webViewController != nil { + webViewController?.isHidden = false + webViewController?.view.isHidden = false + + // Run later to avoid the "took a long time" log message. + DispatchQueue.main.async(execute: {() -> Void in + if webViewController != nil { + let baseWindowLevel = UIApplication.shared.keyWindow?.windowLevel + self.tmpWindow?.windowLevel = UIWindowLevel(baseWindowLevel! + 1) + self.tmpWindow?.makeKeyAndVisible() + UIApplication.shared.delegate?.window??.makeKeyAndVisible() + self.tmpWindow?.rootViewController?.present(webViewController!, animated: true, completion: nil) } }) } - }) + else { + print("Tried to hide IAB after it was closed.") + } + } + } + + public func hide(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + if webViewController != nil { + webViewController?.isHidden = true + + // Run later to avoid the "took a long time" log message. + DispatchQueue.main.async(execute: {() -> Void in + if webViewController != nil { + webViewController?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in + self.tmpWindow?.windowLevel = 0.0 + UIApplication.shared.delegate?.window??.makeKeyAndVisible() + if self.previousStatusBarStyle != -1 { + UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: self.previousStatusBarStyle)! + } + }) + } + }) + } + else { + print("Tried to hide IAB after it was closed.") + } + } } func open(inSystem url: URL) { - if UIApplication.shared.openURL(url) == false { - NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "CDVPluginHandleOpenURLNotification"), object: url)) + if !UIApplication.shared.canOpenURL(url) { + NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "PluginHandleOpenURLNotification"), object: url)) + return + } + + if #available(iOS 10.0, *) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } else { + UIApplication.shared.openURL(url) } } @@ -342,113 +397,131 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin { // '%@' marker). // // If no wrapper is supplied, then the source string is executed directly. - func injectDeferredObject(_ source: String, withWrapper jsWrapper: String, result: FlutterResult?) { - let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: []) - let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8) - if sourceArrayString != nil { - let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.characters.count ?? 0) - 2)) - let jsToInject = String(format: jsWrapper, sourceString!) - - webViewController?.webView?.evaluateJavaScript(jsToInject, completionHandler: {(value, error) in - if result == nil { - return - } + func injectDeferredObject(uuid: String, source: String, withWrapper jsWrapper: String, result: FlutterResult?) { + if let webViewController = self.webViewControllers[uuid] { + let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: []) + let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8) + if sourceArrayString != nil { + let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.characters.count ?? 0) - 2)) + let jsToInject = String(format: jsWrapper, sourceString!) - do { - let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)! - let json: Array = try JSONSerialization.jsonObject(with: data, options: []) as! Array - if json[0] is String { - result!(json[0]) + webViewController?.webView?.evaluateJavaScript(jsToInject, completionHandler: {(value, error) in + if result == nil { + return } - else { - result!(value) + + do { + let data: Data = ("[" + String(describing: value!) + "]").data(using: String.Encoding.utf8, allowLossyConversion: false)! + let json: Array = try JSONSerialization.jsonObject(with: data, options: []) as! Array + if json[0] is String { + result!(json[0]) + } + else { + result!(value) + } + } catch let error as NSError { + print("Failed to load: \(error.localizedDescription)") + result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error)) } - } catch let error as NSError { - print("Failed to load: \(error.localizedDescription)") - result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error)) - } - - }) + + }) + } } } - public func injectScriptCode(arguments: NSDictionary, result: FlutterResult?) { + public func injectScriptCode(uuid: String, arguments: NSDictionary, result: FlutterResult?) { let jsWrapper = "(function(){return JSON.stringify(eval(%@));})();" - injectDeferredObject(arguments["source"] as! String, withWrapper: jsWrapper, result: result) + injectDeferredObject(uuid: uuid, source: arguments["source"] as! String, withWrapper: jsWrapper, result: result) } - public func injectScriptFile(arguments: NSDictionary, result: FlutterResult?) { + public func injectScriptFile(uuid: String, arguments: NSDictionary, result: FlutterResult?) { let jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(arguments["urlFile"] as! String, withWrapper: jsWrapper, result: result) + injectDeferredObject(uuid: uuid, source: arguments["urlFile"] as! String, withWrapper: jsWrapper, result: result) } - public func injectStyleCode(arguments: NSDictionary, result: FlutterResult?) { + public func injectStyleCode(uuid: String, arguments: NSDictionary, result: FlutterResult?) { let jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(arguments["source"] as! String, withWrapper: jsWrapper, result: result) + injectDeferredObject(uuid: uuid, source: arguments["source"] as! String, withWrapper: jsWrapper, result: result) } - public func injectStyleFile(arguments: NSDictionary, result: FlutterResult?) { + public func injectStyleFile(uuid: String, arguments: NSDictionary, result: FlutterResult?) { let jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document);" - injectDeferredObject(arguments["urlFile"] as! String, withWrapper: jsWrapper, result: result) + injectDeferredObject(uuid: uuid, source: arguments["urlFile"] as! String, withWrapper: jsWrapper, result: result) } - func onLoadStart(_ webView: WKWebView) { - let url: String = webViewController!.currentURL!.absoluteString - channel.invokeMethod("onLoadStart", arguments: ["url": url]) - } - - func onLoadStop(_ webView: WKWebView) { - let url: String = webViewController!.currentURL!.absoluteString - channel.invokeMethod("onLoadStop", arguments: ["url": url]) - } - - func onLoadError(_ webView: WKWebView, error: Error) { - let url: String = webViewController!.currentURL!.absoluteString - let arguments = ["url": url, "code": error._code, "message": error.localizedDescription] as [String : Any] - channel.invokeMethod("onLoadError", arguments: arguments) - } - - func onExit() { - channel.invokeMethod("onExit", arguments: []) - } - - func shouldOverrideUrlLoading(_ webView: WKWebView, url: URL) { - channel.invokeMethod("shouldOverrideUrlLoading", arguments: ["url": url.absoluteString]) - } - - func onChromeSafariBrowserOpened() { - channel.invokeMethod("onChromeSafariBrowserOpened", arguments: []) - } - - func onChromeSafariBrowserLoaded() { - channel.invokeMethod("onChromeSafariBrowserLoaded", arguments: []) - } - - func onChromeSafariBrowserClosed() { - channel.invokeMethod("onChromeSafariBrowserClosed", arguments: []) - } - - func safariExit() { - if #available(iOS 9.0, *) { - (safariViewController as! SafariViewController).statusDelegate = nil - (safariViewController as! SafariViewController).delegate = nil + func onLoadStart(uuid: String, webView: WKWebView) { + if let webViewController = self.webViewControllers[uuid] { + let url: String = webViewController!.currentURL!.absoluteString + channel.invokeMethod("onLoadStart", arguments: ["uuid": uuid, "url": url]) } - safariViewController = nil - onChromeSafariBrowserClosed() } - func browserExit() { - // Set navigationDelegate to nil to ensure no callbacks are received from it. - webViewController?.navigationDelegate = nil - // Don't recycle the ViewController since it may be consuming a lot of memory. - // Also - this is required for the PDF/User-Agent bug work-around. - webViewController = nil - - if previousStatusBarStyle != -1 { - UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: previousStatusBarStyle)! + func onLoadStop(uuid: String, webView: WKWebView) { + if let webViewController = self.webViewControllers[uuid] { + let url: String = webViewController!.currentURL!.absoluteString + channel.invokeMethod("onLoadStop", arguments: ["uuid": uuid, "url": url]) + } + } + + func onLoadError(uuid: String, webView: WKWebView, error: Error) { + if let webViewController = self.webViewControllers[uuid] { + let url: String = webViewController!.currentURL!.absoluteString + let arguments = ["uuid": uuid, "url": url, "code": error._code, "message": error.localizedDescription] as [String : Any] + channel.invokeMethod("onLoadError", arguments: arguments) + } + } + + func onExit(uuid: String) { + channel.invokeMethod("onExit", arguments: ["uuid": uuid]) + } + + func shouldOverrideUrlLoading(uuid: String, webView: WKWebView, url: URL) { + if let webViewController = self.webViewControllers[uuid] { + channel.invokeMethod("shouldOverrideUrlLoading", arguments: ["uuid": uuid, "url": url.absoluteString]) + } + } + + func onChromeSafariBrowserOpened(uuid: String) { + if let safariViewController = self.safariViewControllers[uuid] { + channel.invokeMethod("onChromeSafariBrowserOpened", arguments: ["uuid": uuid]) + } + } + + func onChromeSafariBrowserLoaded(uuid: String) { + if let safariViewController = self.safariViewControllers[uuid] { + channel.invokeMethod("onChromeSafariBrowserLoaded", arguments: ["uuid": uuid]) + } + } + + func onChromeSafariBrowserClosed(uuid: String) { + channel.invokeMethod("onChromeSafariBrowserClosed", arguments: ["uuid": uuid]) + } + + func safariExit(uuid: String) { + if let safariViewController = self.safariViewControllers[uuid] { + if #available(iOS 9.0, *) { + (safariViewController as! SafariViewController).statusDelegate = nil + (safariViewController as! SafariViewController).delegate = nil + } + self.safariViewControllers[uuid] = nil + onChromeSafariBrowserClosed(uuid: uuid) + } + } + + func browserExit(uuid: String) { + if let webViewController = self.webViewControllers[uuid] { + // Set navigationDelegate to nil to ensure no callbacks are received from it. + webViewController?.navigationDelegate = nil + // Don't recycle the ViewController since it may be consuming a lot of memory. + // Also - this is required for the PDF/User-Agent bug work-around. + self.webViewControllers[uuid] = nil + + if previousStatusBarStyle != -1 { + UIApplication.shared.statusBarStyle = UIStatusBarStyle(rawValue: previousStatusBarStyle)! + } + + onExit(uuid: uuid) } - - onExit() } } diff --git a/lib/flutter_inappbrowser.dart b/lib/flutter_inappbrowser.dart index f1e86ac9..31727515 100644 --- a/lib/flutter_inappbrowser.dart +++ b/lib/flutter_inappbrowser.dart @@ -20,25 +20,30 @@ */ import 'dart:async'; +import 'dart:collection'; import 'package:flutter/services.dart'; +import 'package:uuid/uuid.dart'; + +typedef Future ListenerCallback(MethodCall call); + +var uuidGenerator = new Uuid(); class _ChannelManager { static const MethodChannel channel = const MethodChannel('com.pichillilorenzo/flutter_inappbrowser'); static final initialized = false; - static final listeners = []; + static final listeners = HashMap(); static Future _handleMethod(MethodCall call) async { - for (var listener in listeners) { - listener(call); - } + String uuid = call.arguments["uuid"]; + listeners[uuid](call); return new Future.value(""); } - static void addListener (Function callback) { + static void addListener (String key, ListenerCallback callback) { if (!initialized) init(); - listeners.add(callback); + listeners.putIfAbsent(key, () => callback); } static void init () { @@ -51,9 +56,12 @@ class _ChannelManager { /// This class uses the native WebView of the platform. class InAppBrowser { + String uuid; + /// InAppBrowser () { - _ChannelManager.addListener(_handleMethod); + uuid = uuidGenerator.v4(); + _ChannelManager.addListener(uuid, _handleMethod); } Future _handleMethod(MethodCall call) async { @@ -144,6 +152,7 @@ class InAppBrowser { /// - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`. Future open(String url, {Map headers = const {}, String target = "_self", Map options = const {}}) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headers); args.putIfAbsent('target', () => target); @@ -155,6 +164,7 @@ class InAppBrowser { ///Loads the given [url] with optional [headers] specified as a map from name to value. Future loadUrl(String url, {Map headers = const {}}) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headers); return await _ChannelManager.channel.invokeMethod('loadUrl', args); @@ -162,52 +172,71 @@ class InAppBrowser { ///Displays an [InAppBrowser] window that was opened hidden. Calling this has no effect if the [InAppBrowser] was already visible. Future show() async { - return await _ChannelManager.channel.invokeMethod('show'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('show', args); } ///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden. Future hide() async { - return await _ChannelManager.channel.invokeMethod('hide'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('hide', args); } ///Closes the [InAppBrowser] window. Future close() async { - return await _ChannelManager.channel.invokeMethod('close'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('close', args); } ///Reloads the [InAppBrowser] window. Future reload() async { - return await _ChannelManager.channel.invokeMethod('reload'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('reload', args); } ///Goes back in the history of the [InAppBrowser] window. Future goBack() async { - return await _ChannelManager.channel.invokeMethod('goBack'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('goBack', args); } ///Goes forward in the history of the [InAppBrowser] window. Future goForward() async { - return await _ChannelManager.channel.invokeMethod('goForward'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('goForward', args); } ///Check if the Web View of the [InAppBrowser] instance is in a loading state. Future isLoading() async { - return await _ChannelManager.channel.invokeMethod('isLoading'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('isLoading', args); } ///Stops the Web View of the [InAppBrowser] instance from loading. Future stopLoading() async { - return await _ChannelManager.channel.invokeMethod('stopLoading'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('stopLoading', args); } ///Check if the Web View of the [InAppBrowser] instance is hidden. Future isHidden() async { - return await _ChannelManager.channel.invokeMethod('isHidden'); + Map args = {}; + args.putIfAbsent('uuid', () => uuid); + return await _ChannelManager.channel.invokeMethod('isHidden', args); } ///Injects JavaScript code into the [InAppBrowser] window and returns the result of the evaluation. (Only available when the target is set to `_blank` or to `_self`) Future injectScriptCode(String source) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('source', () => source); return await _ChannelManager.channel.invokeMethod('injectScriptCode', args); } @@ -215,6 +244,7 @@ class InAppBrowser { ///Injects a JavaScript file into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) Future injectScriptFile(String urlFile) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('urlFile', () => urlFile); return await _ChannelManager.channel.invokeMethod('injectScriptFile', args); } @@ -222,6 +252,7 @@ class InAppBrowser { ///Injects CSS into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) Future injectStyleCode(String source) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('source', () => source); return await _ChannelManager.channel.invokeMethod('injectStyleCode', args); } @@ -229,6 +260,7 @@ class InAppBrowser { ///Injects a CSS file into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) Future injectStyleFile(String urlFile) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('urlFile', () => urlFile); return await _ChannelManager.channel.invokeMethod('injectStyleFile', args); } @@ -268,12 +300,14 @@ class InAppBrowser { /// ///[browserFallback] represents the [InAppBrowser] instance fallback in case [Chrome Custom Tabs]/[SFSafariViewController] is not available. class ChromeSafariBrowser { + String uuid; InAppBrowser browserFallback; /// - ChromeSafariBrowser (browserFallback) { - this.browserFallback = browserFallback; - _ChannelManager.addListener(_handleMethod); + ChromeSafariBrowser (bf) { + uuid = uuidGenerator.v4(); + browserFallback = bf; + _ChannelManager.addListener(uuid, _handleMethod); } Future _handleMethod(MethodCall call) async { @@ -294,6 +328,9 @@ class ChromeSafariBrowser { /// Future open(String url, {Map options = const {}, Map headersFallback = const {}, Map optionsFallback = const {}}) async { Map args = {}; + args.putIfAbsent('uuid', () => uuid); + print(browserFallback.uuid); + args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : ''); args.putIfAbsent('url', () => url); args.putIfAbsent('headers', () => headersFallback); args.putIfAbsent('target', () => ""); diff --git a/pubspec.yaml b/pubspec.yaml index 131f2fdb..64207085 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_inappbrowser description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser). -version: 0.1.1 +version: 0.2.0 author: Lorenzo Pichilli homepage: https://github.com/pichillilorenzo/flutter_inappbrowser @@ -10,6 +10,7 @@ environment: dependencies: flutter: sdk: flutter + uuid: ^1.0.3 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec