This commit is contained in:
pichillilorenzo 2018-10-16 01:27:58 +02:00
parent c7356f33c8
commit 860a42e2ba
26 changed files with 1751 additions and 1237 deletions

281
.idea/workspace.xml generated
View File

@ -15,13 +15,30 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="9b41f7a2-a71e-4923-91fb-249d7815b3e7" name="Default" comment=""> <list default="true" id="9b41f7a2-a71e-4923-91fb-249d7815b3e7" name="Default" comment="">
<change afterPath="$PROJECT_DIR$/example/assets/css/style.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/example/assets/images/dart.svg" afterDir="false" />
<change afterPath="$PROJECT_DIR$/example/assets/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/CHANGELOG.md" beforeDir="false" afterPath="$PROJECT_DIR$/CHANGELOG.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/example/ios/Runner.xcodeproj/project.pbxproj" beforeDir="false" afterPath="$PROJECT_DIR$/example/ios/Runner.xcodeproj/project.pbxproj" afterDir="false" /> <change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserOptions.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebChromeClient.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebChromeClient.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserWebViewClient.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/JavaScriptBridgeInterface.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Options.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/Options.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/WebViewActivity.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/ChromeCustomTabsActivity.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/CustomTabActivityHelper.java" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/chrome_custom_tabs/CustomTabActivityHelper.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/example/ios/Runner/Info.plist" beforeDir="false" afterPath="$PROJECT_DIR$/example/ios/Runner/Info.plist" afterDir="false" />
<change beforePath="$PROJECT_DIR$/example/lib/main.dart" beforeDir="false" afterPath="$PROJECT_DIR$/example/lib/main.dart" afterDir="false" />
<change beforePath="$PROJECT_DIR$/example/pubspec.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/example/pubspec.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/flutter_inappbrowser.iml" beforeDir="false" afterPath="$PROJECT_DIR$/flutter_inappbrowser.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Classes/InAppBrowserOptions.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/InAppBrowserOptions.swift" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Classes/InAppBrowserWebViewController.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/InAppBrowserWebViewController.swift" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Classes/Options.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/Options.swift" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Classes/SafariViewController.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/SafariViewController.swift" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" afterDir="false" /> <change beforePath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" afterDir="false" />
<change beforePath="$PROJECT_DIR$/ios/Storyboards/en.lproj/WebView.storyboard" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/ios/flutter_inappbrowser.podspec" beforeDir="false" afterPath="$PROJECT_DIR$/ios/flutter_inappbrowser.podspec" afterDir="false" />
<change beforePath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" beforeDir="false" afterPath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" afterDir="false" /> <change beforePath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" beforeDir="false" afterPath="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pubspec.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pubspec.yaml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pubspec.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pubspec.yaml" afterDir="false" />
</list> </list>
@ -43,20 +60,25 @@
<file leaf-file-name="flutter_inappbrowser.dart" pinned="false" current-in-tab="false"> <file leaf-file-name="flutter_inappbrowser.dart" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart"> <entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="5791"> <state relative-caret-position="128">
<caret line="429" column="3" selection-start-line="429" selection-start-column="3" selection-end-line="429" selection-end-column="3" /> <caret line="82" column="21" selection-start-line="82" selection-start-column="6" selection-end-line="82" selection-end-column="21" />
<folding> <folding>
<element signature="e#814#834#0" expanded="true" /> <element signature="e#814#831#0" expanded="true" />
</folding> </folding>
</state> </state>
</provider> </provider>
</entry> </entry>
</file> </file>
<file leaf-file-name="pubspec.yaml" pinned="false" current-in-tab="false"> <file leaf-file-name="README.md" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/pubspec.yaml"> <entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
<state relative-caret-position="30"> <state split_layout="SPLIT">
<caret line="2" column="14" selection-start-line="2" selection-start-column="14" selection-end-line="2" selection-end-column="14" /> <first_editor relative-caret-position="4980">
<caret line="332" column="20" lean-forward="true" selection-start-line="332" selection-start-column="20" selection-end-line="332" selection-end-column="41" />
</first_editor>
<second_editor>
<markdownNavigatorState />
</second_editor>
</state> </state>
</provider> </provider>
</entry> </entry>
@ -65,8 +87,8 @@
<entry file="file://$PROJECT_DIR$/CHANGELOG.md"> <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
<provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]"> <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
<state split_layout="SPLIT"> <state split_layout="SPLIT">
<first_editor relative-caret-position="30"> <first_editor relative-caret-position="135">
<caret line="2" column="39" selection-start-line="2" selection-start-column="39" selection-end-line="2" selection-end-column="39" /> <caret line="9" lean-forward="true" selection-start-line="9" selection-end-line="9" />
</first_editor> </first_editor>
<second_editor> <second_editor>
<markdownNavigatorState /> <markdownNavigatorState />
@ -75,6 +97,18 @@
</provider> </provider>
</entry> </entry>
</file> </file>
<file leaf-file-name="main.dart" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="165">
<caret line="165" column="42" selection-start-line="165" selection-start-column="42" selection-end-line="165" selection-end-column="42" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf> </leaf>
</component> </component>
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
@ -86,11 +120,6 @@
</component> </component>
<component name="FindInProjectRecents"> <component name="FindInProjectRecents">
<findStrings> <findStrings>
<find>loadUrl</find>
<find>showWebPage</find>
<find>InAppBrowserClient</find>
<find>LOG.</find>
<find>preferences</find>
<find>getPre</find> <find>getPre</find>
<find>client</find> <find>client</find>
<find>webView</find> <find>webView</find>
@ -116,6 +145,11 @@
<find>presentationStyle</find> <find>presentationStyle</find>
<find>###</find> <find>###</find>
<find>inAppBrowserFallback</find> <find>inAppBrowserFallback</find>
<find>target</find>
<find>.assets</find>
<find>_blank</find>
<find>.html</find>
<find>close(</find>
</findStrings> </findStrings>
<replaceStrings> <replaceStrings>
<replace>activity.getPreferences(0)</replace> <replace>activity.getPreferences(0)</replace>
@ -142,7 +176,6 @@
<option value="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutterwebview/InAppBrowserClient.java" /> <option value="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutterwebview/InAppBrowserClient.java" />
<option value="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutterwebview/InAppBrowserDialog.java" /> <option value="$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutterwebview/InAppBrowserDialog.java" />
<option value="$PROJECT_DIR$/android/settings.gradle" /> <option value="$PROJECT_DIR$/android/settings.gradle" />
<option value="$PROJECT_DIR$/example/pubspec.yaml" />
<option value="$PROJECT_DIR$/ios/Classes/InAppBrowser.m" /> <option value="$PROJECT_DIR$/ios/Classes/InAppBrowser.m" />
<option value="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" /> <option value="$PROJECT_DIR$/ios/Classes/SwiftFlutterPlugin.swift" />
<option value="$PROJECT_DIR$/LICENSE" /> <option value="$PROJECT_DIR$/LICENSE" />
@ -152,17 +185,23 @@
<option value="$PROJECT_DIR$/ios/flutter_inappbrowser.podspec" /> <option value="$PROJECT_DIR$/ios/flutter_inappbrowser.podspec" />
<option value="$PROJECT_DIR$/example/ios/Podfile" /> <option value="$PROJECT_DIR$/example/ios/Podfile" />
<option value="$PROJECT_DIR$/android/build.gradle" /> <option value="$PROJECT_DIR$/android/build.gradle" />
<option value="$PROJECT_DIR$/example/lib/main.dart" /> <option value="$PROJECT_DIR$/example/html/css/style.css" />
<option value="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" /> <option value="$PROJECT_DIR$/example/html/index.html" />
<option value="$PROJECT_DIR$/README.md" />
<option value="$PROJECT_DIR$/pubspec.yaml" /> <option value="$PROJECT_DIR$/pubspec.yaml" />
<option value="$PROJECT_DIR$/example/assets/css/style.css" />
<option value="$PROJECT_DIR$/example/assets/index.html" />
<option value="$PROJECT_DIR$/example/pubspec.yaml" />
<option value="$PROJECT_DIR$/example/lib/main.dart" />
<option value="$PROJECT_DIR$/README.md" />
<option value="$PROJECT_DIR$/lib/flutter_inappbrowser.dart" />
<option value="$PROJECT_DIR$/CHANGELOG.md" /> <option value="$PROJECT_DIR$/CHANGELOG.md" />
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectFrameBounds"> <component name="ProjectFrameBounds">
<option name="y" value="23" /> <option name="x" value="-1920" />
<option name="width" value="1920" /> <option name="y" value="-21" />
<option name="width" value="1711" />
<option name="height" value="1057" /> <option name="height" value="1057" />
</component> </component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" /> <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
@ -171,6 +210,18 @@
<foldersAlwaysOnTop value="true" /> <foldersAlwaysOnTop value="true" />
</navigator> </navigator>
<panes> <panes>
<pane id="Scope">
<subPane subId="Project Files">
<expand>
<path>
<item name="Root" type="cbb8eebc:String" user="Root" />
<item name="flutter_inappbrowser" type="cbb8eebc:String" user="flutter_inappbrowser" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="PackagesPane" />
<pane id="ProjectPane"> <pane id="ProjectPane">
<subPane> <subPane>
<expand> <expand>
@ -183,6 +234,23 @@
<item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" /> <item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" />
<item name="android" type="462c0819:PsiDirectoryNode" /> <item name="android" type="462c0819:PsiDirectoryNode" />
</path> </path>
<path>
<item name="flutter_inappbrowser" type="b2602c69:ProjectViewProjectNode" />
<item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" />
<item name="example" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="flutter_inappbrowser" type="b2602c69:ProjectViewProjectNode" />
<item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" />
<item name="example" type="462c0819:PsiDirectoryNode" />
<item name="assets" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="flutter_inappbrowser" type="b2602c69:ProjectViewProjectNode" />
<item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" />
<item name="example" type="462c0819:PsiDirectoryNode" />
<item name="lib" type="462c0819:PsiDirectoryNode" />
</path>
<path> <path>
<item name="flutter_inappbrowser" type="b2602c69:ProjectViewProjectNode" /> <item name="flutter_inappbrowser" type="b2602c69:ProjectViewProjectNode" />
<item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" /> <item name="flutter_inappbrowser" type="462c0819:PsiDirectoryNode" />
@ -193,19 +261,7 @@
</subPane> </subPane>
<option name="show-excluded-files" value="false" /> <option name="show-excluded-files" value="false" />
</pane> </pane>
<pane id="PackagesPane" />
<pane id="AndroidView" /> <pane id="AndroidView" />
<pane id="Scope">
<subPane subId="Project Files">
<expand>
<path>
<item name="Root" type="cbb8eebc:String" user="Root" />
<item name="flutter_inappbrowser" type="cbb8eebc:String" user="flutter_inappbrowser" />
</path>
</expand>
<select />
</subPane>
</pane>
</panes> </panes>
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">
@ -224,12 +280,13 @@
<property name="show.migrate.to.gradle.popup" value="false" /> <property name="show.migrate.to.gradle.popup" value="false" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/android/libs" />
</key>
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/android/src/main/java" /> <recent name="$PROJECT_DIR$/android/src/main/java" />
</key> </key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/example/assets/images" />
<recent name="$PROJECT_DIR$/android/libs" />
</key>
</component> </component>
<component name="RunDashboard"> <component name="RunDashboard">
<option name="ruleStates"> <option name="ruleStates">
@ -361,7 +418,7 @@
<servers /> <servers />
</component> </component>
<component name="ToolWindowManager"> <component name="ToolWindowManager">
<frame x="0" y="23" width="1920" height="1057" extended-state="0" /> <frame x="-1920" y="-21" width="1711" height="1057" extended-state="0" />
<editor active="true" /> <editor active="true" />
<layout> <layout>
<window_info anchor="bottom" id="Android Profiler" order="7" show_stripe_button="false" /> <window_info anchor="bottom" id="Android Profiler" order="7" show_stripe_button="false" />
@ -372,22 +429,22 @@
<window_info anchor="right" id="Capture Analysis" order="3" /> <window_info anchor="right" id="Capture Analysis" order="3" />
<window_info anchor="bottom" id="Event Log" order="8" sideWeight="0.5035553" side_tool="true" weight="0.25689086" /> <window_info anchor="bottom" id="Event Log" order="8" sideWeight="0.5035553" side_tool="true" weight="0.25689086" />
<window_info anchor="bottom" id="Dart Analysis" order="14" weight="0.32855567" /> <window_info anchor="bottom" id="Dart Analysis" order="14" weight="0.32855567" />
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.49644473" weight="0.20384204" /> <window_info anchor="bottom" id="Run" order="2" sideWeight="0.49644473" weight="0.3628602" />
<window_info anchor="bottom" id="Version Control" order="9" /> <window_info anchor="bottom" id="Version Control" order="9" />
<window_info anchor="bottom" id="Terminal" order="10" sideWeight="0.49644473" visible="true" weight="0.25506938" /> <window_info anchor="bottom" id="Terminal" order="10" sideWeight="0.49644473" visible="true" weight="0.2529349" />
<window_info anchor="right" id="Flutter Outline" order="3" weight="0.32922077" /> <window_info anchor="right" id="Flutter Outline" order="3" weight="0.32922077" />
<window_info anchor="bottom" id="Logcat" order="11" /> <window_info anchor="bottom" id="Logcat" order="11" />
<window_info id="Captures" order="2" weight="0.32936507" /> <window_info id="Captures" order="2" weight="0.32936507" />
<window_info id="Capture Tool" order="2" /> <window_info id="Capture Tool" order="2" />
<window_info id="Designer" order="2" /> <window_info id="Designer" order="2" />
<window_info active="true" content_ui="combo" id="Project" order="0" sideWeight="0.49855492" visible="true" weight="0.15069222" /> <window_info active="true" content_ui="combo" id="Project" order="0" sideWeight="0.49724367" visible="true" weight="0.16956261" />
<window_info id="Structure" order="1" sideWeight="0.5014451" side_tool="true" weight="0.18293472" /> <window_info id="Structure" order="1" sideWeight="0.50275636" side_tool="true" weight="0.1910871" />
<window_info anchor="right" id="Device File Explorer" order="3" side_tool="true" /> <window_info anchor="right" id="Device File Explorer" order="3" side_tool="true" />
<window_info anchor="right" id="Theme Preview" order="3" /> <window_info anchor="right" id="Theme Preview" order="3" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" /> <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info id="Favorites" order="2" side_tool="true" /> <window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="right" id="Flutter Inspector" order="3" weight="0.32987013" /> <window_info anchor="right" id="Flutter Inspector" order="3" weight="0.32987013" />
<window_info anchor="bottom" id="Messages" order="12" weight="0.2370452" /> <window_info anchor="bottom" id="Messages" order="12" weight="0.23692636" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" /> <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="right" id="Commander" order="0" weight="0.4" /> <window_info anchor="right" id="Commander" order="0" weight="0.4" />
<window_info anchor="right" id="Assistant" order="4" visible="true" weight="0.32987013" /> <window_info anchor="right" id="Assistant" order="4" visible="true" weight="0.32987013" />
@ -396,13 +453,12 @@
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" /> <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="bottom" id="Dependency Viewer" order="13" weight="0.32800853" /> <window_info anchor="bottom" id="Dependency Viewer" order="13" weight="0.32800853" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" /> <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="bottom" id="Find" order="1" weight="0.328125" /> <window_info anchor="bottom" id="Find" order="1" weight="0.3276414" />
</layout> </layout>
<layout-to-restore> <layout-to-restore>
<window_info id="Designer" order="4" /> <window_info id="Designer" order="4" />
<window_info anchor="bottom" id="Dart Analysis" order="14" weight="0.3290735" /> <window_info anchor="bottom" id="Dart Analysis" order="14" weight="0.3290735" />
<window_info id="Build Variants" order="2" side_tool="true" /> <window_info id="Build Variants" order="2" side_tool="true" />
<window_info id="Image Layers" order="5" />
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.4973545" weight="0.32161874" /> <window_info anchor="bottom" id="Run" order="2" sideWeight="0.4973545" weight="0.32161874" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" /> <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" /> <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
@ -414,25 +470,29 @@
<window_info anchor="bottom" id="Android Profiler" order="7" show_stripe_button="false" /> <window_info anchor="bottom" id="Android Profiler" order="7" show_stripe_button="false" />
<window_info anchor="right" id="Commander" order="0" weight="0.4" /> <window_info anchor="right" id="Commander" order="0" weight="0.4" />
<window_info anchor="bottom" id="Event Log" order="8" sideWeight="0.5026455" side_tool="true" weight="0.31735888" /> <window_info anchor="bottom" id="Event Log" order="8" sideWeight="0.5026455" side_tool="true" weight="0.31735888" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" /> <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.18376623" />
<window_info anchor="right" id="Device File Explorer" order="4" side_tool="true" /> <window_info anchor="right" id="Device File Explorer" order="4" side_tool="true" />
<window_info anchor="right" id="Flutter Outline" order="3" weight="0.32936507" /> <window_info anchor="right" id="Flutter Outline" order="3" weight="0.32936507" />
<window_info anchor="bottom" id="Logcat" order="11" /> <window_info anchor="bottom" id="Logcat" order="11" />
<window_info anchor="bottom" id="Dependency Viewer" order="13" weight="0.32800853" /> <window_info anchor="bottom" id="Dependency Viewer" order="13" weight="0.32800853" />
<window_info anchor="bottom" id="Version Control" order="9" /> <window_info anchor="bottom" id="Version Control" order="9" />
<window_info anchor="bottom" id="Terminal" order="10" sideWeight="0.4973545" visible="true" weight="0.27888888" /> <window_info anchor="bottom" id="Terminal" order="10" sideWeight="0.4973545" visible="true" weight="0.27888888" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" /> <window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.18376623" />
<window_info anchor="bottom" id="Messages" order="12" weight="0.23777778" /> <window_info anchor="bottom" id="Messages" order="12" weight="0.23777778" />
<window_info anchor="bottom" id="Message" order="0" /> <window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="TODO" order="6" /> <window_info id="Image Layers" order="5" />
<window_info anchor="right" id="Palette&#9;" order="5" /> <window_info anchor="right" id="Palette&#9;" order="5" />
<window_info anchor="right" id="Theme Preview" order="7" /> <window_info anchor="right" id="Theme Preview" order="7" />
<window_info id="Structure" order="1" weight="0.24969475" /> <window_info id="Structure" order="1" weight="0.24969475" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="Find" order="1" weight="0.3290735" /> <window_info anchor="bottom" id="Find" order="1" weight="0.3290735" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" /> <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
</layout-to-restore> </layout-to-restore>
</component> </component>
<component name="UnknownFeatures">
<option featureType="com.intellij.fileTypeFactory" implementationName="*.css" />
</component>
<component name="VcsContentAnnotationSettings"> <component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" /> <option name="myLimit" value="2678400000" />
</component> </component>
@ -442,21 +502,6 @@
</breakpoint-manager> </breakpoint-manager>
</component> </component>
<component name="editorHistoryManager"> <component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/example/pubspec.yaml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="270">
<caret line="18" column="34" lean-forward="true" selection-start-line="18" selection-start-column="34" selection-end-line="18" selection-end-column="34" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="135">
<caret line="9" column="34" lean-forward="true" selection-start-line="9" selection-start-column="34" selection-end-line="9" selection-end-column="34" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutterwebview/WebViewActivity.java" />
<entry file="file://$PROJECT_DIR$/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java"> <entry file="file://$PROJECT_DIR$/example/android/app/src/main/java/com/pichillilorenzo/flutterwebviewexample/MainActivity.java">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="135"> <state relative-caret-position="135">
@ -584,13 +629,6 @@
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/example/pubspec.yaml">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret column="6" selection-start-column="6" selection-end-column="26" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java"> <entry file="file://$PROJECT_DIR$/android/src/main/java/com/pichillilorenzo/flutter_inappbrowser/InAppBrowserFlutterPlugin.java">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1560"> <state relative-caret-position="1560">
@ -647,30 +685,6 @@
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state>
<caret line="1" column="64" selection-start-line="1" selection-start-column="64" selection-end-line="1" selection-end-column="64" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="256">
<caret line="52" column="66" selection-start-line="52" selection-start-column="66" selection-end-line="52" selection-end-column="66" />
</first_editor>
<second_editor>
<markdownNavigatorState />
</second_editor>
</state>
</provider>
<provider editor-type-id="text-editor">
<state relative-caret-position="247">
<caret line="19" column="15" selection-start-line="19" selection-start-column="3" selection-end-line="19" selection-end-column="15" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/flutter_inappbrowser.iml"> <entry file="file://$PROJECT_DIR$/flutter_inappbrowser.iml">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="330"> <state relative-caret-position="330">
@ -692,39 +706,92 @@
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart"> <entry file="file://$PROJECT_DIR$/example/assets/images/dart.svg">
<provider selected="true" editor-type-id="images">
<state />
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/example/assets/css/style.css">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="5791"> <state relative-caret-position="75">
<caret line="429" column="3" selection-start-line="429" selection-start-column="3" selection-end-line="429" selection-end-column="3" /> <caret line="5" column="20" selection-start-line="5" selection-start-column="20" selection-end-line="5" selection-end-column="20" />
<folding> </state>
<element signature="e#814#834#0" expanded="true" /> </provider>
</folding> </entry>
<entry file="file://$PROJECT_DIR$/example/assets/index.html">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="435">
<caret line="29" column="29" selection-start-line="29" selection-start-column="29" selection-end-line="29" selection-end-column="29" />
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/pubspec.yaml"> <entry file="file://$PROJECT_DIR$/pubspec.yaml">
<provider selected="true" editor-type-id="text-editor"> <provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="30"> <state relative-caret-position="180">
<caret line="2" column="14" selection-start-line="2" selection-start-column="14" selection-end-line="2" selection-end-column="14" /> <caret line="12" column="14" selection-start-line="12" selection-start-column="14" selection-end-line="12" selection-end-column="14" />
</state> </state>
</provider> </provider>
</entry> </entry>
<entry file="file://$PROJECT_DIR$/CHANGELOG.md"> <entry file="file://$PROJECT_DIR$/example/pubspec.yaml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="405">
<caret line="27" column="13" selection-start-line="27" selection-start-column="13" selection-end-line="27" selection-end-column="13" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/example/lib/main.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="165">
<caret line="165" column="42" selection-start-line="165" selection-start-column="42" selection-end-line="165" selection-end-column="42" />
<folding>
<element signature="e#0#20#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/lib/flutter_inappbrowser.dart">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="128">
<caret line="82" column="21" selection-start-line="82" selection-start-column="6" selection-end-line="82" selection-end-column="21" />
<folding>
<element signature="e#814#831#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/README.md">
<provider editor-type-id="text-editor">
<state relative-caret-position="247">
<caret line="19" column="15" selection-start-line="19" selection-start-column="3" selection-end-line="19" selection-end-column="15" />
</state>
</provider>
<provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]"> <provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
<state split_layout="SPLIT"> <state split_layout="SPLIT">
<first_editor relative-caret-position="30"> <first_editor relative-caret-position="4980">
<caret line="2" column="39" selection-start-line="2" selection-start-column="39" selection-end-line="2" selection-end-column="39" /> <caret line="332" column="20" lean-forward="true" selection-start-line="332" selection-start-column="20" selection-end-line="332" selection-end-column="41" />
</first_editor> </first_editor>
<second_editor> <second_editor>
<markdownNavigatorState /> <markdownNavigatorState />
</second_editor> </second_editor>
</state> </state>
</provider> </provider>
</entry>
<entry file="file://$PROJECT_DIR$/CHANGELOG.md">
<provider editor-type-id="text-editor"> <provider editor-type-id="text-editor">
<state relative-caret-position="30"> <state relative-caret-position="30">
<caret line="2" column="69" lean-forward="true" selection-start-line="2" selection-start-column="2" selection-end-line="2" selection-end-column="73" /> <caret line="2" column="69" lean-forward="true" selection-start-line="2" selection-start-column="2" selection-end-line="2" selection-end-column="73" />
</state> </state>
</provider> </provider>
<provider selected="true" editor-type-id="split-provider[text-editor;MarkdownPreviewEditor]">
<state split_layout="SPLIT">
<first_editor relative-caret-position="135">
<caret line="9" lean-forward="true" selection-start-line="9" selection-end-line="9" />
</first_editor>
<second_editor>
<markdownNavigatorState />
</second_editor>
</state>
</provider>
</entry> </entry>
</component> </component>
<component name="masterDetails"> <component name="masterDetails">

View File

@ -1,3 +1,13 @@
## 0.4.0
- removed `target` parameter to `InAppBrowser.open()` method. To open the url on the system browser, use the `openWithSystemBrowser: true` option
- fixes for the `_ChannelManager` private class
- fixed `EXC_BAD_INSTRUCTION` onLoadStart in Swift
- added `openWithSystemBrowser` and `isLocalFile` options
- added `InAppBrowser.openWithSystemBrowser` method
- added `InAppBrowser.openOnLocalhost` method
- added `InAppBrowser.loadFile` method
## 0.3.2 ## 0.3.2
- fixed WebView.storyboard path for iOS - fixed WebView.storyboard path for iOS

147
README.md
View File

@ -56,13 +56,13 @@ class MyInAppBrowser extends InAppBrowser {
await this.injectScriptCode("console.log({'testObject': 5});"); // the message will be: [object Object] await this.injectScriptCode("console.log({'testObject': 5});"); // the message will be: [object Object]
await this.injectScriptCode("console.log('testObjectStringify', JSON.stringify({'testObject': 5}));"); // the message will be: testObjectStringify {"testObject": 5} await this.injectScriptCode("console.log('testObjectStringify', JSON.stringify({'testObject': 5}));"); // the message will be: testObjectStringify {"testObject": 5}
await this.injectScriptCode("console.error('testError', false);"); // the message will be: testError false await this.injectScriptCode("console.error('testError', false);"); // the message will be: testError false
// add jquery library and custom javascript // add jquery library and custom javascript
await this.injectScriptFile("https://code.jquery.com/jquery-3.3.1.min.js"); await this.injectScriptFile("https://code.jquery.com/jquery-3.3.1.min.js");
this.injectScriptCode(""" this.injectScriptCode("""
\$( "body" ).html( "Next Step..." ) \$( "body" ).html( "Next Step..." )
"""); """);
// add custom css // add custom css
this.injectStyleCode(""" this.injectStyleCode("""
body { body {
@ -81,7 +81,7 @@ class MyInAppBrowser extends InAppBrowser {
void onExit() { void onExit() {
print("\n\nBrowser closed!\n\n"); print("\n\nBrowser closed!\n\n");
} }
@override @override
void shouldOverrideUrlLoading(String url) { void shouldOverrideUrlLoading(String url) {
print("\n\n override $url\n\n"); print("\n\n override $url\n\n");
@ -136,8 +136,8 @@ class _MyAppState extends State<MyApp> {
title: const Text('Flutter InAppBrowser Plugin example app'), title: const Text('Flutter InAppBrowser Plugin example app'),
), ),
body: new Center( body: new Center(
child: new RaisedButton(onPressed: () { child: new RaisedButton(onPressed: () async {
inAppBrowser.open(url: "https://flutter.io/", options: { await inAppBrowser.open(url: "https://flutter.io/", options: {
"useShouldOverrideUrlLoading": true, "useShouldOverrideUrlLoading": true,
"useOnLoadResource": true "useOnLoadResource": true
}); });
@ -155,8 +155,10 @@ class _MyAppState extends State<MyApp> {
Opens a URL in a new InAppBrowser instance or the system browser. Opens a URL in a new InAppBrowser instance or the system browser.
**NOTE**: If you open the given `url` with the system browser (`openWithSystemBrowser: true`), you wont be able to use the `InAppBrowser` methods!
```dart ```dart
inAppBrowser.open({String url = "about:blank", Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}}); inAppBrowser.open({String url = "about:blank", Map<String, String> headers = const {}, Map<String, dynamic> options = const {}});
``` ```
Opens an `url` in a new `InAppBrowser` instance or the system browser. Opens an `url` in a new `InAppBrowser` instance or the system browser.
@ -165,17 +167,13 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
- `headers`: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value. - `headers`: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value.
- `target`: The target in which to load the `url`, an optional parameter that defaults to `_self`.
- `_self`: Opens in the `InAppBrowser`.
- `_blank`: Opens in the `InAppBrowser`.
- `_system`: Opens in the system's web browser.
- `options`: Options for the `InAppBrowser`. - `options`: Options for the `InAppBrowser`.
All platforms support: All platforms support:
- __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`. - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the `shouldOverrideUrlLoading` event. The default value is `false`.
- __useOnLoadResource__: Set to `true` to be able to listen at the `onLoadResource()` event. The default value is `false`. - __useOnLoadResource__: Set to `true` to be able to listen at the `onLoadResource()` event. The default value is `false`.
- __openWithSystemBrowser__: Set to `true` to open the given `url` with the system browser. The default value is `false`.
- __isLocalFile__: Set to `true` if the `url` is pointing to a local file (the file must be addded in the `assets` section of your `pubspec.yaml`. See `loadFile()` explanation). The default value is `false`.
- __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`. - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
- __userAgent___: Set the custom WebView's user-agent. - __userAgent___: Set the custom WebView's user-agent.
- __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`. - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
@ -185,9 +183,9 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
- __toolbarTopBackgroundColor__: Set the custom background color of the toolbat at the top. - __toolbarTopBackgroundColor__: Set the custom background color of the toolbat at the top.
- __hideUrlBar__: Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`. - __hideUrlBar__: Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`.
- __mediaPlaybackRequiresUserGesture__: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`. - __mediaPlaybackRequiresUserGesture__: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`.
**Android** supports these additional options: **Android** supports these additional options:
- __hideTitleBar__: Set to `true` if you want the title should be displayed. The default value is `false`. - __hideTitleBar__: Set to `true` if you want the title should be displayed. The default value is `false`.
- __closeOnCannotGoBack__: Set to `false` to not close the InAppBrowser when the user click on the back button and the WebView cannot go back to the history. The default value is `true`. - __closeOnCannotGoBack__: Set to `false` to not close the InAppBrowser when the user click on the back button and the WebView cannot go back to the history. The default value is `true`.
- __clearSessionCache__: Set to `true` to have the session cookie cache cleared before the new window is opened. - __clearSessionCache__: Set to `true` to have the session cookie cache cleared before the new window is opened.
@ -200,14 +198,14 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
- __progressBar__: Set to `false` to hide the progress bar at the bottom of the toolbar at the top. The default value is `true`. - __progressBar__: Set to `false` to hide the progress bar at the bottom of the toolbar at the top. The default value is `true`.
**iOS** supports these additional options: **iOS** supports these additional options:
- __disallowOverScroll__: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`. - __disallowOverScroll__: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
- __toolbarBottom__: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`. - __toolbarBottom__: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`.
- __toolbarBottomBackgroundColor__: Set the custom background color of the toolbat at the bottom. - __toolbarBottomBackgroundColor__: Set the custom background color of the toolbat at the bottom.
- __toolbarBottomTranslucent__: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`. - __toolbarBottomTranslucent__: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`.
- __closeButtonCaption__: Set the custom text for the close button. - __closeButtonCaption__: Set the custom text for the close button.
- __closeButtonColor__: Set the custom color for the close button. - __closeButtonColor__: Set the custom color for the close button.
- __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. - __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. - __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.
- __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`. - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`.
- __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`. - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`.
@ -218,7 +216,7 @@ Opens an `url` in a new `InAppBrowser` instance or the system browser.
- __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`. - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`.
- __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`. - __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`.
- __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`. - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`.
Example: Example:
```dart ```dart
inAppBrowser.open('https://flutter.io/', options: { inAppBrowser.open('https://flutter.io/', options: {
@ -231,7 +229,34 @@ inAppBrowser.open('https://flutter.io/', options: {
"toolbarBottomTranslucent": false, "toolbarBottomTranslucent": false,
"allowsLinkPreview": false "allowsLinkPreview": false
}); });
``` ```
#### static Future\<void\> InAppBrowser.openWithSystemBrowser
This is a static method that opens an `url` in the system browser.
This has the same behaviour of an `InAppBrowser` instance calling the `open()` method with option `openWithSystemBrowser: true`.
```dart
InAppBrowser.openWithSystemBrowser(String url);
```
#### Future\<void\> InAppBrowser.openOnLocalhost
Serve the `assetFilePath` from Flutter assets on http://localhost:`port`/. It is similar to `InAppBrowser.open()` with option `isLocalFile: true`, but it starts a server.
**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**.
```dart
inAppBrowser.openOnLocalhost(String assetFilePath, {int port = 8080, Map<String, String> headers = const {}, Map<String, dynamic> options = const {}});
```
#### Events #### Events
@ -239,7 +264,7 @@ Event fires when the `InAppBrowser` starts to load an `url`.
```dart ```dart
@override @override
void onLoadStart(String url) { void onLoadStart(String url) {
} }
``` ```
@ -247,7 +272,7 @@ Event fires when the `InAppBrowser` finishes loading an `url`.
```dart ```dart
@override @override
void onLoadStop(String url) { void onLoadStop(String url) {
} }
``` ```
@ -255,7 +280,7 @@ Event fires when the `InAppBrowser` encounters an error loading an `url`.
```dart ```dart
@override @override
void onLoadError(String url, String code, String message) { void onLoadError(String url, String code, String message) {
} }
``` ```
@ -263,7 +288,7 @@ Event fires when the `InAppBrowser` window is closed.
```dart ```dart
@override @override
void onExit() { void onExit() {
} }
``` ```
@ -305,13 +330,49 @@ Loads the given `url` with optional `headers` specified as a map from name to va
inAppBrowser.loadUrl(String url, {Map<String, String> headers = const {}}); inAppBrowser.loadUrl(String url, {Map<String, String> headers = const {}});
``` ```
#### Future\<void\> InAppBrowser.loadFile
Loads the given `assetFilePath` with optional `headers` specified as a map from name to value.
To be able to load your local files (assets, js, css, etc.), you need to add them in the `assets` section of the `pubspec.yaml` file, otherwise they cannot be found!
Example of a `pubspec.yaml` file:
```yaml
...
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/index.html
- assets/css/
- assets/images/
...
```
Example of a `main.dart` file:
```dart
...
inAppBrowser.loadFile("assets/index.html");
...
```
```dart
inAppBrowser.loadFile(String assetFilePath, {Map<String, String> headers = const {}});
```
#### Future\<void\> InAppBrowser.show #### Future\<void\> InAppBrowser.show
Displays an `InAppBrowser` window that was opened hidden. Calling this has no effect if the `InAppBrowser` was already visible. Displays an `InAppBrowser` window that was opened hidden. Calling this has no effect if the `InAppBrowser` was already visible.
```dart ```dart
inAppBrowser.show(); inAppBrowser.show();
``` ```
#### Future\<void\> InAppBrowser.hide #### Future\<void\> InAppBrowser.hide
@ -319,7 +380,7 @@ Hides the `InAppBrowser` window. Calling this has no effect if the `InAppBrowser
```dart ```dart
inAppBrowser.hide(); inAppBrowser.hide();
``` ```
#### Future\<void\> InAppBrowser.close #### Future\<void\> InAppBrowser.close
@ -327,7 +388,7 @@ Closes the `InAppBrowser` window.
```dart ```dart
inAppBrowser.close(); inAppBrowser.close();
``` ```
#### Future\<void\> InAppBrowser.reload #### Future\<void\> InAppBrowser.reload
@ -335,7 +396,7 @@ Reloads the `InAppBrowser` window.
```dart ```dart
inAppBrowser.reload(); inAppBrowser.reload();
``` ```
#### Future\<void\> InAppBrowser.goBack #### Future\<void\> InAppBrowser.goBack
@ -343,7 +404,7 @@ Goes back in the history of the `InAppBrowser` window.
```dart ```dart
inAppBrowser.goBack(); inAppBrowser.goBack();
``` ```
#### Future\<void\> InAppBrowser.goForward #### Future\<void\> InAppBrowser.goForward
@ -351,7 +412,7 @@ Goes forward in the history of the `InAppBrowser` window.
```dart ```dart
inAppBrowser.goForward(); inAppBrowser.goForward();
``` ```
#### Future\<bool\> InAppBrowser.isLoading #### Future\<bool\> InAppBrowser.isLoading
@ -359,7 +420,7 @@ Check if the Web View of the `InAppBrowser` instance is in a loading state.
```dart ```dart
inAppBrowser.isLoading(); inAppBrowser.isLoading();
``` ```
#### Future\<void\> InAppBrowser.stopLoading #### Future\<void\> InAppBrowser.stopLoading
@ -367,7 +428,7 @@ Stops the Web View of the `InAppBrowser` instance from loading.
```dart ```dart
inAppBrowser.stopLoading(); inAppBrowser.stopLoading();
``` ```
#### Future\<bool\> InAppBrowser.isHidden #### Future\<bool\> InAppBrowser.isHidden
@ -375,35 +436,35 @@ Check if the Web View of the `InAppBrowser` instance is hidden.
```dart ```dart
inAppBrowser.isHidden(); inAppBrowser.isHidden();
``` ```
#### Future\<String\> InAppBrowser.injectScriptCode #### Future\<String\> InAppBrowser.injectScriptCode
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`) Injects JavaScript code into the `InAppBrowser` window and returns the result of the evaluation.
```dart ```dart
inAppBrowser.injectScriptCode(String source); inAppBrowser.injectScriptCode(String source);
``` ```
#### Future\<void\> InAppBrowser.injectScriptFile #### Future\<void\> InAppBrowser.injectScriptFile
Injects a JavaScript file into the `InAppBrowser` window. (Only available when the target is set to `_blank` or to `_self`) Injects a JavaScript file into the `InAppBrowser` window.
```dart ```dart
inAppBrowser.injectScriptFile(String urlFile); inAppBrowser.injectScriptFile(String urlFile);
``` ```
#### Future\<void\> InAppBrowser.injectStyleCode #### Future\<void\> InAppBrowser.injectStyleCode
Injects CSS into the `InAppBrowser` window. (Only available when the target is set to `_blank` or to `_self`) Injects CSS into the `InAppBrowser` window.
```dart ```dart
inAppBrowser.injectStyleCode(String source); inAppBrowser.injectStyleCode(String source);
``` ```
#### Future\<void\> InAppBrowser.injectStyleFile #### Future\<void\> InAppBrowser.injectStyleFile
Injects a CSS file into the `InAppBrowser` window. (Only available when the target is set to `_blank` or to `_self`) Injects a CSS file into the `InAppBrowser` window.
```dart ```dart
inAppBrowser.injectStyleFile(String urlFile); inAppBrowser.injectStyleFile(String urlFile);
@ -462,13 +523,13 @@ class MyInAppBrowser extends InAppBrowser {
void onExit() { void onExit() {
print("\n\nBrowser closed!\n\n"); print("\n\nBrowser closed!\n\n");
} }
} }
MyInAppBrowser inAppBrowserFallback = new MyInAppBrowser(); MyInAppBrowser inAppBrowserFallback = new MyInAppBrowser();
class MyChromeSafariBrowser extends ChromeSafariBrowser { class MyChromeSafariBrowser extends ChromeSafariBrowser {
MyChromeSafariBrowser(browserFallback) : super(browserFallback); MyChromeSafariBrowser(browserFallback) : super(browserFallback);
@override @override
@ -579,7 +640,7 @@ Event fires when the `ChromeSafariBrowser` is opened.
```dart ```dart
@override @override
void onOpened() { void onOpened() {
} }
``` ```
@ -587,7 +648,7 @@ Event fires when the `ChromeSafariBrowser` is loaded.
```dart ```dart
@override @override
void onLoaded() { void onLoaded() {
} }
``` ```
@ -595,7 +656,7 @@ Event fires when the `ChromeSafariBrowser` is closed.
```dart ```dart
@override @override
void onClosed() { void onClosed() {
} }
``` ```

View File

@ -25,6 +25,7 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.AssetManager;
import android.os.Parcelable; import android.os.Parcelable;
import android.provider.Browser; import android.provider.Browser;
import android.net.Uri; import android.net.Uri;
@ -40,8 +41,10 @@ import android.util.Log;
import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.ChromeCustomTabsActivity; import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.ChromeCustomTabsActivity;
import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.ChromeCustomTabsOptions; import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.ChromeCustomTabsOptions;
import com.pichillilorenzo.flutter_inappbrowser.chrome_custom_tabs.CustomTabActivityHelper;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.io.StringReader; import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,7 +58,9 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.Registrar;
/** InAppBrowserFlutterPlugin */ /**
* InAppBrowserFlutterPlugin
*/
public class InAppBrowserFlutterPlugin implements MethodCallHandler { public class InAppBrowserFlutterPlugin implements MethodCallHandler {
public static Registrar registrar; public static Registrar registrar;
@ -64,9 +69,9 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
public static Map<String, WebViewActivity> webViewActivities = new HashMap<>(); public static Map<String, WebViewActivity> webViewActivities = new HashMap<>();
public static Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>(); public static Map<String, ChromeCustomTabsActivity> chromeCustomTabsActivities = new HashMap<>();
private static final String NULL = "null"; protected static final String LOG_TAG = "IABFlutterPlugin";
protected static final String LOG_TAG = "InAppBrowserFlutterP";
static final String ANDROID_ASSET_URL = "file:///android_asset/";
public InAppBrowserFlutterPlugin(Registrar r, Activity activity) { public InAppBrowserFlutterPlugin(Registrar r, Activity activity) {
registrar = r; registrar = r;
@ -74,7 +79,9 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser");
} }
/** Plugin registration. */ /**
* Plugin registration.
*/
public static void registerWith(Registrar registrar) { public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser"); final MethodChannel channel = new MethodChannel(registrar.messenger(), "com.pichillilorenzo/flutter_inappbrowser");
channel.setMethodCallHandler(new InAppBrowserFlutterPlugin(registrar, registrar.activity())); channel.setMethodCallHandler(new InAppBrowserFlutterPlugin(registrar, registrar.activity()));
@ -89,15 +96,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
switch (call.method) { switch (call.method) {
case "open": case "open":
final String url = call.argument("url").toString(); final String url_final = call.argument("url").toString();
String t = call.argument("target").toString();
if (t == null || t.equals("") || t.equals(NULL)) {
t = "_self";
}
final String target = t;
Log.d(LOG_TAG, "target = " + target);
final boolean useChromeSafariBrowser = (boolean) call.argument("useChromeSafariBrowser"); final boolean useChromeSafariBrowser = (boolean) call.argument("useChromeSafariBrowser");
@ -119,19 +118,48 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
final InAppBrowserOptions optionsFallback = new InAppBrowserOptions(); final InAppBrowserOptions optionsFallback = new InAppBrowserOptions();
optionsFallback.parse((HashMap<String, Object>) call.argument("optionsFallback")); optionsFallback.parse((HashMap<String, Object>) call.argument("optionsFallback"));
open(uuid, uuidFallback, url, options, headers,true, optionsFallback); open(uuid, uuidFallback, url_final, options, headers, true, optionsFallback, result);
} } else {
else {
String url = url_final;
final InAppBrowserOptions options = new InAppBrowserOptions(); final InAppBrowserOptions options = new InAppBrowserOptions();
options.parse((HashMap<String, Object>) call.argument("options")); options.parse((HashMap<String, Object>) call.argument("options"));
if ("_self".equals(target)) { if (options.isLocalFile) {
Log.d(LOG_TAG, "in self"); // check if the asset file exists
String key = registrar.lookupKeyForAsset(url);
AssetManager mg = registrar.activeContext().getResources().getAssets();
InputStream is = null;
boolean assetExists = false;
try {
is = mg.open(key);
assetExists = true;
} catch (IOException ex) {
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (!assetExists) {
result.error(LOG_TAG, key + " asset file cannot be found!", null);
return;
}
url = ANDROID_ASSET_URL + key;
}
// SYSTEM
if (options.openWithSystemBrowser) {
Log.d(LOG_TAG, "in system");
openExternal(url, result);
}
else {
//Load the dialer //Load the dialer
if (url.startsWith(WebView.SCHEME_TEL)) if (url.startsWith(WebView.SCHEME_TEL)) {
{
try { try {
Log.d(LOG_TAG, "loading in dialer"); Log.d(LOG_TAG, "loading in dialer");
Intent intent = new Intent(Intent.ACTION_DIAL); Intent intent = new Intent(Intent.ACTION_DIAL);
@ -144,31 +172,21 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
// load in InAppBrowserFlutterPlugin // load in InAppBrowserFlutterPlugin
else { else {
Log.d(LOG_TAG, "loading in InAppBrowserFlutterPlugin"); Log.d(LOG_TAG, "loading in InAppBrowserFlutterPlugin");
open(uuid, null, url, options, headers, false, null); open(uuid, null, url, options, headers, false, null, result);
} }
} }
// SYSTEM
else if ("_system".equals(target)) {
Log.d(LOG_TAG, "in system");
openExternal(url);
}
// BLANK - or anything else
else {
Log.d(LOG_TAG, "in blank");
open(uuid, null, url, options, headers, false, null);
}
} }
result.success(true);
} }
}); });
break; break;
case "loadUrl": case "loadUrl":
loadUrl(uuid, call.argument("url").toString(), (Map<String, String>) call.argument("headers"), result); loadUrl(uuid, call.argument("url").toString(), (Map<String, String>) call.argument("headers"), result);
break; break;
case "loadFile":
loadFile(uuid, call.argument("url").toString(), (Map<String, String>) call.argument("headers"), result);
break;
case "close": case "close":
close(uuid); close(uuid, result);
result.success(true);
break; break;
case "injectScriptCode": case "injectScriptCode":
source = call.argument("source").toString(); source = call.argument("source").toString();
@ -237,18 +255,19 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
/** /**
* Inject an object (script or style) into the InAppBrowserFlutterPlugin WebView. * Inject an object (script or style) into the InAppBrowserFlutterPlugin WebView.
* * <p>
* This is a helper method for the inject{Script|Style}{Code|File} API calls, which * This is a helper method for the inject{Script|Style}{Code|File} API calls, which
* provides a consistent method for injecting JavaScript code into the document. * provides a consistent method for injecting JavaScript code into the document.
* * <p>
* If a wrapper string is supplied, then the source string will be JSON-encoded (adding * 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 * quotes) and wrapped using string formatting. (The wrapper string should have a single
* '%s' marker) * '%s' marker)
*
* @param uuid * @param uuid
* @param source The source object (filename or script/style text) to inject into * @param source The source object (filename or script/style text) to inject into
* the document. * the document.
* @param jsWrapper A JavaScript string to wrap the source string in, so that the object * @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 * @param result
*/ */
private void injectDeferredObject(String uuid, String source, String jsWrapper, final Result result) { private void injectDeferredObject(String uuid, String source, String jsWrapper, final Result result) {
@ -259,7 +278,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
org.json.JSONArray jsonEsc = new org.json.JSONArray(); org.json.JSONArray jsonEsc = new org.json.JSONArray();
jsonEsc.put(source); jsonEsc.put(source);
String jsonRepr = jsonEsc.toString(); String jsonRepr = jsonEsc.toString();
String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1); String jsonSourceString = jsonRepr.substring(1, jsonRepr.length() - 1);
scriptToInject = String.format(jsWrapper, jsonSourceString); scriptToInject = String.format(jsWrapper, jsonSourceString);
} else { } else {
scriptToInject = source; scriptToInject = source;
@ -295,8 +314,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
msg = reader2.nextString(); msg = reader2.nextString();
result.success(msg); result.success(msg);
} } else {
else {
result.success(""); result.success("");
} }
@ -332,9 +350,10 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
* Display a new browser with the specified URL. * Display a new browser with the specified URL.
* *
* @param url the url to load. * @param url the url to load.
* @param result
* @return "" if ok, or error message. * @return "" if ok, or error message.
*/ */
public void openExternal(String url) { public void openExternal(String url, Result result) {
try { try {
Intent intent; Intent intent;
intent = new Intent(Intent.ACTION_VIEW); intent = new Intent(Intent.ACTION_VIEW);
@ -349,9 +368,11 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName()); intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
// CB-10795: Avoid circular loops by preventing it from opening in the current app // CB-10795: Avoid circular loops by preventing it from opening in the current app
this.openExternalExcludeCurrentApp(intent); this.openExternalExcludeCurrentApp(intent);
result.success(true);
// not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it // not catching FileUriExposedException explicitly because buildtools<24 doesn't know about it
} catch (java.lang.RuntimeException e) { } catch (java.lang.RuntimeException e) {
Log.d(LOG_TAG, "InAppBrowserFlutterPlugin: Error loading url "+url+":"+ e.toString()); Log.d(LOG_TAG, url + " cannot be opened: " + e.toString());
result.error(LOG_TAG, url + " cannot be opened!", null);
} }
} }
@ -367,11 +388,10 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
ArrayList<Intent> targetIntents = new ArrayList<Intent>(); ArrayList<Intent> targetIntents = new ArrayList<Intent>();
for (ResolveInfo ri : activities) { for (ResolveInfo ri : activities) {
if (!currentPackage.equals(ri.activityInfo.packageName)) { if (!currentPackage.equals(ri.activityInfo.packageName)) {
Intent targetIntent = (Intent)intent.clone(); Intent targetIntent = (Intent) intent.clone();
targetIntent.setPackage(ri.activityInfo.packageName); targetIntent.setPackage(ri.activityInfo.packageName);
targetIntents.add(targetIntent); targetIntents.add(targetIntent);
} } else {
else {
hasCurrentPackage = true; hasCurrentPackage = true;
} }
} }
@ -386,35 +406,57 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
} }
// Otherwise, show a custom chooser without the current app listed // Otherwise, show a custom chooser without the current app listed
else if (targetIntents.size() > 0) { else if (targetIntents.size() > 0) {
Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size()-1), null); Intent chooser = Intent.createChooser(targetIntents.remove(targetIntents.size() - 1), null);
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[] {})); chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{}));
activity.startActivity(chooser); activity.startActivity(chooser);
} }
} }
public static void open(String uuid, String uuidFallback, final String url, Options options, Map<String, String> headers, boolean useChromeSafariBrowser, InAppBrowserOptions optionsFallback) { public void open(String uuid, String uuidFallback, String url, Options options, Map<String, String> headers, boolean useChromeSafariBrowser, InAppBrowserOptions optionsFallback, Result result) {
Intent intent = new Intent(registrar.activity(), (useChromeSafariBrowser) ? ChromeCustomTabsActivity.class : WebViewActivity.class);
Intent intent = null;
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString("uuid", uuid);
extras.putString("url", url); extras.putString("url", url);
extras.putString("uuid", uuid);
extras.putSerializable("options", options.getHashMap()); extras.putSerializable("options", options.getHashMap());
extras.putSerializable("headers", (Serializable) headers); extras.putSerializable("headers", (Serializable) headers);
if (useChromeSafariBrowser) {
extras.putString("uuidFallback", uuidFallback); if (useChromeSafariBrowser && CustomTabActivityHelper.isAvailable(activity)) {
intent = new Intent(activity, ChromeCustomTabsActivity.class);
}
// check for webview fallback
else if (useChromeSafariBrowser && !CustomTabActivityHelper.isAvailable(activity) && !uuidFallback.isEmpty()) {
Log.d(LOG_TAG, "WebView fallback declared.");
// overwrite with extras fallback parameters
extras.putString("uuid", uuidFallback);
if (optionsFallback != null) if (optionsFallback != null)
extras.putSerializable("optionsFallback", optionsFallback.getHashMap()); extras.putSerializable("options", optionsFallback.getHashMap());
else else
extras.putSerializable("optionsFallback", (new InAppBrowserOptions()).getHashMap()); extras.putSerializable("options", (new InAppBrowserOptions()).getHashMap());
extras.putSerializable("headers", (Serializable) headers);
intent = new Intent(activity, WebViewActivity.class);
}
// native webview
else if (!useChromeSafariBrowser) {
intent = new Intent(activity, WebViewActivity.class);
}
else {
Log.d(LOG_TAG, "No WebView fallback declared.");
} }
intent.putExtras(extras); if (intent != null) {
intent.putExtras(extras);
activity.startActivity(intent);
result.success(true);
return;
}
registrar.activity().startActivity(intent); result.error(LOG_TAG, "No WebView fallback declared.", null);
} }
public void loadUrl(String uuid, String url, Map<String, String> headers, Result result) { public void loadUrl(String uuid, String url, Map<String, String> headers, Result result) {
WebViewActivity webViewActivity = webViewActivities.get(uuid); WebViewActivity webViewActivity = webViewActivities.get(uuid);
if (webViewActivity != null) { if (webViewActivity != null) {
if (headers != null) if (headers != null)
webViewActivity.loadUrl(url, headers, result); webViewActivity.loadUrl(url, headers, result);
@ -423,6 +465,16 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
} }
} }
public void loadFile(String uuid, String url, Map<String, String> headers, Result result) {
WebViewActivity webViewActivity = webViewActivities.get(uuid);
if (webViewActivity != null) {
if (headers != null)
webViewActivity.loadFile(url, headers, result);
else
webViewActivity.loadFile(url, result);
}
}
public void show(String uuid) { public void show(String uuid) {
WebViewActivity webViewActivity = webViewActivities.get(uuid); WebViewActivity webViewActivity = webViewActivities.get(uuid);
if (webViewActivity != null) if (webViewActivity != null)
@ -488,7 +540,7 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
} }
public static void close(final String uuid) { public static void close(final String uuid, final Result result) {
final WebViewActivity webViewActivity = webViewActivities.get(uuid); final WebViewActivity webViewActivity = webViewActivities.get(uuid);
if (webViewActivity != null) { if (webViewActivity != null) {
registrar.activity().runOnUiThread(new Runnable() { registrar.activity().runOnUiThread(new Runnable() {
@ -501,8 +553,12 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
// The JS protects against multiple calls, so this should happen only when // The JS protects against multiple calls, so this should happen only when
// close() is called by other native code. // close() is called by other native code.
if (webViewActivity == null) if (webViewActivity == null) {
if (result != null) {
result.success(true);
}
return; return;
}
webViewActivity.webView.setWebViewClient(new WebViewClient() { webViewActivity.webView.setWebViewClient(new WebViewClient() {
// NB: wait for about:blank before dismissing // NB: wait for about:blank before dismissing
@ -514,8 +570,14 @@ public class InAppBrowserFlutterPlugin implements MethodCallHandler {
// other than your app's UI thread, it can cause unexpected results." // other than your app's UI thread, it can cause unexpected results."
// http://developer.android.com/guide/webapps/migrating.html#Threads // http://developer.android.com/guide/webapps/migrating.html#Threads
webViewActivity.webView.loadUrl("about:blank"); webViewActivity.webView.loadUrl("about:blank");
if (result != null) {
result.success(true);
}
} }
}); });
} }
else if (result != null) {
result.success(true);
}
} }
} }

View File

@ -2,29 +2,31 @@ package com.pichillilorenzo.flutter_inappbrowser;
public class InAppBrowserOptions extends Options { public class InAppBrowserOptions extends Options {
final static String LOG_TAG = "InAppBrowserOptions"; static final String LOG_TAG = "InAppBrowserOptions";
public boolean useShouldOverrideUrlLoading = false; public boolean useShouldOverrideUrlLoading = false;
public boolean useOnLoadResource = false; public boolean useOnLoadResource = false;
public boolean clearCache = false; public boolean openWithSystemBrowser = false;
public String userAgent = ""; public boolean clearCache = false;
public boolean javaScriptEnabled = true; public String userAgent = "";
public boolean javaScriptCanOpenWindowsAutomatically = false; public boolean javaScriptEnabled = true;
public boolean hidden = false; public boolean javaScriptCanOpenWindowsAutomatically = false;
public boolean toolbarTop = true; public boolean hidden = false;
public String toolbarTopBackgroundColor = ""; public boolean toolbarTop = true;
public String toolbarTopFixedTitle = ""; public String toolbarTopBackgroundColor = "";
public boolean hideUrlBar = false; public String toolbarTopFixedTitle = "";
public boolean mediaPlaybackRequiresUserGesture = true; public boolean hideUrlBar = false;
public boolean mediaPlaybackRequiresUserGesture = true;
public boolean isLocalFile = false;
public boolean hideTitleBar = false; public boolean hideTitleBar = false;
public boolean closeOnCannotGoBack = true; public boolean closeOnCannotGoBack = true;
public boolean clearSessionCache = false; public boolean clearSessionCache = false;
public boolean builtInZoomControls = false; public boolean builtInZoomControls = false;
public boolean supportZoom = true; public boolean supportZoom = true;
public boolean databaseEnabled = false; public boolean databaseEnabled = false;
public boolean domStorageEnabled = false; public boolean domStorageEnabled = false;
public boolean useWideViewPort = true; public boolean useWideViewPort = true;
public boolean safeBrowsingEnabled = true; public boolean safeBrowsingEnabled = true;
public boolean progressBar = true; public boolean progressBar = true;
} }

View File

@ -16,112 +16,111 @@ import java.util.Map;
public class InAppBrowserWebChromeClient extends WebChromeClient { public class InAppBrowserWebChromeClient extends WebChromeClient {
protected static final String LOG_TAG = "IABWebChromeClient"; protected static final String LOG_TAG = "IABWebChromeClient";
private WebViewActivity activity; private WebViewActivity activity;
private ValueCallback<Uri[]> mUploadMessageArray; private ValueCallback<Uri[]> mUploadMessageArray;
private ValueCallback<Uri> mUploadMessage; private ValueCallback<Uri> mUploadMessage;
private final static int FILECHOOSER_RESULTCODE=1; private final static int FILECHOOSER_RESULTCODE = 1;
public InAppBrowserWebChromeClient(WebViewActivity activity) { public InAppBrowserWebChromeClient(WebViewActivity activity) {
super(); super();
this.activity = activity; this.activity = activity;
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("sourceURL", consoleMessage.sourceId());
obj.put("lineNumber", consoleMessage.lineNumber());
obj.put("message", consoleMessage.message());
obj.put("messageLevel", consoleMessage.messageLevel().toString());
InAppBrowserFlutterPlugin.channel.invokeMethod("onConsoleMessage", obj);
return true;
}
@Override
public void onProgressChanged(WebView view, int progress) {
if (activity.progressBar != null) {
activity.progressBar.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
activity.progressBar.setProgress(progress, true);
} else {
activity.progressBar.setProgress(progress);
}
if (progress == 100) {
activity.progressBar.setVisibility(View.GONE);
}
} }
super.onProgressChanged(view, progress);
}
@Override @Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) { public void onReceivedTitle(WebView view, String title) {
Map<String, Object> obj = new HashMap<>(); super.onReceivedTitle(view, title);
obj.put("uuid", activity.uuid); if (activity.actionBar != null && activity.options.toolbarTopFixedTitle.isEmpty())
obj.put("sourceURL", consoleMessage.sourceId()); activity.actionBar.setTitle(title);
obj.put("lineNumber", consoleMessage.lineNumber()); }
obj.put("message", consoleMessage.message());
obj.put("messageLevel", consoleMessage.messageLevel().toString()); @Override
InAppBrowserFlutterPlugin.channel.invokeMethod("onConsoleMessage", obj); public void onReceivedIcon(WebView view, Bitmap icon) {
return true; super.onReceivedIcon(view, icon);
}
//The undocumented magic method override
//Eclipse will swear at you if you try to put @Override here
// For Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
// For Android 3.0+
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
activity.startActivityForResult(
Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
//For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
}
//For Android 5.0+
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
if (mUploadMessageArray != null) {
mUploadMessageArray.onReceiveValue(null);
} }
mUploadMessageArray = filePathCallback;
@Override Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
public void onProgressChanged(WebView view, int progress) { contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
if (activity.progressBar != null) { contentSelectionIntent.setType("*/*");
activity.progressBar.setVisibility(View.VISIBLE); Intent[] intentArray;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intentArray = new Intent[0];
activity.progressBar.setProgress(progress, true);
}
else {
activity.progressBar.setProgress(progress);
}
if (progress == 100) {
activity.progressBar.setVisibility(View.GONE);
}
}
super.onProgressChanged(view, progress);
}
@Override Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
public void onReceivedTitle(WebView view, String title) { chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
super.onReceivedTitle(view, title); chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
if (activity.actionBar != null && activity.options.toolbarTopFixedTitle.isEmpty()) chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
activity.actionBar.setTitle(title); activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
} return true;
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
//The undocumented magic method override
//Eclipse will swear at you if you try to put @Override here
// For Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult(Intent.createChooser(i,"File Chooser"), FILECHOOSER_RESULTCODE);
}
// For Android 3.0+
public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
activity.startActivityForResult(
Intent.createChooser(i, "File Browser"),
FILECHOOSER_RESULTCODE);
}
//For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture){
mUploadMessage = uploadMsg;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
activity.startActivityForResult( Intent.createChooser( i, "File Chooser" ), FILECHOOSER_RESULTCODE );
}
//For Android 5.0+
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams){
if(mUploadMessageArray != null){
mUploadMessageArray.onReceiveValue(null);
}
mUploadMessageArray = filePathCallback;
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*");
Intent[] intentArray;
intentArray = new Intent[0];
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
return true;
}
} }

View File

@ -23,337 +23,341 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.flutter.plugin.common.MethodChannel;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
public class InAppBrowserWebViewClient extends WebViewClient { public class InAppBrowserWebViewClient extends WebViewClient {
protected static final String LOG_TAG = "IABWebViewClient"; protected static final String LOG_TAG = "IABWebViewClient";
private WebViewActivity activity; private WebViewActivity activity;
Map<Integer, String> statusCodeMapping = new HashMap<Integer, String>(); Map<Integer, String> statusCodeMapping = new HashMap<Integer, String>();
long startPageTime = 0; long startPageTime = 0;
public InAppBrowserWebViewClient(WebViewActivity activity) { public InAppBrowserWebViewClient(WebViewActivity activity) {
super(); super();
this.activity = activity; this.activity = activity;
statusCodeMapping.put(100, "Continue"); statusCodeMapping.put(100, "Continue");
statusCodeMapping.put(101, "Switching Protocols"); statusCodeMapping.put(101, "Switching Protocols");
statusCodeMapping.put(200, "OK"); statusCodeMapping.put(200, "OK");
statusCodeMapping.put(201, "Created"); statusCodeMapping.put(201, "Created");
statusCodeMapping.put(202, "Accepted"); statusCodeMapping.put(202, "Accepted");
statusCodeMapping.put(203, "Non-Authoritative Information"); statusCodeMapping.put(203, "Non-Authoritative Information");
statusCodeMapping.put(204, "No Content"); statusCodeMapping.put(204, "No Content");
statusCodeMapping.put(205, "Reset Content"); statusCodeMapping.put(205, "Reset Content");
statusCodeMapping.put(206, "Partial Content"); statusCodeMapping.put(206, "Partial Content");
statusCodeMapping.put(300, "Multiple Choices"); statusCodeMapping.put(300, "Multiple Choices");
statusCodeMapping.put(301, "Moved Permanently"); statusCodeMapping.put(301, "Moved Permanently");
statusCodeMapping.put(302, "Found"); statusCodeMapping.put(302, "Found");
statusCodeMapping.put(303, "See Other"); statusCodeMapping.put(303, "See Other");
statusCodeMapping.put(304, "Not Modified"); statusCodeMapping.put(304, "Not Modified");
statusCodeMapping.put(307, "Temporary Redirect"); statusCodeMapping.put(307, "Temporary Redirect");
statusCodeMapping.put(308, "Permanent Redirect"); statusCodeMapping.put(308, "Permanent Redirect");
statusCodeMapping.put(400, "Bad Request"); statusCodeMapping.put(400, "Bad Request");
statusCodeMapping.put(401, "Unauthorized"); statusCodeMapping.put(401, "Unauthorized");
statusCodeMapping.put(403, "Forbidden"); statusCodeMapping.put(403, "Forbidden");
statusCodeMapping.put(404, "Not Found"); statusCodeMapping.put(404, "Not Found");
statusCodeMapping.put(405, "Method Not Allowed"); statusCodeMapping.put(405, "Method Not Allowed");
statusCodeMapping.put(406, "Not Acceptable"); statusCodeMapping.put(406, "Not Acceptable");
statusCodeMapping.put(407, "Proxy Authentication Required"); statusCodeMapping.put(407, "Proxy Authentication Required");
statusCodeMapping.put(408, "Request Timeout"); statusCodeMapping.put(408, "Request Timeout");
statusCodeMapping.put(409, "Conflict"); statusCodeMapping.put(409, "Conflict");
statusCodeMapping.put(410, "Gone"); statusCodeMapping.put(410, "Gone");
statusCodeMapping.put(411, "Length Required"); statusCodeMapping.put(411, "Length Required");
statusCodeMapping.put(412, "Precondition Failed"); statusCodeMapping.put(412, "Precondition Failed");
statusCodeMapping.put(413, "Payload Too Large"); statusCodeMapping.put(413, "Payload Too Large");
statusCodeMapping.put(414, "URI Too Long"); statusCodeMapping.put(414, "URI Too Long");
statusCodeMapping.put(415, "Unsupported Media Type"); statusCodeMapping.put(415, "Unsupported Media Type");
statusCodeMapping.put(416, "Range Not Satisfiable"); statusCodeMapping.put(416, "Range Not Satisfiable");
statusCodeMapping.put(417, "Expectation Failed"); statusCodeMapping.put(417, "Expectation Failed");
statusCodeMapping.put(418, "I'm a teapot"); statusCodeMapping.put(418, "I'm a teapot");
statusCodeMapping.put(422, "Unprocessable Entity"); statusCodeMapping.put(422, "Unprocessable Entity");
statusCodeMapping.put(425, "Too Early"); statusCodeMapping.put(425, "Too Early");
statusCodeMapping.put(426, "Upgrade Required"); statusCodeMapping.put(426, "Upgrade Required");
statusCodeMapping.put(428, "Precondition Required"); statusCodeMapping.put(428, "Precondition Required");
statusCodeMapping.put(429, "Too Many Requests"); statusCodeMapping.put(429, "Too Many Requests");
statusCodeMapping.put(431, "Request Header Fields Too Large"); statusCodeMapping.put(431, "Request Header Fields Too Large");
statusCodeMapping.put(451, "Unavailable For Legal Reasons"); statusCodeMapping.put(451, "Unavailable For Legal Reasons");
statusCodeMapping.put(500, "Internal Server Error"); statusCodeMapping.put(500, "Internal Server Error");
statusCodeMapping.put(501, "Not Implemented"); statusCodeMapping.put(501, "Not Implemented");
statusCodeMapping.put(502, "Bad Gateway"); statusCodeMapping.put(502, "Bad Gateway");
statusCodeMapping.put(503, "Service Unavailable"); statusCodeMapping.put(503, "Service Unavailable");
statusCodeMapping.put(504, "Gateway Timeout"); statusCodeMapping.put(504, "Gateway Timeout");
statusCodeMapping.put(505, "HTTP Version Not Supported"); statusCodeMapping.put(505, "HTTP Version Not Supported");
statusCodeMapping.put(511, "Network Authentication Required"); statusCodeMapping.put(511, "Network Authentication Required");
}
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
if (activity.options.useShouldOverrideUrlLoading) {
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", url);
InAppBrowserFlutterPlugin.channel.invokeMethod("shouldOverrideUrlLoading", obj);
return true;
} }
@Override if (url.startsWith(WebView.SCHEME_TEL)) {
public boolean shouldOverrideUrlLoading(WebView webView, String url) { try {
Intent intent = new Intent(Intent.ACTION_DIAL);
if (activity.options.useShouldOverrideUrlLoading) { intent.setData(Uri.parse(url));
Map<String, Object> obj = new HashMap<>(); activity.startActivity(intent);
obj.put("uuid", activity.uuid); return true;
obj.put("url", url); } catch (android.content.ActivityNotFoundException e) {
InAppBrowserFlutterPlugin.channel.invokeMethod("shouldOverrideUrlLoading", obj); Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
return true; }
} } else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) {
try {
if (url.startsWith(WebView.SCHEME_TEL)) { Intent intent = new Intent(Intent.ACTION_VIEW);
try { intent.setData(Uri.parse(url));
Intent intent = new Intent(Intent.ACTION_DIAL); activity.startActivity(intent);
intent.setData(Uri.parse(url)); return true;
activity.startActivity(intent); } catch (android.content.ActivityNotFoundException e) {
return true; Log.e(LOG_TAG, "Error with " + url + ": " + e.toString());
} catch (android.content.ActivityNotFoundException e) { }
Log.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
}
}
else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:") || url.startsWith("intent:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
activity.startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error with " + url + ": " + e.toString());
}
}
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
String address;
int parmIndex = url.indexOf('?');
if (parmIndex == -1) {
address = url.substring(4);
} else {
address = url.substring(4, parmIndex);
// If body, then set sms body
Uri uri = Uri.parse(url);
String query = uri.getQuery();
if (query != null) {
if (query.startsWith("body=")) {
intent.putExtra("sms_body", query.substring(5));
}
}
}
intent.setData(Uri.parse("sms:" + address));
intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
activity.startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
}
}
return super.shouldOverrideUrlLoading(webView, url);
} }
// If sms:5551212?body=This is the message
else if (url.startsWith("sms:")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
// Get address
/* String address;
* onPageStarted fires the LOAD_START_EVENT int parmIndex = url.indexOf('?');
* if (parmIndex == -1) {
* @param view address = url.substring(4);
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
startPageTime = System.currentTimeMillis();
activity.isLoading = true;
if (activity.searchView != null && !url.equals(activity.searchView.getQuery().toString())) {
activity.searchView.setQuery(url, false);
}
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", url);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStart", obj);
}
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
activity.isLoading = false;
// CB-10395 InAppBrowserFlutterPlugin's WebView not storing cookies reliable to local device storage
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
} else { } else {
CookieSyncManager.getInstance().sync(); address = url.substring(4, parmIndex);
}
// https://issues.apache.org/jira/browse/CB-11248 // If body, then set sms body
view.clearFocus(); Uri uri = Uri.parse(url);
view.requestFocus(); String query = uri.getQuery();
if (query != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (query.startsWith("body=")) {
view.evaluateJavascript(WebViewActivity.consoleLogJS, null); intent.putExtra("sms_body", query.substring(5));
view.evaluateJavascript(JavaScriptBridgeInterface.flutterInAppBroserJSClass, null);
}
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", url);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStop", obj);
}
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
activity.isLoading = false;
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", failingUrl);
obj.put("code", errorCode);
obj.put("message", description);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadError", obj);
}
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", error.getUrl());
obj.put("code", error.getPrimaryError());
String message;
switch (error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
message = "The date of the certificate is invalid";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired";
break;
case SslError.SSL_IDMISMATCH:
message = "Hostname mismatch";
break;
default:
case SslError.SSL_INVALID:
message = "A generic error occurred";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid";
break;
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted";
break;
}
obj.put("message", "SslError: " + message);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadError", obj);
handler.cancel();
}
/**
* On received http auth request.
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request){
if (!request.getMethod().toLowerCase().equals("get") || !activity.options.useOnLoadResource) {
return null;
}
final String url = request.getUrl().toString();
try {
Request mRequest = new Request.Builder().url(url).build();
long startResourceTime = System.currentTimeMillis();
Response response = activity.httpClient.newCall(mRequest).execute();
long startTime = startResourceTime - startPageTime;
long duration = System.currentTimeMillis() - startResourceTime;
if (response.cacheResponse() != null) {
duration = 0;
} }
}
String reasonPhrase = response.message();
if (reasonPhrase.equals("")) {
reasonPhrase = statusCodeMapping.get(response.code());
}
reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase;
Map<String, String> headersResponse = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val: entry.getValue()) {
value.append( (value.toString().isEmpty()) ? val : "; " + val );
}
headersResponse.put(entry.getKey().toLowerCase(), value.toString());
}
Map<String, String> headersRequest = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val: entry.getValue()) {
value.append( (value.toString().isEmpty()) ? val : "; " + val );
}
headersRequest.put(entry.getKey().toLowerCase(), value.toString());
}
Map<String, Object> obj = new HashMap<>();
Map<String, Object> res = new HashMap<>();
Map<String, Object> req = new HashMap<>();
obj.put("uuid", activity.uuid);
byte[] dataBytes = response.body().bytes();
InputStream dataStream = new ByteArrayInputStream(dataBytes);
res.put("url", url);
res.put("statusCode", response.code());
res.put("headers", headersResponse);
res.put("startTime", startTime);
res.put("duration", duration);
res.put("data", dataBytes);
req.put("url", url);
req.put("headers", headersRequest);
req.put("method", mRequest.method());
obj.put("response", res);
obj.put("request", req);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadResource", obj);
return new WebResourceResponse(
response.header("content-type", "text/plain").split(";")[0].trim(),
response.header("content-encoding"),
response.code(),
reasonPhrase,
headersResponse,
dataStream
);
} catch (IOException e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
} }
intent.setData(Uri.parse("sms:" + address));
return null; intent.putExtra("address", address);
intent.setType("vnd.android-dir/mms-sms");
activity.startActivity(intent);
return true;
} catch (android.content.ActivityNotFoundException e) {
Log.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
}
} }
return super.shouldOverrideUrlLoading(webView, url);
}
/*
* onPageStarted fires the LOAD_START_EVENT
*
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
startPageTime = System.currentTimeMillis();
activity.isLoading = true;
if (activity.searchView != null && !url.equals(activity.searchView.getQuery().toString())) {
activity.searchView.setQuery(url, false);
}
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", url);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStart", obj);
}
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
activity.isLoading = false;
// CB-10395 InAppBrowserFlutterPlugin's WebView not storing cookies reliable to local device storage
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().flush();
} else {
CookieSyncManager.getInstance().sync();
}
// https://issues.apache.org/jira/browse/CB-11248
view.clearFocus();
view.requestFocus();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
view.evaluateJavascript(WebViewActivity.consoleLogJS, null);
view.evaluateJavascript(JavaScriptBridgeInterface.flutterInAppBroserJSClass, null);
}
else {
view.loadUrl("javascript:"+WebViewActivity.consoleLogJS);
view.loadUrl("javascript:"+JavaScriptBridgeInterface.flutterInAppBroserJSClass);
}
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", url);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadStop", obj);
}
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
activity.isLoading = false;
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", failingUrl);
obj.put("code", errorCode);
obj.put("message", description);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadError", obj);
}
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", activity.uuid);
obj.put("url", error.getUrl());
obj.put("code", error.getPrimaryError());
String message;
switch (error.getPrimaryError()) {
case SslError.SSL_DATE_INVALID:
message = "The date of the certificate is invalid";
break;
case SslError.SSL_EXPIRED:
message = "The certificate has expired";
break;
case SslError.SSL_IDMISMATCH:
message = "Hostname mismatch";
break;
default:
case SslError.SSL_INVALID:
message = "A generic error occurred";
break;
case SslError.SSL_NOTYETVALID:
message = "The certificate is not yet valid";
break;
case SslError.SSL_UNTRUSTED:
message = "The certificate authority is not trusted";
break;
}
obj.put("message", "SslError: " + message);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadError", obj);
handler.cancel();
}
/**
* On received http auth request.
*/
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
// By default handle 401 like we'd normally do!
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (!request.getMethod().toLowerCase().equals("get") || !activity.options.useOnLoadResource) {
return null;
}
final String url = request.getUrl().toString();
try {
Request mRequest = new Request.Builder().url(url).build();
long startResourceTime = System.currentTimeMillis();
Response response = activity.httpClient.newCall(mRequest).execute();
long startTime = startResourceTime - startPageTime;
startTime = (startTime < 0) ? 0 : startTime;
long duration = System.currentTimeMillis() - startResourceTime;
if (response.cacheResponse() != null) {
duration = 0;
}
String reasonPhrase = response.message();
if (reasonPhrase.equals("")) {
reasonPhrase = statusCodeMapping.get(response.code());
}
reasonPhrase = (reasonPhrase.equals("") || reasonPhrase == null) ? "OK" : reasonPhrase;
Map<String, String> headersResponse = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val : entry.getValue()) {
value.append((value.toString().isEmpty()) ? val : "; " + val);
}
headersResponse.put(entry.getKey().toLowerCase(), value.toString());
}
Map<String, String> headersRequest = new HashMap<String, String>();
for (Map.Entry<String, List<String>> entry : mRequest.headers().toMultimap().entrySet()) {
StringBuilder value = new StringBuilder();
for (String val : entry.getValue()) {
value.append((value.toString().isEmpty()) ? val : "; " + val);
}
headersRequest.put(entry.getKey().toLowerCase(), value.toString());
}
Map<String, Object> obj = new HashMap<>();
Map<String, Object> res = new HashMap<>();
Map<String, Object> req = new HashMap<>();
obj.put("uuid", activity.uuid);
byte[] dataBytes = response.body().bytes();
InputStream dataStream = new ByteArrayInputStream(dataBytes);
res.put("url", url);
res.put("statusCode", response.code());
res.put("headers", headersResponse);
res.put("startTime", startTime);
res.put("duration", duration);
res.put("data", dataBytes);
req.put("url", url);
req.put("headers", headersRequest);
req.put("method", mRequest.method());
obj.put("response", res);
obj.put("request", req);
InAppBrowserFlutterPlugin.channel.invokeMethod("onLoadResource", obj);
return new WebResourceResponse(
response.header("content-type", "text/plain").split(";")[0].trim(),
response.header("content-encoding"),
response.code(),
reasonPhrase,
headersResponse,
dataStream
);
} catch (IOException e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.getMessage());
}
return null;
}
} }

View File

@ -10,9 +10,9 @@ public class JavaScriptBridgeInterface {
static final String name = "flutter_inappbrowser"; static final String name = "flutter_inappbrowser";
WebViewActivity activity; WebViewActivity activity;
static final String flutterInAppBroserJSClass = "window." + name + ".callHandler = function(handlerName, ...args) {\n" + static final String flutterInAppBroserJSClass = "window." + name + ".callHandler = function(handlerName, ...args) {" +
"window." + name + "._callHandler(handlerName, JSON.stringify(args));\n" + "window." + name + "._callHandler(handlerName, JSON.stringify(args));" +
"}\n"; "}";
JavaScriptBridgeInterface(WebViewActivity a) { JavaScriptBridgeInterface(WebViewActivity a) {
activity = a; activity = a;

View File

@ -9,32 +9,33 @@ import java.util.HashMap;
public class Options { public class Options {
final static String LOG_TAG = ""; static String LOG_TAG = "";
public void parse(HashMap<String, Object> options) { public Options parse(HashMap<String, Object> options) {
Iterator it = options.entrySet().iterator(); Iterator it = options.entrySet().iterator();
while (it.hasNext()) { while (it.hasNext()) {
Map.Entry<String, Object> pair = (Map.Entry<String, Object>)it.next(); Map.Entry<String, Object> pair = (Map.Entry<String, Object>) it.next();
try { try {
this.getClass().getDeclaredField(pair.getKey()).set(this, pair.getValue()); this.getClass().getDeclaredField(pair.getKey()).set(this, pair.getValue());
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} }
}
} }
return this;
}
public HashMap<String, Object> getHashMap() { public HashMap<String, Object> getHashMap() {
HashMap<String, Object> options = new HashMap<>(); HashMap<String, Object> options = new HashMap<>();
for (Field f: this.getClass().getDeclaredFields()) { for (Field f : this.getClass().getDeclaredFields()) {
try { try {
options.put(f.getName(), f.get(this)); options.put(f.getName(), f.get(this));
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
Log.d(LOG_TAG, e.getMessage()); Log.d(LOG_TAG, e.getMessage());
} }
}
return options;
} }
return options;
}
} }

View File

@ -28,85 +28,85 @@ import okhttp3.OkHttpClient;
public class WebViewActivity extends AppCompatActivity { public class WebViewActivity extends AppCompatActivity {
String uuid; String uuid;
WebView webView; WebView webView;
ActionBar actionBar; ActionBar actionBar;
InAppBrowserWebViewClient inAppBrowserWebViewClient; InAppBrowserWebViewClient inAppBrowserWebViewClient;
InAppBrowserWebChromeClient inAppBrowserWebChromeClient; InAppBrowserWebChromeClient inAppBrowserWebChromeClient;
SearchView searchView; SearchView searchView;
InAppBrowserOptions options; InAppBrowserOptions options;
Map<String, String> headers; Map<String, String> headers;
ProgressBar progressBar; ProgressBar progressBar;
public boolean isLoading = false; public boolean isLoading = false;
public boolean isHidden = false; public boolean isHidden = false;
OkHttpClient httpClient; OkHttpClient httpClient;
static final String consoleLogJS = "(function() {\n"+ static final String consoleLogJS = "(function() {" +
" var oldLogs = {\n"+ " var oldLogs = {" +
" 'log': console.log,\n"+ " 'log': console.log," +
" 'debug': console.debug,\n"+ " 'debug': console.debug," +
" 'error': console.error,\n"+ " 'error': console.error," +
" 'info': console.info,\n"+ " 'info': console.info," +
" 'warn': console.warn\n"+ " 'warn': console.warn" +
" };\n"+ " };" +
" for (var k in oldLogs) {\n"+ " for (var k in oldLogs) {" +
" (function(oldLog) {\n"+ " (function(oldLog) {" +
" console[oldLog] = function() {\n"+ " console[oldLog] = function() {" +
" var message = ''\n"+ " var message = '';" +
" for (var i in arguments) {\n"+ " for (var i in arguments) {" +
" if (message == '') {\n"+ " if (message == '') {" +
" message += arguments[i];\n"+ " message += arguments[i];" +
" }\n"+ " }" +
" else {\n"+ " else {" +
" message += ' ' + arguments[i];\n"+ " message += ' ' + arguments[i];" +
" }\n"+ " }" +
" }\n"+ " }" +
" oldLogs[oldLog].call(console, message);\n"+ " oldLogs[oldLog].call(console, message);" +
" }\n"+ " }" +
" })(k);\n"+ " })(k);" +
" }\n"+ " }" +
"})();"; "})();";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view); setContentView(R.layout.activity_web_view);
webView = findViewById(R.id.webView); webView = findViewById(R.id.webView);
Bundle b = getIntent().getExtras(); Bundle b = getIntent().getExtras();
uuid = b.getString("uuid"); uuid = b.getString("uuid");
String url = b.getString("url"); String url = b.getString("url");
options = new InAppBrowserOptions(); options = new InAppBrowserOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options")); options.parse((HashMap<String, Object>) b.getSerializable("options"));
headers = (HashMap<String, String>) b.getSerializable("headers"); headers = (HashMap<String, String>) b.getSerializable("headers");
InAppBrowserFlutterPlugin.webViewActivities.put(uuid, this); InAppBrowserFlutterPlugin.webViewActivities.put(uuid, this);
actionBar = getSupportActionBar(); actionBar = getSupportActionBar();
prepareWebView(); prepareWebView();
int cacheSize = 10 * 1024 * 1024; // 10MB int cacheSize = 10 * 1024 * 1024; // 10MB
httpClient = new OkHttpClient().newBuilder().cache(new Cache(getApplicationContext().getCacheDir(), cacheSize)).build(); httpClient = new OkHttpClient().newBuilder().cache(new Cache(getApplicationContext().getCacheDir(), cacheSize)).build();
webView.loadUrl(url, headers); webView.loadUrl(url, headers);
//webView.loadData("<!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>Document</title> </head> <body> ciao <img src=\"https://via.placeholder.com/350x150\" /> <img src=\"./images/test\" alt=\"not found\" /></body> </html>", "text/html", "utf8"); //webView.loadData("<!DOCTYPE assets> <assets lang=\"en\"> <head> <meta charset=\"UTF-8\"> <title>Document</title> </head> <body> ciao <img src=\"https://via.placeholder.com/350x150\" /> <img src=\"./images/test\" alt=\"not found\" /></body> </assets>", "text/assets", "utf8");
} }
private void prepareWebView() { private void prepareWebView() {
webView.addJavascriptInterface(new JavaScriptBridgeInterface(this), JavaScriptBridgeInterface.name); webView.addJavascriptInterface(new JavaScriptBridgeInterface(this), JavaScriptBridgeInterface.name);
inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this); inAppBrowserWebChromeClient = new InAppBrowserWebChromeClient(this);
webView.setWebChromeClient(inAppBrowserWebChromeClient); webView.setWebChromeClient(inAppBrowserWebChromeClient);
inAppBrowserWebViewClient = new InAppBrowserWebViewClient(this); inAppBrowserWebViewClient = new InAppBrowserWebViewClient(this);
webView.setWebViewClient(inAppBrowserWebViewClient); webView.setWebViewClient(inAppBrowserWebViewClient);
// final Activity activity = this; // final Activity activity = this;
// //
@ -142,241 +142,256 @@ public class WebViewActivity extends AppCompatActivity {
// } // }
// }); // });
WebSettings settings = webView.getSettings(); WebSettings settings = webView.getSettings();
if (options.hidden) if (options.hidden)
hide(); hide();
else else
show(); show();
settings.setJavaScriptEnabled(options.javaScriptEnabled); settings.setJavaScriptEnabled(options.javaScriptEnabled);
settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically); settings.setJavaScriptCanOpenWindowsAutomatically(options.javaScriptCanOpenWindowsAutomatically);
settings.setBuiltInZoomControls(options.builtInZoomControls); settings.setBuiltInZoomControls(options.builtInZoomControls);
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);
settings.setMediaPlaybackRequiresUserGesture(options.mediaPlaybackRequiresUserGesture); settings.setMediaPlaybackRequiresUserGesture(options.mediaPlaybackRequiresUserGesture);
settings.setDatabaseEnabled(options.databaseEnabled); settings.setDatabaseEnabled(options.databaseEnabled);
settings.setDomStorageEnabled(options.domStorageEnabled); settings.setDomStorageEnabled(options.domStorageEnabled);
if (!options.userAgent.isEmpty()) if (!options.userAgent.isEmpty())
settings.setUserAgentString(options.userAgent); settings.setUserAgentString(options.userAgent);
if (options.clearCache) if (options.clearCache)
clearCache(); clearCache();
else if (options.clearSessionCache) else if (options.clearSessionCache)
CookieManager.getInstance().removeSessionCookie(); CookieManager.getInstance().removeSessionCookie();
// Enable Thirdparty Cookies on >=Android 5.0 device // Enable Thirdparty Cookies on >=Android 5.0 device
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
CookieManager.getInstance().setAcceptThirdPartyCookies(webView,true); CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
settings.setLoadWithOverviewMode(true); settings.setLoadWithOverviewMode(true);
settings.setUseWideViewPort(options.useWideViewPort); settings.setUseWideViewPort(options.useWideViewPort);
settings.setSupportZoom(options.supportZoom); settings.setSupportZoom(options.supportZoom);
// fix webview scaling // fix webview scaling
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING); settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING);
else else
settings.setTextZoom(100); settings.setTextZoom(100);
if (options.progressBar) if (options.progressBar)
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
actionBar.setDisplayShowTitleEnabled(!options.hideTitleBar); actionBar.setDisplayShowTitleEnabled(!options.hideTitleBar);
if (!options.toolbarTop) if (!options.toolbarTop)
actionBar.hide(); actionBar.hide();
if (!options.toolbarTopBackgroundColor.isEmpty()) if (!options.toolbarTopBackgroundColor.isEmpty())
actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(options.toolbarTopBackgroundColor))); actionBar.setBackgroundDrawable(new ColorDrawable(Color.parseColor(options.toolbarTopBackgroundColor)));
if (!options.toolbarTopFixedTitle.isEmpty()) if (!options.toolbarTopFixedTitle.isEmpty())
actionBar.setTitle(options.toolbarTopFixedTitle); actionBar.setTitle(options.toolbarTopFixedTitle);
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
// Inflate menu to add items to action bar if it is present. // Inflate menu to add items to action bar if it is present.
inflater.inflate(R.menu.menu_main, menu); inflater.inflate(R.menu.menu_main, menu);
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setFocusable(true); searchView.setFocusable(true);
if (options.hideUrlBar) if (options.hideUrlBar)
menu.findItem(R.id.menu_search).setVisible(false); menu.findItem(R.id.menu_search).setVisible(false);
searchView.setQuery(webView.getUrl(), false); searchView.setQuery(webView.getUrl(), false);
if (options.toolbarTopFixedTitle.isEmpty()) if (options.toolbarTopFixedTitle.isEmpty())
actionBar.setTitle(webView.getTitle()); actionBar.setTitle(webView.getTitle());
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
if (!query.isEmpty()) { if (!query.isEmpty()) {
webView.loadUrl(query); webView.loadUrl(query);
searchView.setQuery("", false); searchView.setQuery("", false);
searchView.setIconified(true); searchView.setIconified(true);
return true; return true;
}
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
@Override
public boolean onClose() {
if (searchView.getQuery().toString().isEmpty())
searchView.setQuery(webView.getUrl(), false);
return false;
}
});
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (!b) {
searchView.setQuery("", false);
searchView.setIconified(true);
}
}
});
return true;
}
public void loadUrl (String url, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url);
} }
else {
result.error("Cannot load url", "", null);
}
}
public void loadUrl (String url, Map<String, String> headers, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url, headers);
}
else {
result.error("Cannot load url", "", null);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if (canGoBack())
goBack();
else if (options.closeOnCannotGoBack)
InAppBrowserFlutterPlugin.close(uuid);
return true;
}
return super.onKeyDown(keyCode, event);
}
public void close() {
hide();
finish();
}
public void reload() {
if (webView != null)
webView.reload();
}
public void goBack() {
if (webView != null && canGoBack())
webView.goBack();
}
public void goForward() {
if (webView != null && canGoForward())
webView.goForward();
}
public boolean canGoBack() {
return webView.canGoBack();
}
public boolean canGoForward() {
return webView.canGoForward();
}
public void hide() {
isHidden = true;
Intent openActivity = new Intent(this, InAppBrowserFlutterPlugin.registrar.activity().getClass());
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
}
public void show() {
isHidden = false;
Intent openActivity = new Intent(InAppBrowserFlutterPlugin.registrar.activity(), WebViewActivity.class);
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
}
public void stopLoading(){
if (webView != null)
webView.stopLoading();
}
public boolean isLoading() {
if (webView != null)
return isLoading;
return false; return false;
} }
private void clearCookies() { @Override
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { public boolean onQueryTextChange(String newText) {
CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() { return false;
@Override }
public void onReceiveValue(Boolean aBoolean) {
} });
});
} else { searchView.setOnCloseListener(new SearchView.OnCloseListener() {
CookieManager.getInstance().removeAllCookie(); @Override
public boolean onClose() {
if (searchView.getQuery().toString().isEmpty())
searchView.setQuery(webView.getUrl(), false);
return false;
}
});
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (!b) {
searchView.setQuery("", false);
searchView.setIconified(true);
} }
} }
});
private void clearCache() { return true;
webView.clearCache(true); }
clearCookies();
webView.clearFormData();
}
public void goBackButtonClicked(MenuItem item) { public void loadUrl(String url, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url);
} else {
result.error("Cannot load url", "", null);
}
}
public void loadUrl(String url, Map<String, String> headers, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url, headers);
} else {
result.error("Cannot load url", "", null);
}
}
public void loadFile(String url, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url);
} else {
result.error("Cannot load url", "", null);
}
}
public void loadFile(String url, Map<String, String> headers, MethodChannel.Result result) {
if (webView != null && !url.isEmpty()) {
webView.loadUrl(url, headers);
} else {
result.error("Cannot load url", "", null);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
if (canGoBack())
goBack(); goBack();
else if (options.closeOnCannotGoBack)
InAppBrowserFlutterPlugin.close(uuid, null);
return true;
} }
return super.onKeyDown(keyCode, event);
}
public void goForwardButtonClicked(MenuItem item) { public void close() {
goForward(); hide();
} finish();
}
public void shareButtonClicked(MenuItem item) { public void reload() {
Intent share = new Intent(Intent.ACTION_SEND); if (webView != null)
share.setType("text/plain"); webView.reload();
share.putExtra(Intent.EXTRA_TEXT, webView.getUrl()); }
startActivity(Intent.createChooser(share, "Share"));
}
public void reloadButtonClicked(MenuItem item) { public void goBack() {
reload(); if (webView != null && canGoBack())
} webView.goBack();
}
public void closeButtonClicked(MenuItem item) { public void goForward() {
InAppBrowserFlutterPlugin.close(uuid); if (webView != null && canGoForward())
webView.goForward();
}
public boolean canGoBack() {
return webView.canGoBack();
}
public boolean canGoForward() {
return webView.canGoForward();
}
public void hide() {
isHidden = true;
Intent openActivity = new Intent(this, InAppBrowserFlutterPlugin.registrar.activity().getClass());
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
}
public void show() {
isHidden = false;
Intent openActivity = new Intent(InAppBrowserFlutterPlugin.registrar.activity(), WebViewActivity.class);
openActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityIfNeeded(openActivity, 0);
}
public void stopLoading() {
if (webView != null)
webView.stopLoading();
}
public boolean isLoading() {
if (webView != null)
return isLoading;
return false;
}
private void clearCookies() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean aBoolean) {
}
});
} else {
CookieManager.getInstance().removeAllCookie();
} }
}
private void clearCache() {
webView.clearCache(true);
clearCookies();
webView.clearFormData();
}
public void goBackButtonClicked(MenuItem item) {
goBack();
}
public void goForwardButtonClicked(MenuItem item) {
goForward();
}
public void shareButtonClicked(MenuItem item) {
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("text/plain");
share.putExtra(Intent.EXTRA_TEXT, webView.getUrl());
startActivity(Intent.createChooser(share, "Share"));
}
public void reloadButtonClicked(MenuItem item) {
reload();
}
public void closeButtonClicked(MenuItem item) {
InAppBrowserFlutterPlugin.close(uuid, null);
}
} }

View File

@ -17,99 +17,78 @@ import java.util.Map;
public class ChromeCustomTabsActivity extends Activity { public class ChromeCustomTabsActivity extends Activity {
protected static final String LOG_TAG = "CustomTabsActivity"; protected static final String LOG_TAG = "CustomTabsActivity";
String uuid; String uuid;
String uuidFallback; CustomTabsIntent.Builder builder;
CustomTabsIntent.Builder builder; ChromeCustomTabsOptions options;
ChromeCustomTabsOptions options; private CustomTabActivityHelper customTabActivityHelper;
Map<String, String> headersFallback; private final int CHROME_CUSTOM_TAB_REQUEST_CODE = 100;
InAppBrowserOptions optionsFallback;
private CustomTabActivityHelper customTabActivityHelper;
private final int CHROME_CUSTOM_TAB_REQUEST_CODE = 100;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.chrome_custom_tabs_layout); setContentView(R.layout.chrome_custom_tabs_layout);
Bundle b = getIntent().getExtras(); Bundle b = getIntent().getExtras();
uuid = b.getString("uuid"); assert b != null;
uuidFallback = b.getString("uuidFallback"); uuid = b.getString("uuid");
String url = b.getString("url"); String url = b.getString("url");
options = new ChromeCustomTabsOptions(); options = new ChromeCustomTabsOptions();
options.parse((HashMap<String, Object>) b.getSerializable("options")); options.parse((HashMap<String, Object>) b.getSerializable("options"));
headersFallback = (HashMap<String, String>) b.getSerializable("headers"); InAppBrowserFlutterPlugin.chromeCustomTabsActivities.put(uuid, this);
optionsFallback = new InAppBrowserOptions(); customTabActivityHelper = new CustomTabActivityHelper();
optionsFallback.parse((HashMap<String, Object>) b.getSerializable("optionsFallback")); builder = new CustomTabsIntent.Builder();
InAppBrowserFlutterPlugin.chromeCustomTabsActivities.put(uuid, this); prepareCustomTabs();
customTabActivityHelper = new CustomTabActivityHelper(); CustomTabsIntent customTabsIntent = builder.build();
builder = new CustomTabsIntent.Builder();
prepareCustomTabs(); CustomTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), CHROME_CUSTOM_TAB_REQUEST_CODE);
CustomTabsIntent customTabsIntent = builder.build(); Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", obj);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", obj);
}
boolean chromeCustomTabsOpened = customTabActivityHelper.openCustomTab(this, customTabsIntent, Uri.parse(url), CHROME_CUSTOM_TAB_REQUEST_CODE, private void prepareCustomTabs() {
new CustomTabActivityHelper.CustomTabFallback() { if (options.addShareButton)
@Override builder.addDefaultShareMenuItem();
public void openUri(Activity activity, Uri uri) {
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) { if (!options.toolbarBackgroundColor.isEmpty())
Map<String, Object> obj = new HashMap<>(); builder.setToolbarColor(Color.parseColor(options.toolbarBackgroundColor));
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserOpened", obj); builder.setShowTitle(options.showTitle);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserLoaded", obj);
} if (options.enableUrlBarHiding)
} builder.enableUrlBarHiding();
private void prepareCustomTabs() { builder.setInstantAppsEnabled(options.instantAppsEnabled);
if (options.addShareButton) }
builder.addDefaultShareMenuItem();
@Override
if (!options.toolbarBackgroundColor.isEmpty()) protected void onStart() {
builder.setToolbarColor(Color.parseColor(options.toolbarBackgroundColor)); super.onStart();
customTabActivityHelper.bindCustomTabsService(this);
builder.setShowTitle(options.showTitle); }
if (options.enableUrlBarHiding) @Override
builder.enableUrlBarHiding(); protected void onStop() {
super.onStop();
builder.setInstantAppsEnabled(options.instantAppsEnabled); customTabActivityHelper.unbindCustomTabsService(this);
} }
@Override @Override
protected void onStart() { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onStart(); if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
customTabActivityHelper.bindCustomTabsService(this); finish();
} Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
@Override InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserClosed", obj);
protected void onStop() {
super.onStop();
customTabActivityHelper.unbindCustomTabsService(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CHROME_CUSTOM_TAB_REQUEST_CODE) {
finish();
Map<String, Object> obj = new HashMap<>();
obj.put("uuid", uuid);
InAppBrowserFlutterPlugin.channel.invokeMethod("onChromeSafariBrowserClosed", obj);
}
} }
}
} }

View File

@ -27,26 +27,13 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
* @param activity The host activity. * @param activity The host activity.
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available. * @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
* @param uri the Uri to be opened. * @param uri the Uri to be opened.
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
*/ */
public static boolean openCustomTab(Activity activity, public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent, CustomTabsIntent customTabsIntent,
Uri uri, Uri uri,
int requestCode, int requestCode) {
CustomTabFallback fallback) { customTabsIntent.intent.setData(uri);
//If we cant find a package name, it means theres no browser that supports activity.startActivityForResult(customTabsIntent.intent, requestCode);
//Chrome Custom Tabs installed. So, we fallback to the webview
if (!isAvailable(activity)) {
if (fallback != null) {
fallback.openUri(activity, uri);
}
} else {
//customTabsIntent.intent.setPackage(packageName);
customTabsIntent.intent.setData(uri);
activity.startActivityForResult(customTabsIntent.intent, requestCode);
return true;
}
return false;
} }
public static boolean isAvailable(Activity activity) { public static boolean isAvailable(Activity activity) {
@ -144,16 +131,4 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
void onCustomTabsDisconnected(); void onCustomTabsDisconnected();
} }
/**
* To be used as a fallback to open the Uri when Custom Tabs is not available.
*/
public interface CustomTabFallback {
/**
*
* @param activity The Activity that wants to open the Uri.
* @param uri The uri to be opened by the fallback.
*/
void openUri(Activity activity, Uri uri);
}
} }

View File

@ -0,0 +1,7 @@
body {
background-color: #333;
color: #fff;
}
img {
max-width: 100%;
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 439 137.29" enable-background="new 0 0 439 137.29" xml:space="preserve">
<g>
<g opacity="0.54">
<path d="M207.08,20.2h27.55c9.35,0,17.51,1.93,24.49,5.8c6.97,3.87,12.33,9.25,16.07,16.13c3.74,6.89,5.61,14.79,5.61,23.72
s-1.87,16.83-5.61,23.72s-9.1,12.26-16.07,16.13c-6.97,3.87-15.13,5.8-24.49,5.8h-27.55V20.2z M234.63,101.19
c10.8,0,19.36-3.1,25.7-9.31c6.33-6.21,9.5-14.88,9.5-26.02s-3.17-19.81-9.5-26.02c-6.33-6.21-14.9-9.31-25.7-9.31H217.8v70.65
h16.83V101.19z"/>
<path d="M297.49,110.75c-3.74-1.87-6.63-4.44-8.67-7.72c-2.04-3.27-3.06-6.99-3.06-11.16c0-6.89,2.59-12.26,7.78-16.13
c5.18-3.87,11.73-5.8,19.64-5.8c3.91,0,7.54,0.43,10.9,1.28s5.93,1.83,7.72,2.93V70.2c0-4.85-1.7-8.74-5.1-11.67
c-3.4-2.93-7.7-4.4-12.88-4.4c-3.66,0-7.01,0.79-10.08,2.36c-3.06,1.57-5.48,3.76-7.27,6.57l-8.16-6.12
c2.55-3.91,6.06-6.97,10.52-9.18c4.46-2.21,9.42-3.32,14.86-3.32c8.84,0,15.79,2.32,20.85,6.95c5.06,4.64,7.59,10.95,7.59,18.94
v41.19H331.8v-9.31h-0.51c-1.87,3.15-4.68,5.82-8.42,8.03c-3.74,2.21-7.95,3.32-12.63,3.32
C305.49,113.56,301.24,112.62,297.49,110.75z M321.47,101.19c3.14-1.87,5.65-4.38,7.52-7.52s2.81-6.59,2.81-10.33
c-2.04-1.36-4.55-2.47-7.52-3.32c-2.98-0.85-6.12-1.28-9.44-1.28c-5.95,0-10.44,1.23-13.45,3.7c-3.02,2.47-4.53,5.66-4.53,9.56
c0,3.57,1.36,6.46,4.08,8.67c2.72,2.21,6.16,3.32,10.33,3.32C314.92,103.99,318.33,103.06,321.47,101.19z"/>
<path d="M353.57,47.5h10.33v10.33h0.51c1.53-3.83,4.12-6.8,7.78-8.93c3.65-2.12,7.65-3.19,11.99-3.19c1.87,0,3.44,0.13,4.72,0.38
v11.1c-1.45-0.34-3.4-0.51-5.87-0.51c-5.53,0-10.01,1.83-13.45,5.48c-3.44,3.66-5.17,8.42-5.17,14.28v36.09h-10.84V47.5
L353.57,47.5z M420.89,112.26c-2.25-0.86-4.14-2.03-5.68-3.51c-1.7-1.64-2.98-3.55-3.83-5.71c-0.85-2.16-1.28-4.8-1.28-7.92V56.3
h-11.35v-9.82h11.35V28.12h10.84v18.36h15.81v9.82h-15.81v36.24c0,3.65,0.68,6.34,2.04,8.08c1.61,1.91,3.95,2.87,7.01,2.87
c2.46,0,4.85-0.72,7.14-2.17v10.59c-1.28,0.59-2.57,1.02-3.89,1.28s-3,0.38-5.04,0.38C425.59,113.56,423.15,113.12,420.89,112.26z
"/>
</g>
<g>
<path fill="#01579B" d="M29.64,108.94L6.36,85.66c-2.76-2.84-4.48-6.84-4.48-10.75c0-1.81,1.02-4.64,1.79-6.27l21.49-44.77
L29.64,108.94z"/>
<path fill="#40C4FF" d="M109.34,28.35L86.06,5.07c-2.03-2.04-6.27-4.48-9.85-4.48c-3.08,0-6.1,0.62-8.06,1.79L25.17,23.87
L109.34,28.35z"/>
<polygon fill="#40C4FF" points="57.4,136.7 113.82,136.7 113.82,112.52 71.73,99.09 33.23,112.52 "/>
<path fill="#29B6F6" d="M25.17,96.41c0,7.18,0.9,8.95,4.48,12.54l3.58,3.58h80.59l-39.4-44.77L25.17,23.88V96.41z"/>
<path fill="#01579B" d="M96.8,23.87H25.16l88.65,88.65h24.18V57l-28.65-28.65C105.32,24.31,101.74,23.87,96.8,23.87z"/>
<path opacity="0.2" fill="#FFFFFF" enable-background="new " d="M30.54,109.84c-3.58-3.6-4.48-7.14-4.48-13.43V24.77l-0.9-0.9
V96.4C25.17,102.7,25.17,104.44,30.54,109.84l2.69,2.69l0,0L30.54,109.84z"/>
<polygon opacity="0.2" fill="#263238" enable-background="new " points="137.1,56.11 137.1,111.63 112.92,111.63
113.82,112.52 138,112.52 138,57.01 "/>
<path opacity="0.2" fill="#FFFFFF" enable-background="new " d="M109.34,28.35c-4.44-4.44-8.08-4.48-13.43-4.48H25.17l0.9,0.9
h69.85C98.58,24.77,105.33,24.32,109.34,28.35L109.34,28.35z"/>
<radialGradient id="SVGID_1_" cx="69.955" cy="60.8864" r="68.065" gradientTransform="matrix(1 0 0 -1 0 129.5328)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.1"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</radialGradient>
<path opacity="0.2" fill="url(#SVGID_1_)" enable-background="new " d="M137.1,56.11l-27.76-27.76L86.06,5.07
c-2.03-2.04-6.27-4.48-9.85-4.48c-3.08,0-6.1,0.62-8.06,1.79L25.17,23.87L3.68,68.64c-0.77,1.63-1.79,4.46-1.79,6.27
c0,3.91,1.72,7.91,4.48,10.75l21.46,21.3c0.51,0.63,1.11,1.27,1.83,1.98l0.9,0.9l2.69,2.69l23.28,23.28l0.9,0.9h55.52h0.9v-24.18
h24.18v-0.06V57.01L137.1,56.11z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

34
example/assets/index.html Normal file
View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="css/style.css">
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container">
<div class="container">
<img src="images/dart.svg" alt="dart logo">
<div class="row">
<div class="col-sm">
One of three columns
</div>
<div class="col-sm">
One of three columns
</div>
<div class="col-sm">
One of three columns
</div>
</div>
</div>
<script>
console.log("hello");
</script>
</div>
</body>
</html>

View File

@ -2,6 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<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>

View File

@ -1,8 +1,8 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappbrowser/flutter_inappbrowser.dart'; import 'package:flutter_inappbrowser/flutter_inappbrowser.dart';
class MyInAppBrowser extends InAppBrowser { class MyInAppBrowser extends InAppBrowser {
@override @override
Future onLoadStart(String url) async { Future onLoadStart(String url) async {
print("\n\nStarted $url\n\n"); print("\n\nStarted $url\n\n");
@ -13,6 +13,8 @@ class MyInAppBrowser extends InAppBrowser {
Future onLoadStop(String url) async { Future onLoadStop(String url) async {
print("\n\nStopped $url\n\n"); print("\n\nStopped $url\n\n");
//print("\n\n ${await this.isHidden()} \n\n");
// await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest', 1, 5,'string', {'key': 5}, [4,6,8]);"); // await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest', 1, 5,'string', {'key': 5}, [4,6,8]);");
// await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest2', false, null, undefined);"); // await this.injectScriptCode("window.flutter_inappbrowser.callHandler('handlerTest2', false, null, undefined);");
// await this.injectScriptCode("setTimeout(function(){window.flutter_inappbrowser.callHandler('handlerTest', 'anotherString');}, 1000);"); // await this.injectScriptCode("setTimeout(function(){window.flutter_inappbrowser.callHandler('handlerTest', 'anotherString');}, 1000);");
@ -40,8 +42,7 @@ class MyInAppBrowser extends InAppBrowser {
// var x = {"as":4, "dfdfg": 6}; // var x = {"as":4, "dfdfg": 6};
// x; // x;
// """)); // """));
//print("\n\n ${await this.isHidden()} \n\n"); //
// await this.injectScriptFile("https://code.jquery.com/jquery-3.3.1.min.js"); // await this.injectScriptFile("https://code.jquery.com/jquery-3.3.1.min.js");
// this.injectScriptCode(""" // this.injectScriptCode("""
// \$( "body" ).html( "Next Step..." ) // \$( "body" ).html( "Next Step..." )
@ -73,29 +74,33 @@ class MyInAppBrowser extends InAppBrowser {
} }
@override @override
void onLoadResource(WebResourceResponse response, WebResourceRequest request) { void onLoadResource(
WebResourceResponse response, WebResourceRequest request) {
print("Started at: " + response.startTime.toString() + "ms ---> duration: " + response.duration.toString() + "ms " + response.url); print("Started at: " +
response.startTime.toString() +
"ms ---> duration: " +
response.duration.toString() +
"ms " +
response.url);
// if (response.headers["content-length"] != null) // if (response.headers["content-length"] != null)
// print(response.headers["content-length"] + " length"); // print(response.headers["content-length"] + " length");
} }
@override @override
void onConsoleMessage(ConsoleMessage consoleMessage) { void onConsoleMessage(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}
// """); """);
} }
} }
MyInAppBrowser inAppBrowserFallback = new MyInAppBrowser(); MyInAppBrowser inAppBrowserFallback = new MyInAppBrowser();
class MyChromeSafariBrowser extends ChromeSafariBrowser { class MyChromeSafariBrowser extends ChromeSafariBrowser {
MyChromeSafariBrowser(browserFallback) : super(browserFallback); MyChromeSafariBrowser(browserFallback) : super(browserFallback);
@override @override
@ -115,9 +120,10 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser {
} }
// adding a webview fallback // adding a webview fallback
MyChromeSafariBrowser chromeSafariBrowser = new MyChromeSafariBrowser(inAppBrowserFallback); MyChromeSafariBrowser chromeSafariBrowser =
new MyChromeSafariBrowser(inAppBrowserFallback);
void main() { Future main() async {
runApp(new MyApp()); runApp(new MyApp());
} }
@ -127,11 +133,11 @@ class MyApp extends StatefulWidget {
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// int indexTest = inAppBrowserFallback.addJavaScriptHandler("handlerTest", (arguments) async { // int indexTest = inAppBrowserFallback.addJavaScriptHandler("handlerTest",
// (arguments) async {
// print("handlerTest arguments"); // print("handlerTest arguments");
// print(arguments); // print(arguments);
// }); // });
@ -150,21 +156,45 @@ class _MyAppState extends State<MyApp> {
title: const Text('Flutter InAppBrowser Plugin example app'), title: const Text('Flutter InAppBrowser Plugin example app'),
), ),
body: new Center( body: new Center(
child: new RaisedButton(onPressed: () { child: new RaisedButton(
//chromeSafariBrowser.open("https://flutter.io/"); onPressed: () async {
inAppBrowserFallback.open(url: "https://flutter.io/", options: { // await chromeSafariBrowser.open("https://flutter.io/");
//"useOnLoadResource": true,
//"hidden": true,
//"toolbarTopFixedTitle": "Fixed title",
//"useShouldOverrideUrlLoading": true
//"hideUrlBar": true,
//"toolbarTop": false,
//"toolbarBottom": false
});
}, // await InAppBrowser.openWithSystemBrowser("https://flutter.io/");
child: Text("Open InAppBrowser")
), await inAppBrowserFallback.openOnLocalhost("assets/index.html", options: {
"useOnLoadResource": true,
//"hidden": true,
//"toolbarTopFixedTitle": "Fixed title",
//"useShouldOverrideUrlLoading": true
//"hideUrlBar": true,
//"toolbarTop": false,
//"toolbarBottom": false
});
// await inAppBrowserFallback.open(url: "assets/index.html", options: {
// "isLocalFile": true,
// "useOnLoadResource": true,
// //"hidden": true,
// //"toolbarTopFixedTitle": "Fixed title",
// //"useShouldOverrideUrlLoading": true
// //"hideUrlBar": true,
// //"toolbarTop": false,
// //"toolbarBottom": false
// });
// await inAppBrowserFallback.open(url: "https://flutter.io/", options: {
// //"useOnLoadResource": true,
// //"hidden": true,
// //"toolbarTopFixedTitle": "Fixed title",
// //"useShouldOverrideUrlLoading": true
// //"hideUrlBar": true,
// //"toolbarTop": false,
// //"toolbarBottom": false
// });
//await inAppBrowserFallback.openOnLocalhost("assets/index.html");
},
child: Text("Open InAppBrowser")),
), ),
), ),
); );

View File

@ -38,6 +38,11 @@ flutter:
# the material Icons class. # the material Icons class.
uses-material-design: true uses-material-design: true
assets:
- assets/index.html
- assets/css/
- assets/images/
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg

View File

@ -14,6 +14,12 @@
<excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/.dart_tool" /> <excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/.pub" /> <excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/build" /> <excludeFolder url="file://$MODULE_DIR$/example/flutter_plugin/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/.pub" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/.symlinks/plugins/flutter_inappbrowser/example/build" />
<excludeFolder url="file://$MODULE_DIR$/example/ios/Flutter/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" />

View File

@ -12,6 +12,7 @@ public class InAppBrowserOptions: Options {
var useShouldOverrideUrlLoading = false var useShouldOverrideUrlLoading = false
var useOnLoadResource = false var useOnLoadResource = false
var openWithSystemBrowser = false;
var clearCache = false var clearCache = false
var userAgent = "" var userAgent = ""
var javaScriptEnabled = true var javaScriptEnabled = true
@ -21,6 +22,7 @@ public class InAppBrowserOptions: Options {
var toolbarTopBackgroundColor = "" var toolbarTopBackgroundColor = ""
var hideUrlBar = false var hideUrlBar = false
var mediaPlaybackRequiresUserGesture = true var mediaPlaybackRequiresUserGesture = true
var isLocalFile = false
var disallowOverScroll = false var disallowOverScroll = false
var toolbarBottom = true var toolbarBottom = true

View File

@ -81,7 +81,6 @@ func convertToDictionary(text: String) -> [String: Any]? {
return nil return nil
} }
//extension WKWebView{ //extension WKWebView{
// //
// var keyboardDisplayRequiresUserAction: Bool? { // var keyboardDisplayRequiresUserAction: Bool? {
@ -139,6 +138,7 @@ class WKWebView_IBWrapper: WKWebView {
} }
class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler { class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UITextFieldDelegate, WKScriptMessageHandler {
@IBOutlet var webView: WKWebView_IBWrapper! @IBOutlet var webView: WKWebView_IBWrapper!
@IBOutlet var closeButton: UIButton! @IBOutlet var closeButton: UIButton!
@IBOutlet var reloadButton: UIBarButtonItem! @IBOutlet var reloadButton: UIBarButtonItem!
@ -200,7 +200,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
spinner.hidesWhenStopped = true spinner.hidesWhenStopped = true
spinner.isHidden = false spinner.isHidden = false
spinner.stopAnimating() spinner.stopAnimating()
loadUrl(url: self.currentURL!, headers: self.initHeaders) loadUrl(url: self.currentURL!, headers: self.initHeaders)
} }
@ -421,11 +421,7 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
} }
@objc func close() { @objc func close() {
currentURL = nil //currentURL = nil
if (navigationDelegate != nil) {
navigationDelegate?.browserExit(uuid: self.uuid)
}
weak var weakSelf = self weak var weakSelf = self
@ -435,12 +431,18 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
weakSelf?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in weakSelf?.presentingViewController?.dismiss(animated: true, completion: {() -> Void in
self.tmpWindow?.windowLevel = 0.0 self.tmpWindow?.windowLevel = 0.0
UIApplication.shared.delegate?.window??.makeKeyAndVisible() UIApplication.shared.delegate?.window??.makeKeyAndVisible()
if (self.navigationDelegate != nil) {
self.navigationDelegate?.browserExit(uuid: self.uuid)
}
}) })
} }
else { else {
weakSelf?.parent?.dismiss(animated: true, completion: {() -> Void in weakSelf?.parent?.dismiss(animated: true, completion: {() -> Void in
self.tmpWindow?.windowLevel = 0.0 self.tmpWindow?.windowLevel = 0.0
UIApplication.shared.delegate?.window??.makeKeyAndVisible() UIApplication.shared.delegate?.window??.makeKeyAndVisible()
if (self.navigationDelegate != nil) {
self.navigationDelegate?.browserExit(uuid: self.uuid)
}
}) })
} }
}) })
@ -527,7 +529,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
} }
if navigationAction.navigationType == .linkActivated && (browserOptions?.useShouldOverrideUrlLoading)! { if navigationAction.navigationType == .linkActivated && (browserOptions?.useShouldOverrideUrlLoading)! {
navigationDelegate?.shouldOverrideUrlLoading(uuid: self.uuid, webView: webView, url: url) if navigationDelegate != nil {
navigationDelegate?.shouldOverrideUrlLoading(uuid: self.uuid, webView: webView, url: url)
}
decisionHandler(.cancel) decisionHandler(.cancel)
return return
} }
@ -606,7 +610,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
spinner.startAnimating() spinner.startAnimating()
} }
return (navigationDelegate?.onLoadStart(uuid: self.uuid, webView: webView))! if navigationDelegate != nil {
navigationDelegate?.onLoadStart(uuid: self.uuid, webView: webView)
}
} }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
@ -617,7 +623,10 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
backButton.isEnabled = webView.canGoBack backButton.isEnabled = webView.canGoBack
forwardButton.isEnabled = webView.canGoForward forwardButton.isEnabled = webView.canGoForward
spinner.stopAnimating() spinner.stopAnimating()
navigationDelegate?.onLoadStop(uuid: self.uuid, webView: webView)
if navigationDelegate != nil {
navigationDelegate?.onLoadStop(uuid: self.uuid, webView: webView)
}
} }
func webView(_ view: WKWebView, func webView(_ view: WKWebView,
@ -627,15 +636,19 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
} }
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("webView:didFailNavigationWithError - \(Int(error._code)): \(error.localizedDescription)")
backButton.isEnabled = webView.canGoBack backButton.isEnabled = webView.canGoBack
forwardButton.isEnabled = webView.canGoForward forwardButton.isEnabled = webView.canGoForward
spinner.stopAnimating() spinner.stopAnimating()
navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error)
if navigationDelegate != nil {
navigationDelegate?.onLoadError(uuid: self.uuid, webView: webView, error: error)
}
} }
func didReceiveResourceResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int, duration: Int) { func didReceiveResourceResponse(_ response: URLResponse, fromRequest request: URLRequest?, withData data: Data, startTime: Int, duration: Int) {
navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response, fromRequest: request, withData: data, startTime: startTime, duration: duration) if navigationDelegate != nil {
navigationDelegate?.onLoadResource(uuid: self.uuid, webView: webView, response: response, fromRequest: request, withData: data, startTime: startTime, duration: duration)
}
} }
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
@ -663,7 +676,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
messageLevel = "LOG" messageLevel = "LOG"
break; break;
} }
navigationDelegate?.onConsoleMessage(uuid: self.uuid, sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel) if navigationDelegate != nil {
navigationDelegate?.onConsoleMessage(uuid: self.uuid, sourceURL: "", lineNumber: 1, message: message.body as! String, messageLevel: messageLevel)
}
} }
else if message.name == "resourceLoaded" { else if message.name == "resourceLoaded" {
if let resource = convertToDictionary(text: message.body as! String) { if let resource = convertToDictionary(text: message.body as! String) {
@ -695,7 +710,9 @@ class InAppBrowserWebViewController: UIViewController, WKUIDelegate, WKNavigatio
let body = message.body as! [String: Any] let body = message.body as! [String: Any]
let handlerName = body["handlerName"] as! String let handlerName = body["handlerName"] as! String
let args = body["args"] as! String let args = body["args"] as! String
self.navigationDelegate?.onCallJsHandler(uuid: self.uuid, webView: webView, handlerName: handlerName, args: args) if navigationDelegate != nil {
self.navigationDelegate?.onCallJsHandler(uuid: self.uuid, webView: webView, handlerName: handlerName, args: args)
}
} }
} }
} }

View File

@ -14,12 +14,13 @@ public class Options: NSObject {
super.init() super.init()
} }
public func parse(options: [String: Any]) { public func parse(options: [String: Any]) -> Options {
for (key, value) in options { for (key, value) in options {
if self.responds(to: Selector(key)) { if self.responds(to: Selector(key)) {
self.setValue(value, forKey: key) self.setValue(value, forKey: key)
} }
} }
return self
} }
} }

View File

@ -40,15 +40,15 @@ class SafariViewController: SFSafariViewController, SFSafariViewControllerDelega
} }
func close() { func close() {
if (statusDelegate != nil) {
statusDelegate?.safariExit(uuid: self.uuid)
}
dismiss(animated: true) dismiss(animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400), execute: {() -> Void in
self.tmpWindow?.windowLevel = 0.0 self.tmpWindow?.windowLevel = 0.0
UIApplication.shared.delegate?.window??.makeKeyAndVisible() UIApplication.shared.delegate?.window??.makeKeyAndVisible()
if (self.statusDelegate != nil) {
self.statusDelegate?.safariExit(uuid: self.uuid)
}
}) })
} }

View File

@ -22,19 +22,6 @@ import Foundation
import AVFoundation import AVFoundation
import SafariServices import SafariServices
//class CustomURLCache: URLCache {
// override func cachedResponse(for request: URLRequest) -> CachedURLResponse? {
// dump(request.url)
// return super.cachedResponse(for: request)
// }
//
// override func getCachedResponse(for dataTask: URLSessionDataTask,
// completionHandler: @escaping (CachedURLResponse?) -> Void) {
// dump(dataTask.response)
// super.getCachedResponse(for: dataTask, completionHandler: completionHandler)
// }
//}
let WEBVIEW_STORYBOARD = "WebView" let WEBVIEW_STORYBOARD = "WebView"
let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController" let WEBVIEW_STORYBOARD_CONTROLLER_ID = "viewController"
@ -47,6 +34,9 @@ extension Dictionary where Key: ExpressibleByStringLiteral {
} }
public class SwiftFlutterPlugin: NSObject, FlutterPlugin { public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
static var registrar: FlutterPluginRegistrar?
var webViewControllers: [String: InAppBrowserWebViewController?] = [:] var webViewControllers: [String: InAppBrowserWebViewController?] = [:]
var safariViewControllers: [String: Any?] = [:] var safariViewControllers: [String: Any?] = [:]
@ -59,11 +49,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
public static func register(with registrar: FlutterPluginRegistrar) { public static func register(with registrar: FlutterPluginRegistrar) {
// URLProtocol.wk_registerScheme("http")
// URLProtocol.wk_registerScheme("https") SwiftFlutterPlugin.registrar = registrar
// URLProtocol.registerClass(MyURLProtocol.self)
//URLCache.shared = CustomURLCache()
let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger()) let channel = FlutterMethodChannel(name: "com.pichillilorenzo/flutter_inappbrowser", binaryMessenger: registrar.messenger())
let instance = SwiftFlutterPlugin(with: registrar) let instance = SwiftFlutterPlugin(with: registrar)
@ -73,7 +60,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
let arguments = call.arguments as? NSDictionary let arguments = call.arguments as? NSDictionary
let uuid: String = (arguments!["uuid"] as? String)! let uuid: String = (arguments!["uuid"] as? String)!
switch call.method { switch call.method {
case "open": case "open":
self.open(uuid: uuid, arguments: arguments!, result: result) self.open(uuid: uuid, arguments: arguments!, result: result)
@ -177,7 +164,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
else { else {
print("IAB.close() called but it was already closed.") print("IAB.close() called but it was already closed.")
return
} }
} }
@ -192,41 +178,38 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
let url: String = (arguments["url"] as? String)! let url: String = (arguments["url"] as? String)!
let headers = (arguments["headers"] as? [String: String])! let headers = (arguments["headers"] as? [String: String])!
var target: String? = (arguments["target"] as? String)!
target = target != nil ? target : "_self"
let absoluteUrl = URL(string: url)?.absoluteURL let absoluteUrl = URL(string: url)?.absoluteURL
let useChromeSafariBrowser = (arguments["useChromeSafariBrowser"] as? Bool) let useChromeSafariBrowser = (arguments["useChromeSafariBrowser"] as? Bool)
if useChromeSafariBrowser! { if useChromeSafariBrowser! {
let uuidFallback = (arguments["uuidFallback"] as? String)! let uuidFallback = (arguments["uuidFallback"] as? String)!
let options = (arguments["options"] as? [String: Any])! let safariOptions = SafariBrowserOptions()
let optionsFallback = (arguments["optionsFallback"] as? [String: Any])! safariOptions.parse(options: (arguments["options"] as? [String: Any])!)
open(uuid: uuid, uuidFallback: uuidFallback, inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: true, withOptionsFallback: optionsFallback); let optionsFallback = InAppBrowserOptions()
optionsFallback.parse(options: (arguments["optionsFallback"] as? [String: Any])!)
open(uuid: uuid, uuidFallback: uuidFallback, inAppBrowser: absoluteUrl!, headers: headers, withOptions: safariOptions, useChromeSafariBrowser: true, withOptionsFallback: optionsFallback, result: result);
} }
else { else {
let options = (arguments["options"] as? [String: Any])! let options = InAppBrowserOptions()
options.parse(options: (arguments["options"] as? [String: Any])!)
if isSystemUrl(absoluteUrl!) { if isSystemUrl(absoluteUrl!) {
target = "_system" options.openWithSystemBrowser = true
} }
if (target == "_self" || target == "_target") { if (options.openWithSystemBrowser) {
open(uuid: uuid, uuidFallback: nil, inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil) open(inSystem: absoluteUrl!, result: result)
}
else if (target == "_system") {
open(inSystem: absoluteUrl!)
} }
else { else {
// anything else open(uuid: uuid, uuidFallback: nil, inAppBrowser: absoluteUrl!, headers: headers, withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil, result: result)
open(uuid: uuid, uuidFallback: nil, inAppBrowser: absoluteUrl!, headers: headers,withOptions: options, useChromeSafariBrowser: false, withOptionsFallback: nil)
} }
} }
result(true)
} }
func open(uuid: String, uuidFallback: String?, inAppBrowser url: URL, headers: [String: String], withOptions options: [String: Any], useChromeSafariBrowser: Bool, withOptionsFallback optionsFallback: [String: Any]?) { func open(uuid: String, uuidFallback: String?, inAppBrowser url: URL, headers: [String: String], withOptions options: Options, useChromeSafariBrowser: Bool, withOptionsFallback optionsFallback: Options?, result: @escaping FlutterResult) {
var uuid = uuid var uuid = uuid
@ -235,7 +218,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
let safariViewController = self.safariViewControllers[uuid] let safariViewController = self.safariViewControllers[uuid]
if safariViewController != nil { if safariViewController != nil {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
(safariViewController! as! SafariViewController).close() (safariViewController! as! SafariViewController).close()
@ -264,8 +246,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
if useChromeSafariBrowser == true { if useChromeSafariBrowser == true {
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
let safariOptions = SafariBrowserOptions() let safariOptions = options as! SafariBrowserOptions
safariOptions.parse(options: options)
let safari: SafariViewController let safari: SafariViewController
@ -290,24 +271,37 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
tmpController.present(self.safariViewControllers[uuid]! as! SFSafariViewController, animated: true) tmpController.present(self.safariViewControllers[uuid]! as! SFSafariViewController, animated: true)
onChromeSafariBrowserOpened(uuid: uuid) onChromeSafariBrowserOpened(uuid: uuid)
result(true)
return return
} }
else { else {
if uuidFallback == nil { if uuidFallback == nil {
print("No WebView fallback declared.") print("No WebView fallback declared.")
result(true)
return return
} }
uuid = uuidFallback! uuid = uuidFallback!
browserOptions = InAppBrowserOptions() browserOptions = optionsFallback as! InAppBrowserOptions
browserOptions.parse(options: optionsFallback!)
} }
} }
else { else {
browserOptions = InAppBrowserOptions() browserOptions = options as! InAppBrowserOptions
browserOptions.parse(options: options)
} }
var currentURL = url
if browserOptions.isLocalFile {
let key = SwiftFlutterPlugin.registrar!.lookupKey(forAsset: url.absoluteString)
let assetURL = Bundle.main.url(forResource: key, withExtension: nil)
if assetURL == nil {
result(FlutterError(code: "InAppBrowserFlutterPlugin", message: url.absoluteString + " asset file cannot be found!", details: nil))
return
}
currentURL = assetURL!
}
let storyboard = UIStoryboard(name: WEBVIEW_STORYBOARD, bundle: Bundle(for: InAppBrowserFlutterPlugin.self)) let storyboard = UIStoryboard(name: WEBVIEW_STORYBOARD, bundle: Bundle(for: InAppBrowserFlutterPlugin.self))
let vc = storyboard.instantiateViewController(withIdentifier: WEBVIEW_STORYBOARD_CONTROLLER_ID) let vc = storyboard.instantiateViewController(withIdentifier: WEBVIEW_STORYBOARD_CONTROLLER_ID)
self.webViewControllers[uuid] = vc as? InAppBrowserWebViewController self.webViewControllers[uuid] = vc as? InAppBrowserWebViewController
@ -316,7 +310,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
webViewController.browserOptions = browserOptions webViewController.browserOptions = browserOptions
webViewController.isHidden = browserOptions.hidden webViewController.isHidden = browserOptions.hidden
webViewController.tmpWindow = tmpWindow webViewController.tmpWindow = tmpWindow
webViewController.currentURL = url webViewController.currentURL = currentURL
webViewController.initHeaders = headers webViewController.initHeaders = headers
webViewController.navigationDelegate = self webViewController.navigationDelegate = self
@ -338,6 +332,8 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
else { else {
tmpController.present(webViewController, animated: true, completion: nil) tmpController.present(webViewController, animated: true, completion: nil)
} }
result(true)
} }
public func loadUrl(uuid: String, arguments: NSDictionary, result: @escaping FlutterResult) { public func loadUrl(uuid: String, arguments: NSDictionary, result: @escaping FlutterResult) {
@ -350,7 +346,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
webViewController.loadUrl(url: absoluteUrl, headers: headers) webViewController.loadUrl(url: absoluteUrl, headers: headers)
} }
else { else {
print("url is empty")
result(FlutterError(code: "InAppBrowserFlutterPlugin", message: "url is empty", details: nil)) result(FlutterError(code: "InAppBrowserFlutterPlugin", message: "url is empty", details: nil))
} }
result(true) result(true)
@ -403,17 +398,18 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
} }
} }
func open(inSystem url: URL) { func open(inSystem url: URL, result: @escaping FlutterResult) {
if !UIApplication.shared.canOpenURL(url) { if !UIApplication.shared.canOpenURL(url) {
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "PluginHandleOpenURLNotification"), object: url)) result(FlutterError(code: "InAppBrowserFlutterPlugin", message: url.absoluteString + " cannot be opened!", details: nil))
return
} }
else {
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil) UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else { } else {
UIApplication.shared.openURL(url) UIApplication.shared.openURL(url)
}
} }
result(true)
} }
// This is a helper method for the inject{Script|Style}{Code|File} API calls, which // This is a helper method for the inject{Script|Style}{Code|File} API calls, which
@ -429,7 +425,7 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: []) let jsonData: Data? = try? JSONSerialization.data(withJSONObject: [source], options: [])
let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8) let sourceArrayString = String(data: jsonData!, encoding: String.Encoding.utf8)
if sourceArrayString != nil { if sourceArrayString != nil {
let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.characters.count ?? 0) - 2)) let sourceString: String? = (sourceArrayString! as NSString).substring(with: NSRange(location: 1, length: (sourceArrayString?.count ?? 0) - 2))
let jsToInject = String(format: jsWrapper, sourceString!) let jsToInject = String(format: jsWrapper, sourceString!)
webViewController?.webView?.evaluateJavaScript(jsToInject, completionHandler: {(value, error) in webViewController?.webView?.evaluateJavaScript(jsToInject, completionHandler: {(value, error) in
@ -439,7 +435,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
if error != nil { if error != nil {
let userInfo = (error! as NSError).userInfo let userInfo = (error! as NSError).userInfo
dump(userInfo)
self.onConsoleMessage(uuid: uuid, sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: "ERROR") self.onConsoleMessage(uuid: uuid, sourceURL: (userInfo["WKJavaScriptExceptionSourceURL"] as? URL)?.absoluteString ?? "", lineNumber: userInfo["WKJavaScriptExceptionLineNumber"] as! Int, message: userInfo["WKJavaScriptExceptionMessage"] as! String, messageLevel: "ERROR")
} }
@ -458,7 +453,6 @@ public class SwiftFlutterPlugin: NSObject, FlutterPlugin {
result!(value) result!(value)
} }
} catch let error as NSError { } catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error)) result!(FlutterError(code: "InAppBrowserFlutterPlugin", message: "Failed to load: \(error.localizedDescription)", details: error))
} }

View File

@ -19,6 +19,7 @@
* *
*/ */
import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data'; import 'dart:typed_data';
@ -26,6 +27,7 @@ import 'dart:convert';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:mime/mime.dart';
typedef Future<dynamic> ListenerCallback(MethodCall call); typedef Future<dynamic> ListenerCallback(MethodCall call);
typedef Future<void> JavaScriptHandlerCallback(List<dynamic> arguments); typedef Future<void> JavaScriptHandlerCallback(List<dynamic> arguments);
@ -85,8 +87,7 @@ class _ChannelManager {
static Future<dynamic> _handleMethod(MethodCall call) async { static Future<dynamic> _handleMethod(MethodCall call) async {
String uuid = call.arguments["uuid"]; String uuid = call.arguments["uuid"];
listeners[uuid](call); return await listeners[uuid](call);
return new Future.value("");
} }
static void addListener (String key, ListenerCallback callback) { static void addListener (String key, ListenerCallback callback) {
@ -107,11 +108,14 @@ class InAppBrowser {
String uuid; String uuid;
Map<String, List<JavaScriptHandlerCallback>> javaScriptHandlersMap = HashMap<String, List<JavaScriptHandlerCallback>>(); Map<String, List<JavaScriptHandlerCallback>> javaScriptHandlersMap = HashMap<String, List<JavaScriptHandlerCallback>>();
HttpServer _server;
bool _isOpened = false;
/// ///
InAppBrowser () { InAppBrowser () {
uuid = _uuidGenerator.v4(); uuid = _uuidGenerator.v4();
_ChannelManager.addListener(uuid, _handleMethod); _ChannelManager.addListener(uuid, _handleMethod);
_isOpened = false;
} }
Future<dynamic> _handleMethod(MethodCall call) async { Future<dynamic> _handleMethod(MethodCall call) async {
@ -131,6 +135,8 @@ class InAppBrowser {
onLoadError(url, code, message); onLoadError(url, code, message);
break; break;
case "onExit": case "onExit":
this._closeServer();
this._isOpened = false;
onExit(); onExit();
break; break;
case "shouldOverrideUrlLoading": case "shouldOverrideUrlLoading":
@ -183,133 +189,271 @@ class InAppBrowser {
} }
} }
break; break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
} }
return new Future.value("");
} }
///Opens an [url] in a new [InAppBrowser] instance or the system browser. ///Opens an [url] in a new [InAppBrowser] instance or in the system browser (`openWithSystemBrowser: true`).
///
///**NOTE**: If you open the given [url] with the system browser (`openWithSystemBrowser: true`), you wont be able to use the [InAppBrowser] methods!
/// ///
///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters. The default value is `about:blank`. ///- [url]: The [url] to load. Call [encodeUriComponent()] on this if the [url] contains Unicode characters. The default value is `about:blank`.
/// ///
///- [headers]: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value. ///- [headers]: The additional headers to be used in the HTTP request for this URL, specified as a map from name to value.
/// ///
///- [target]: The target in which to load the [url], an optional parameter that defaults to `_self`.
///
/// - `_self`: Opens in the [InAppBrowser].
/// - `_blank`: Opens in the [InAppBrowser].
/// - `_system`: Opens in the system's web browser.
///
///- [options]: Options for the [InAppBrowser]. ///- [options]: Options for the [InAppBrowser].
/// ///
/// All platforms support: /// - All platforms support:
/// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [shouldOverrideUrlLoading()] event. The default value is `false`. /// - __useShouldOverrideUrlLoading__: Set to `true` to be able to listen at the [shouldOverrideUrlLoading()] event. The default value is `false`.
/// - __useOnLoadResource__: Set to `true` to be able to listen at the [onLoadResource()] event. The default value is `false`. /// - __useOnLoadResource__: Set to `true` to be able to listen at the [onLoadResource()] event. The default value is `false`.
/// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`. /// - __openWithSystemBrowser__: Set to `true` to open the given `url` with the system browser. The default value is `false`.
/// - __userAgent___: Set the custom WebView's user-agent. /// - __isLocalFile__: Set to `true` if the [url] is pointing to a local file (the file must be addded in the `assets` section of your `pubspec.yaml`. See [loadFile()] explanation). The default value is `false`.
/// - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`. /// - __clearCache__: Set to `true` to have all the browser's cache cleared before the new window is opened. The default value is `false`.
/// - __javaScriptCanOpenWindowsAutomatically__: Set to `true` to allow JavaScript open windows without user interaction. The default value is `false`. /// - __userAgent___: Set the custom WebView's user-agent.
/// - __hidden__: Set to `true` to create the browser and load the page, but not show it. The `onLoadStop` event fires when loading is complete. Omit or set to `false` (default) to have the browser open and load normally. /// - __javaScriptEnabled__: Set to `true` to enable JavaScript. The default value is `true`.
/// - __toolbarTop__: Set to `false` to hide the toolbar at the top of the WebView. The default value is `true`. /// - __javaScriptCanOpenWindowsAutomatically__: Set to `true` to allow JavaScript open windows without user interaction. The default value is `false`.
/// - __toolbarTopBackgroundColor__: Set the custom background color of the toolbat at the top. /// - __hidden__: Set to `true` to create the browser and load the page, but not show it. The `onLoadStop` event fires when loading is complete. Omit or set to `false` (default) to have the browser open and load normally.
/// - __hideUrlBar__: Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`. /// - __toolbarTop__: Set to `false` to hide the toolbar at the top of the WebView. The default value is `true`.
/// - __mediaPlaybackRequiresUserGesture__: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`. /// - __toolbarTopBackgroundColor__: Set the custom background color of the toolbat at the top.
/// - __hideUrlBar__: Set to `true` to hide the url bar on the toolbar at the top. The default value is `false`.
/// - __mediaPlaybackRequiresUserGesture__: Set to `true` to prevent HTML5 audio or video from autoplaying. The default value is `true`.
/// ///
/// **Android** supports these additional options: /// - **Android** supports these additional options:
/// ///
/// - __hideTitleBar__: Set to `true` if you want the title should be displayed. The default value is `false`. /// - __hideTitleBar__: Set to `true` if you want the title should be displayed. The default value is `false`.
/// - __closeOnCannotGoBack__: Set to `false` to not close the InAppBrowser when the user click on the back button and the WebView cannot go back to the history. The default value is `true`. /// - __closeOnCannotGoBack__: Set to `false` to not close the InAppBrowser when the user click on the back button and the WebView cannot go back to the history. The default value is `true`.
/// - __clearSessionCache__: Set to `true` to have the session cookie cache cleared before the new window is opened. /// - __clearSessionCache__: Set to `true` to have the session cookie cache cleared before the new window is opened.
/// - __builtInZoomControls__: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`. /// - __builtInZoomControls__: Set to `true` if the WebView should use its built-in zoom mechanisms. The default value is `false`.
/// - __supportZoom__: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`. /// - __supportZoom__: Set to `false` if the WebView should not support zooming using its on-screen zoom controls and gestures. The default value is `true`.
/// - __databaseEnabled__: Set to `true` if you want the database storage API is enabled. The default value is `false`. /// - __databaseEnabled__: Set to `true` if you want the database storage API is enabled. The default value is `false`.
/// - __domStorageEnabled__: Set to `true` if you want the DOM storage API is enabled. The default value is `false`. /// - __domStorageEnabled__: Set to `true` if you want the DOM storage API is enabled. The default value is `false`.
/// - __useWideViewPort__: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. When the value of the setting is false, the layout width is always set to the width of the WebView control in device-independent (CSS) pixels. When the value is true and the page contains the viewport meta tag, the value of the width specified in the tag is used. If the page does not contain the tag or does not provide a width, then a wide viewport will be used. The default value is `true`. /// - __useWideViewPort__: Set to `true` if the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. When the value of the setting is false, the layout width is always set to the width of the WebView control in device-independent (CSS) pixels. When the value is true and the page contains the viewport meta tag, the value of the width specified in the tag is used. If the page does not contain the tag or does not provide a width, then a wide viewport will be used. The default value is `true`.
/// - __safeBrowsingEnabled__: Set to `true` if you want the Safe Browsing is enabled. Safe Browsing allows WebView to protect against malware and phishing attacks by verifying the links. The default value is `true`. /// - __safeBrowsingEnabled__: Set to `true` if you want the Safe Browsing is enabled. Safe Browsing allows WebView to protect against malware and phishing attacks by verifying the links. The default value is `true`.
/// - __progressBar__: Set to `false` to hide the progress bar at the bottom of the toolbar at the top. The default value is `true`. /// - __progressBar__: Set to `false` to hide the progress bar at the bottom of the toolbar at the top. The default value is `true`.
/// ///
/// **iOS** supports these additional options: /// - **iOS** supports these additional options:
/// ///
/// - __disallowOverScroll__: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`. /// - __disallowOverScroll__: Set to `true` to disable the bouncing of the WebView when the scrolling has reached an edge of the content. The default value is `false`.
/// - __toolbarBottom__: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`. /// - __toolbarBottom__: Set to `false` to hide the toolbar at the bottom of the WebView. The default value is `true`.
/// - __toolbarBottomBackgroundColor__: Set the custom background color of the toolbat at the bottom. /// - __toolbarBottomBackgroundColor__: Set the custom background color of the toolbat at the bottom.
/// - __toolbarBottomTranslucent__: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`. /// - __toolbarBottomTranslucent__: Set to `true` to set the toolbar at the bottom translucent. The default value is `true`.
/// - __closeButtonCaption__: Set the custom text for the close button. /// - __closeButtonCaption__: Set the custom text for the close button.
/// - __closeButtonColor__: Set the custom color for the close button. /// - __closeButtonColor__: Set the custom color for the close button.
/// - __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. /// - __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. /// - __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.
/// - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`. /// - __enableViewportScale__: Set to `true` to allow a viewport meta tag to either disable or restrict the range of user scaling. The default value is `false`.
/// - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`. /// - __suppressesIncrementalRendering__: Set to `true` if you want the WebView suppresses content rendering until it is fully loaded into memory.. The default value is `false`.
/// - __allowsAirPlayForMediaPlayback__: Set to `true` to allow AirPlay. The default value is `true`. /// - __allowsAirPlayForMediaPlayback__: Set to `true` to allow AirPlay. The default value is `true`.
/// - __allowsBackForwardNavigationGestures__: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`. /// - __allowsBackForwardNavigationGestures__: Set to `true` to allow the horizontal swipe gestures trigger back-forward list navigations. The default value is `true`.
/// - __allowsLinkPreview__: Set to `true` to allow that pressing on a link displays a preview of the destination for the link. The default value is `true`. /// - __allowsLinkPreview__: Set to `true` to allow that pressing on a link displays a preview of the destination for the link. The default value is `true`.
/// - __ignoresViewportScaleLimits__: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent. The ignoresViewportScaleLimits property overrides the `user-scalable` HTML property in a webpage. The default value is `false`. /// - __ignoresViewportScaleLimits__: Set to `true` if you want that the WebView should always allow scaling of the webpage, regardless of the author's intent. The ignoresViewportScaleLimits property overrides the `user-scalable` HTML property in a webpage. The default value is `false`.
/// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`. /// - __allowsInlineMediaPlayback__: Set to `true` to allow HTML5 media playback to appear inline within the screen layout, using browser-supplied controls rather than native controls. For this to work, add the `webkit-playsinline` attribute to any `<video>` elements. The default value is `false`.
/// - __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`. /// - __allowsPictureInPictureMediaPlayback__: Set to `true` to allow HTML5 videos play picture-in-picture. The default value is `true`.
/// - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`. /// - __spinner__: Set to `false` to hide the spinner when the WebView is loading a page. The default value is `true`.
Future<void> open({String url = "about:blank", Map<String, String> headers = const {}, String target = "_self", Map<String, dynamic> options = const {}}) async { Future<void> open({String url = "about:blank", Map<String, String> headers = const {}, Map<String, dynamic> options = const {}}) async {
this._throwIsAlreadyOpened(message: 'Cannot open $url!');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
args.putIfAbsent('target', () => target);
args.putIfAbsent('options', () => options); args.putIfAbsent('options', () => options);
args.putIfAbsent('useChromeSafariBrowser', () => false); args.putIfAbsent('useChromeSafariBrowser', () => false);
await _ChannelManager.channel.invokeMethod('open', args);
this._isOpened = true;
}
///This is a static method that opens an [url] in the system browser.
///This has the same behaviour of an [InAppBrowser] instance calling the [open()] method with option `openWithSystemBrowser: true`.
static Future<void> openWithSystemBrowser(String url) async {
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => "");
args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => {});
args.putIfAbsent('options', () => {"openWithSystemBrowser": true});
args.putIfAbsent('useChromeSafariBrowser', () => false);
return await _ChannelManager.channel.invokeMethod('open', args); return await _ChannelManager.channel.invokeMethod('open', args);
} }
Future<void> _startServer({int port = 8080}) async {
this._closeServer();
var completer = new Completer();
runZoned(() {
HttpServer.bind('127.0.0.1', port).then((server) {
print('Server running at http://127.0.0.1:' + 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;
}
Future<void> _closeServer() async {
if (this._server != null) {
final port = this._server.port;
await this._server.close(force: true);
print('Server running at http://127.0.0.1:$port closed');
this._server = null;
}
}
///Serve the [assetFilePath] from Flutter assets on http://localhost:[port]/. It is similar to [InAppBrowser.open()] with option `isLocalFile: true`, but it starts a server.
///
///**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> openOnLocalhost(String assetFilePath, {int port = 8080, Map<String, String> headers = const {}, Map<String, dynamic> options = const {}}) async {
this._throwIsAlreadyOpened(message: 'Cannot open $assetFilePath!');
await this._startServer(port: port);
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => 'http://localhost:${this._server.port}/' + assetFilePath);
args.putIfAbsent('headers', () => headers);
options["isLocalFile"] = false;
args.putIfAbsent('options', () => options);
args.putIfAbsent('useChromeSafariBrowser', () => false);
await _ChannelManager.channel.invokeMethod('open', args);
this._isOpened = true;
}
///Loads the given [url] with optional [headers] specified as a map from name to value. ///Loads the given [url] with optional [headers] specified as a map from name to value.
Future<void> loadUrl(String url, {Map<String, String> headers = const {}}) async { Future<void> loadUrl(String url, {Map<String, String> headers = const {}}) async {
this._throwIsNotOpened(message: 'Cannot laod $url!');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headers); args.putIfAbsent('headers', () => headers);
return await _ChannelManager.channel.invokeMethod('loadUrl', args); await _ChannelManager.channel.invokeMethod('loadUrl', args);
}
///Loads the given [assetFilePath] with optional [headers] specified as a map from name to value.
///
///To be able to load your local files (assets, js, css, etc.), you need to add them in the `assets` section of the `pubspec.yaml` file, otherwise they cannot be found!
///
///Example of a `pubspec.yaml` file:
///```yaml
///...
///
///# The following section is specific to Flutter.
///flutter:
///
/// # The following line ensures that the Material Icons font is
/// # included with your application, so that you can use the icons in
/// # the material Icons class.
/// uses-material-design: true
///
/// assets:
/// - assets/index.html
/// - assets/css/
/// - assets/images/
///
///...
///```
///Example of a `main.dart` file:
///```dart
///...
///inAppBrowser.loadFile("assets/index.html");
///...
///```
Future<void> loadFile(String assetFilePath, {Map<String, String> headers = const {}}) async {
this._throwIsNotOpened(message: 'Cannot laod $assetFilePath!');
Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('url', () => assetFilePath);
args.putIfAbsent('headers', () => headers);
await _ChannelManager.channel.invokeMethod('loadFile', args);
} }
///Displays an [InAppBrowser] window that was opened hidden. Calling this has no effect if the [InAppBrowser] was already visible. ///Displays an [InAppBrowser] window that was opened hidden. Calling this has no effect if the [InAppBrowser] was already visible.
Future<void> show() async { Future<void> show() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('show', args); await _ChannelManager.channel.invokeMethod('show', args);
} }
///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden. ///Hides the [InAppBrowser] window. Calling this has no effect if the [InAppBrowser] was already hidden.
Future<void> hide() async { Future<void> hide() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('hide', args); await _ChannelManager.channel.invokeMethod('hide', args);
} }
///Closes the [InAppBrowser] window. ///Closes the [InAppBrowser] window.
Future<void> close() async { Future<void> close() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('close', args); await _ChannelManager.channel.invokeMethod('close', args);
} }
///Reloads the [InAppBrowser] window. ///Reloads the [InAppBrowser] window.
Future<void> reload() async { Future<void> reload() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('reload', args); await _ChannelManager.channel.invokeMethod('reload', args);
} }
///Goes back in the history of the [InAppBrowser] window. ///Goes back in the history of the [InAppBrowser] window.
Future<void> goBack() async { Future<void> goBack() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('goBack', args); await _ChannelManager.channel.invokeMethod('goBack', args);
} }
///Goes forward in the history of the [InAppBrowser] window. ///Goes forward in the history of the [InAppBrowser] window.
Future<void> goForward() async { Future<void> goForward() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('goForward', args); await _ChannelManager.channel.invokeMethod('goForward', args);
} }
///Check if the Web View of the [InAppBrowser] instance is in a loading state. ///Check if the Web View of the [InAppBrowser] instance is in a loading state.
Future<bool> isLoading() async { Future<bool> isLoading() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('isLoading', args); return await _ChannelManager.channel.invokeMethod('isLoading', args);
@ -317,48 +461,54 @@ class InAppBrowser {
///Stops the Web View of the [InAppBrowser] instance from loading. ///Stops the Web View of the [InAppBrowser] instance from loading.
Future<void> stopLoading() async { Future<void> stopLoading() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('stopLoading', args); await _ChannelManager.channel.invokeMethod('stopLoading', args);
} }
///Check if the Web View of the [InAppBrowser] instance is hidden. ///Check if the Web View of the [InAppBrowser] instance is hidden.
Future<bool> isHidden() async { Future<bool> isHidden() async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
return await _ChannelManager.channel.invokeMethod('isHidden', args); 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`) ///Injects JavaScript code into the [InAppBrowser] window and returns the result of the evaluation.
Future<String> injectScriptCode(String source) async { Future<String> injectScriptCode(String source) async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('source', () => source); args.putIfAbsent('source', () => source);
return await _ChannelManager.channel.invokeMethod('injectScriptCode', args); return await _ChannelManager.channel.invokeMethod('injectScriptCode', args);
} }
///Injects a JavaScript file into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) ///Injects a JavaScript file into the [InAppBrowser] window.
Future<void> injectScriptFile(String urlFile) async { Future<void> injectScriptFile(String urlFile) async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('urlFile', () => urlFile); args.putIfAbsent('urlFile', () => urlFile);
return await _ChannelManager.channel.invokeMethod('injectScriptFile', args); await _ChannelManager.channel.invokeMethod('injectScriptFile', args);
} }
///Injects CSS into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) ///Injects CSS into the [InAppBrowser] window.
Future<void> injectStyleCode(String source) async { Future<void> injectStyleCode(String source) async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('source', () => source); args.putIfAbsent('source', () => source);
return await _ChannelManager.channel.invokeMethod('injectStyleCode', args); await _ChannelManager.channel.invokeMethod('injectStyleCode', args);
} }
///Injects a CSS file into the [InAppBrowser] window. (Only available when the target is set to `_blank` or to `_self`) ///Injects a CSS file into the [InAppBrowser] window.
Future<void> injectStyleFile(String urlFile) async { Future<void> injectStyleFile(String urlFile) async {
this._throwIsNotOpened();
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('urlFile', () => urlFile); args.putIfAbsent('urlFile', () => urlFile);
return await _ChannelManager.channel.invokeMethod('injectStyleFile', args); await _ChannelManager.channel.invokeMethod('injectStyleFile', args);
} }
///Adds/Appends a JavaScript message handler [callback] ([JavaScriptHandlerCallback]) that listen to post messages sent from JavaScript by the handler with name [handlerName]. ///Adds/Appends a JavaScript message handler [callback] ([JavaScriptHandlerCallback]) that listen to post messages sent from JavaScript by the handler with name [handlerName].
@ -419,7 +569,7 @@ class InAppBrowser {
/// ///
///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`. ///**NOTE**: In order to be able to listen this event, you need to set `useOnLoadResource` option to `true`.
/// ///
///**NOTE only for iOS**: In some cases, the [response.data] of a [response] with `text/html` encoding could be empty. ///**NOTE only for iOS**: In some cases, the [response.data] of a [response] with `text/assets` encoding could be empty.
void onLoadResource(WebResourceResponse response, WebResourceRequest request) { void onLoadResource(WebResourceResponse response, WebResourceRequest request) {
} }
@ -429,6 +579,22 @@ class InAppBrowser {
} }
///Returns `true` if the browser is opened, otherwise `false`.
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.']);
}
}
} }
///ChromeSafariBrowser class. ///ChromeSafariBrowser class.
@ -440,12 +606,14 @@ class InAppBrowser {
class ChromeSafariBrowser { class ChromeSafariBrowser {
String uuid; String uuid;
InAppBrowser browserFallback; InAppBrowser browserFallback;
bool _isOpened = false;
///Initialize the [ChromeSafariBrowser] instance with a [InAppBrowser] fallback instance or `null`. ///Initialize the [ChromeSafariBrowser] instance with a [InAppBrowser] fallback instance or `null`.
ChromeSafariBrowser (bf) { ChromeSafariBrowser (bf) {
uuid = _uuidGenerator.v4(); uuid = _uuidGenerator.v4();
browserFallback = bf; browserFallback = bf;
_ChannelManager.addListener(uuid, _handleMethod); _ChannelManager.addListener(uuid, _handleMethod);
_isOpened = false;
} }
Future<dynamic> _handleMethod(MethodCall call) async { Future<dynamic> _handleMethod(MethodCall call) async {
@ -458,9 +626,11 @@ class ChromeSafariBrowser {
break; break;
case "onChromeSafariBrowserClosed": case "onChromeSafariBrowserClosed":
onClosed(); onClosed();
this._isOpened = false;
break; break;
default:
throw UnimplementedError("Unimplemented ${call.method} method");
} }
return new Future.value("");
} }
///Opens an [url] in a new [ChromeSafariBrowser] instance or the system browser. ///Opens an [url] in a new [ChromeSafariBrowser] instance or the system browser.
@ -491,16 +661,17 @@ class ChromeSafariBrowser {
///- __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. ///- __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. ///- __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 { Future<void> open(String url, {Map<String, dynamic> options = const {}, Map<String, String> headersFallback = const {}, Map<String, dynamic> optionsFallback = const {}}) async {
this._throwIsAlreadyOpened(message: 'Cannot open $url!');
Map<String, dynamic> args = <String, dynamic>{}; Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent('uuid', () => uuid); args.putIfAbsent('uuid', () => uuid);
args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : ''); args.putIfAbsent('uuidFallback', () => (browserFallback != null) ? browserFallback.uuid : '');
args.putIfAbsent('url', () => url); args.putIfAbsent('url', () => url);
args.putIfAbsent('headers', () => headersFallback); args.putIfAbsent('headers', () => headersFallback);
args.putIfAbsent('target', () => "");
args.putIfAbsent('options', () => options); args.putIfAbsent('options', () => options);
args.putIfAbsent('optionsFallback', () => optionsFallback); args.putIfAbsent('optionsFallback', () => optionsFallback);
args.putIfAbsent('useChromeSafariBrowser', () => true); args.putIfAbsent('useChromeSafariBrowser', () => true);
return await _ChannelManager.channel.invokeMethod('open', args); await _ChannelManager.channel.invokeMethod('open', args);
this._isOpened = true;
} }
///Event fires when the [ChromeSafariBrowser] is opened. ///Event fires when the [ChromeSafariBrowser] is opened.
@ -517,4 +688,20 @@ class ChromeSafariBrowser {
void onClosed() { 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.']);
}
}
} }

View File

@ -1,6 +1,6 @@
name: flutter_inappbrowser name: flutter_inappbrowser
description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser). description: A Flutter plugin that allows you to open an in-app browser window. (inspired by the popular cordova-plugin-inappbrowser).
version: 0.3.2 version: 0.4.0
author: Lorenzo Pichilli <pichillilorenzo@gmail.com> author: Lorenzo Pichilli <pichillilorenzo@gmail.com>
homepage: https://github.com/pichillilorenzo/flutter_inappbrowser homepage: https://github.com/pichillilorenzo/flutter_inappbrowser
@ -11,6 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
uuid: ^1.0.3 uuid: ^1.0.3
mime: ^0.9.6+2
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec # following page: https://www.dartlang.org/tools/pub/pubspec