From 7ea59ee988c44885d9533c86a125a970c2391a39 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 12 Oct 2023 13:08:48 -0400 Subject: [PATCH 01/14] Add new mode and query methods --- .../CHANGELOG.md | 8 +++ .../lib/src/types.dart | 6 ++- .../lib/src/url_launcher_platform.dart | 26 +++++++++- .../pubspec.yaml | 2 +- .../test/url_launcher_platform_test.dart | 51 +++++++++++++++++++ 5 files changed, 90 insertions(+), 3 deletions(-) diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index f5523ced4717..04facb782b06 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.2.0 + +* Adds a new `inAppBrowserView` launch mode, to distinguish in-app browser + views (such as Android Custom Tabs or SFSafariViewController) from simple + web views. +* Adds `supportsMode` and `supportsCloseForMode` to query platform support for + launching and closing with various modes. + ## 2.1.5 * Updates documentation to mention support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/src/types.dart b/packages/url_launcher/url_launcher_platform_interface/lib/src/types.dart index ca9d8e1c9175..610df29f7091 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/src/types.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/src/types.dart @@ -13,9 +13,13 @@ enum PreferredLaunchMode { /// implementation. platformDefault, - /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, Safari View Controller). + /// Loads the URL in an in-app web view (e.g., Android WebView). inAppWebView, + /// Loads the URL in an in-app browser view (e.g., Android Custom Tabs, + /// SFSafariViewController). + inAppBrowserView, + /// Passes the URL to the OS to be handled by another application. externalApplication, diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart b/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart index 8928d4249e90..b6982bc6e313 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart @@ -9,6 +9,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../link.dart'; import '../method_channel_url_launcher.dart'; import '../url_launcher_platform_interface.dart'; +import 'types.dart'; /// The interface that implementations of url_launcher must implement. /// @@ -87,8 +88,31 @@ abstract class UrlLauncherPlatform extends PlatformInterface { ); } - /// Closes the WebView, if one was opened earlier by [launch]. + /// Closes the web view, if one was opened earlier by [launchUrl]. + /// + /// This will only work if the launch mode (the actual launch mode used, + /// not the requested launch mode, which may not match if [supportsMode] is + /// false for the requested mode) was one for which [supportsCloseForMode] + /// returns true. Future closeWebView() { throw UnimplementedError('closeWebView() has not been implemented.'); } + + /// Returns true if the given launch mode is supported by the current + /// implementation. + /// + /// Clients are not required to query this, as implementations are strongly + /// encouraged to automatically fall back to other modes if a launch is + /// requested using an unsupported mode (matching historical behavior of the + /// plugin, and thus maximizing compatibility with existing code). + Future supportsMode(PreferredLaunchMode mode) { + return Future.value(mode == PreferredLaunchMode.platformDefault); + } + + /// Returns true if the given launch mode can be closed with [closeWebView]. + Future supportsCloseForMode(PreferredLaunchMode mode) { + // This is the historical documented behavior, so default to that for + // compatibility. + return Future.value(mode == PreferredLaunchMode.inAppWebView); + } } diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index 5aa135fbc315..a999793f58e9 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.1.5 +version: 2.2.0 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher_platform_interface/test/url_launcher_platform_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/url_launcher_platform_test.dart index f764f679f96d..56920596eb77 100644 --- a/packages/url_launcher/url_launcher_platform_interface/test/url_launcher_platform_test.dart +++ b/packages/url_launcher/url_launcher_platform_interface/test/url_launcher_platform_test.dart @@ -118,4 +118,55 @@ void main() { expect(launcher.headers['foo'], 'bar'); expect(launcher.webOnlyWindowName, 'a_name'); }); + + test('supportsMode defaults to true for platform default', () async { + final UrlLauncherPlatform launcher = CapturingUrlLauncher(); + + expect( + await launcher.supportsMode(PreferredLaunchMode.platformDefault), true); + }); + + test('supportsMode defaults to false for all specific values', () async { + final UrlLauncherPlatform launcher = CapturingUrlLauncher(); + + expect(await launcher.supportsMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), false); + }); + + test('supportsCloseForMode defaults to true for in-app web views', () async { + final UrlLauncherPlatform launcher = CapturingUrlLauncher(); + + expect( + await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), + true); + }); + + test('supportsCloseForMode defaults to false for all other values', () async { + final UrlLauncherPlatform launcher = CapturingUrlLauncher(); + + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher.supportsCloseForMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + }); } From 50efebced5c092aa8837ea0667f87c0418f0b733 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 12 Oct 2023 13:10:00 -0400 Subject: [PATCH 02/14] Pathify --- packages/url_launcher/url_launcher/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher/pubspec.yaml | 5 +++++ .../url_launcher/url_launcher_android/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_android/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_ios/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_ios/pubspec.yaml | 5 +++++ .../url_launcher/url_launcher_linux/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_linux/pubspec.yaml | 5 +++++ .../url_launcher/url_launcher_macos/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_macos/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_web/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_web/pubspec.yaml | 5 +++++ .../url_launcher/url_launcher_windows/example/pubspec.yaml | 5 +++++ packages/url_launcher/url_launcher_windows/pubspec.yaml | 5 +++++ 14 files changed, 70 insertions(+) diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index e09df486b417..f61bda8b076d 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -29,3 +29,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 9feae723f2f1..ed4b33f8ea5a 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -50,3 +50,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index e8aee1779b0d..15ccdee82476 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -28,3 +28,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index f5db21c93b4a..2ac42aee6efb 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -34,3 +34,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index d755c553f6af..793cde0144d5 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -27,3 +27,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 8b45ed750549..43d58cb6f3cd 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -33,3 +33,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index 0dd4a4cf8a3f..ff6a34aabdf6 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -26,3 +26,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 2649beeecb99..65e615ed2055 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -31,3 +31,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index 0ad239027827..bbe82f8c5054 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -26,3 +26,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 23d5f6a2f474..45e09ee396d0 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -33,3 +33,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index 9915164471a0..adc6f4da5f32 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -19,3 +19,8 @@ dev_dependencies: url_launcher_platform_interface: ^2.0.3 url_launcher_web: path: ../ + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index fce7da421b1b..8c6f3e65b61d 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -32,3 +32,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index 77106bc48a74..b9e23486d631 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -26,3 +26,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index 6b17b9d7c55b..ddc595ca9076 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -32,3 +32,8 @@ topics: - os-integration - url-launcher - urls + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} From d155a015b74ee0f2033e2128140a3ba46b66fc41 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 12 Oct 2023 13:21:52 -0400 Subject: [PATCH 03/14] Add desktop implementations of new methods --- .../lib/url_launcher_linux.dart | 12 ++++++ .../test/url_launcher_linux_test.dart | 43 ++++++++++++++++++- .../lib/url_launcher_macos.dart | 12 ++++++ .../test/url_launcher_macos_test.dart | 41 ++++++++++++++++++ .../lib/url_launcher_windows.dart | 12 ++++++ .../test/url_launcher_windows_test.dart | 39 +++++++++++++++++ 6 files changed, 158 insertions(+), 1 deletion(-) diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart index 87ef3142e3f6..286ac923ce9b 100644 --- a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart +++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart @@ -51,4 +51,16 @@ class UrlLauncherLinux extends UrlLauncherPlatform { }, ).then((bool? value) => value ?? false); } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart index 4e62cc446199..c7e6c8e328c6 100644 --- a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart +++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart @@ -10,7 +10,7 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$UrlLauncherLinux', () { + group('UrlLauncherLinux', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_linux'); final List log = []; @@ -142,6 +142,47 @@ void main() { expect(launched, false); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), + false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherLinux launcher = UrlLauncherLinux(); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); }); } diff --git a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart index 55c07b798cd8..1d229737c34c 100644 --- a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart +++ b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart @@ -57,6 +57,18 @@ class UrlLauncherMacOS extends UrlLauncherPlatform { return result.value; } + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } + Exception _getInvalidUrlException(String url) { // TODO(stuartmorgan): Make this an actual ArgumentError. This should be // coordinated across all platforms as a breaking change to have them all diff --git a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart index a7147af7749b..e9cc3c6c6dc9 100644 --- a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart +++ b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart @@ -106,6 +106,47 @@ void main() { throwsA(isA())); }); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), + false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); }); } diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart index 41c403e56f8e..790a45149be8 100644 --- a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -45,4 +45,16 @@ class UrlLauncherWindows extends UrlLauncherPlatform { // Failure is handled via a PlatformException from `launchUrl`. return true; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.platformDefault || + mode == PreferredLaunchMode.externalApplication; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart index 7f48f64fa92c..0be939bad19b 100644 --- a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart +++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart @@ -77,6 +77,45 @@ void main() { expect(api.argument, 'http://example.com/'); }); }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), + false); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), false); + }); + }); + + test('supportsCloseForMode returns false', () async { + final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.platformDefault), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + }); } class _FakeUrlLauncherApi implements UrlLauncherApi { From 177207db1aee05800a109457e2d251758d693435 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 12 Oct 2023 14:28:45 -0400 Subject: [PATCH 04/14] App-facing updates --- .../url_launcher/url_launcher/CHANGELOG.md | 14 +++++ .../url_launcher/example/lib/main.dart | 6 +-- .../url_launcher/lib/src/link.dart | 2 +- .../url_launcher/lib/src/type_conversion.dart | 2 + .../url_launcher/lib/src/types.dart | 5 +- .../lib/src/url_launcher_string.dart | 3 +- .../lib/src/url_launcher_uri.dart | 51 +++++++++++-------- .../url_launcher/url_launcher/pubspec.yaml | 2 +- .../url_launcher/test/link_test.dart | 2 +- .../mocks/mock_url_launcher_platform.dart | 12 +++++ .../test/src/url_launcher_uri_test.dart | 36 +++++++++++++ 11 files changed, 105 insertions(+), 30 deletions(-) diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index cebc8177291b..75e60861227c 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,17 @@ +## 6.2.0 + +* Adds `supportsLaunchMode` for checking whether the current platform supports a + given launch mode, to allow clients that will only work with specific modes + to avoid fallback to a different mode. +* Adds `supportsCloseForLaunchMode` to allow checking programatically if a + launched URL will be able to be closed. Previously the documented behvaior was + that it worked only with the `inAppWebView` launch mode, but this is no longer + true on all platforms with the addition of `inAppBrowserView`. +* Updates the documention for `launchUrl` to clarify that clients should not + rely on any specific behavior of the `platformDefault` launch mode. Changes + to the handling of `platformDefault`, such as Android's recent change from + `inAppBrowserView` to the new `inAppBrowserView`, are not considered breaking. + ## 6.1.14 * Updates documentation to mention support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index 57a0ce9ef470..816eb29c58e9 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -99,7 +99,7 @@ class _MyHomePageState extends State { } } - Future _launchUniversalLinkIos(Uri url) async { + Future _launchUniversalLinkIOS(Uri url) async { final bool nativeAppLaunchSucceeded = await launchUrl( url, mode: LaunchMode.externalNonBrowserApplication, @@ -107,7 +107,7 @@ class _MyHomePageState extends State { if (!nativeAppLaunchSucceeded) { await launchUrl( url, - mode: LaunchMode.inAppWebView, + mode: LaunchMode.inAppBrowserView, ); } } @@ -198,7 +198,7 @@ class _MyHomePageState extends State { const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchUniversalLinkIos(toLaunch); + _launched = _launchUniversalLinkIOS(toLaunch); }), child: const Text( 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), diff --git a/packages/url_launcher/url_launcher/lib/src/link.dart b/packages/url_launcher/url_launcher/lib/src/link.dart index cea6845b12ea..b5c47403097f 100644 --- a/packages/url_launcher/url_launcher/lib/src/link.dart +++ b/packages/url_launcher/url_launcher/lib/src/link.dart @@ -126,7 +126,7 @@ class DefaultLinkDelegate extends StatelessWidget { success = await launchUrl( url, mode: _useWebView - ? LaunchMode.inAppWebView + ? LaunchMode.inAppBrowserView : LaunchMode.externalApplication, ); } on PlatformException { diff --git a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart index 970f04dced57..3169e25dfa7a 100644 --- a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart +++ b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart @@ -22,6 +22,8 @@ PreferredLaunchMode convertLaunchMode(LaunchMode mode) { switch (mode) { case LaunchMode.platformDefault: return PreferredLaunchMode.platformDefault; + case LaunchMode.inAppBrowserView: + return PreferredLaunchMode.inAppBrowserView; case LaunchMode.inAppWebView: return PreferredLaunchMode.inAppWebView; case LaunchMode.externalApplication: diff --git a/packages/url_launcher/url_launcher/lib/src/types.dart b/packages/url_launcher/url_launcher/lib/src/types.dart index 359e293ef82e..2bf56e1b5d8b 100644 --- a/packages/url_launcher/url_launcher/lib/src/types.dart +++ b/packages/url_launcher/url_launcher/lib/src/types.dart @@ -14,9 +14,12 @@ enum LaunchMode { /// implementation. platformDefault, - /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, Safari View Controller). + /// Loads the URL in an in-app web view (e.g., Android WebView). inAppWebView, + /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, SFSafariViewController). + inAppBrowserView, + /// Passes the URL to the OS to be handled by another application. externalApplication, diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart index 45193ff17cb3..ca0bcc6109d0 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart @@ -25,7 +25,8 @@ Future launchUrlString( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if (mode == LaunchMode.inAppWebView && + if ((mode == LaunchMode.inAppWebView || + mode == LaunchMode.inAppBrowserView) && !(urlString.startsWith('https:') || urlString.startsWith('http:'))) { throw ArgumentError.value(urlString, 'urlString', 'To use an in-app web view, you must provide an http(s) URL.'); diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart index b3ce6c279f39..062621656932 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart @@ -11,25 +11,13 @@ import 'type_conversion.dart'; /// Passes [url] to the underlying platform for handling. /// -/// [mode] support varies significantly by platform: -/// - [LaunchMode.platformDefault] is supported on all platforms: -/// - On iOS and Android, this treats web URLs as -/// [LaunchMode.inAppWebView], and all other URLs as -/// [LaunchMode.externalApplication]. -/// - On Windows, macOS, and Linux this behaves like -/// [LaunchMode.externalApplication]. -/// - On web, this uses `webOnlyWindowName` for web URLs, and behaves like -/// [LaunchMode.externalApplication] for any other content. -/// - [LaunchMode.inAppWebView] is currently only supported on iOS and -/// Android. If a non-web URL is passed with this mode, an [ArgumentError] -/// will be thrown. -/// - [LaunchMode.externalApplication] is supported on all platforms. -/// On iOS, this should be used in cases where sharing the cookies of the -/// user's browser is important, such as SSO flows, since Safari View -/// Controller does not share the browser's context. -/// - [LaunchMode.externalNonBrowserApplication] is supported on iOS 10+. -/// This setting is used to require universal links to open in a non-browser -/// application. +/// [mode] support varies significantly by platform. Clients can use +/// [supportsLaunchMode] to query for support, but platforms will fall back to +/// other modes if the requested mode is not supported, so checking is not +/// required. The default behavior of [LaunchMode.platformDefault] is up to each +/// platform, and its behavior for a given platform may change over time as new +/// modes are supported, so clients that want a specific mode should request it +/// rather than rely on any currently observed default behavior. /// /// For web, [webOnlyWindowName] specifies a target for the launch. This /// supports the standard special link target names. For example: @@ -45,7 +33,8 @@ Future launchUrl( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if (mode == LaunchMode.inAppWebView && + if ((mode == LaunchMode.inAppWebView || + mode == LaunchMode.inAppBrowserView) && !(url.scheme == 'https' || url.scheme == 'http')) { throw ArgumentError.value(url, 'url', 'To use an in-app web view, you must provide an http(s) URL.'); @@ -81,8 +70,26 @@ Future canLaunchUrl(Uri url) async { /// Closes the current in-app web view, if one was previously opened by /// [launchUrl]. /// -/// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this -/// call will have no effect. +/// This works only if [supportsCloseForLaunchMode] returns true for the mode +/// that was used by [launchUrl]. Future closeInAppWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } + +/// Returns true if [mode] is supported by the current platform implementation. +/// +/// Calling [launchUrl] with an unsupported mode will fall back to a supported +/// mode, so calling this method is only necessary for cases where the caller +/// needs to know which mode will be used. +Future supportsLaunchMode(PreferredLaunchMode mode) { + return UrlLauncherPlatform.instance.supportsMode(mode); +} + +/// Returns true if [closeInAppWebView] is supported for [mode] in the current +/// platform implementation. +/// +/// If this returns false, [closeInAppWebView] will not work when launching +/// URLs with [mode]. +Future supportsCloseForLaunchMode(PreferredLaunchMode mode) { + return UrlLauncherPlatform.instance.supportsMode(mode); +} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index ed4b33f8ea5a..fb3e46e4a2cb 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.14 +version: 6.2.0 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index 1585420d9b29..052ca2556e39 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -86,7 +86,7 @@ void main() { mock ..setLaunchExpectations( url: 'http://example.com/foobar', - launchMode: PreferredLaunchMode.inAppWebView, + launchMode: PreferredLaunchMode.inAppBrowserView, universalLinksOnly: false, enableJavaScript: true, enableDomStorage: true, diff --git a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart index 05c8b5e4b375..fc0181d4a4ed 100644 --- a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart +++ b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart @@ -107,4 +107,16 @@ class MockUrlLauncher extends Fake Future closeWebView() async { closeWebViewCalled = true; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + launchMode = mode; + return response!; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + launchMode = mode; + return response!; + } } diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart index d71d07fc8fc4..0e6e76c38ea4 100644 --- a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart +++ b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart @@ -248,4 +248,40 @@ void main() { expect(await launchUrl(emailLaunchUrl), isTrue); }); }); + + group('supportsLaunchMode', () { + test('handles returning true', () async { + const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; + mock.setResponse(true); + + expect(await supportsLaunchMode(mode), true); + expect(mock.launchMode, mode); + }); + + test('handles returning false', () async { + const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; + mock.setResponse(false); + + expect(await supportsLaunchMode(mode), false); + expect(mock.launchMode, mode); + }); + }); + + group('supportsCloseForLaunchMode', () { + test('handles returning true', () async { + const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; + mock.setResponse(true); + + expect(await supportsCloseForLaunchMode(mode), true); + expect(mock.launchMode, mode); + }); + + test('handles returning false', () async { + const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; + mock.setResponse(false); + + expect(await supportsCloseForLaunchMode(mode), false); + expect(mock.launchMode, mode); + }); + }); } From 49c459488e07843dc2b31ffe9da04af9d6d2a3b6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 12 Oct 2023 16:36:05 -0400 Subject: [PATCH 05/14] Implement for Android --- .../flutter/plugins/urllauncher/Messages.java | 78 +++++-- .../plugins/urllauncher/UrlLauncher.java | 15 +- .../plugins/urllauncher/UrlLauncherTest.java | 70 ++++-- .../lib/src/messages.g.dart | 54 +++-- .../lib/url_launcher_android.dart | 78 ++++++- .../pigeons/messages.dart | 8 +- .../test/url_launcher_android_test.dart | 218 +++++++++++++++++- 7 files changed, 444 insertions(+), 77 deletions(-) diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java index eab0d87f5b35..61506d826a6f 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.0), do not edit directly. +// Autogenerated from Pigeon (v10.1.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.urllauncher; @@ -16,6 +16,10 @@ import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @@ -31,7 +35,8 @@ public static class FlutterError extends RuntimeException { /** The error details. Must be a datatype supported by the api codec. */ public final Object details; - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) + { super(message); this.code = code; this.details = details; @@ -50,7 +55,7 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { errorList.add(exception.toString()); errorList.add(exception.getClass().getSimpleName()); errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); } return errorList; } @@ -58,7 +63,7 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { /** * Configuration options for an in-app WebView. * - *

Generated class from Pigeon that represents data sent in messages. + * Generated class from Pigeon that represents data sent in messages. */ public static final class WebViewOptions { private @NonNull Boolean enableJavaScript; @@ -185,14 +190,20 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface UrlLauncherApi { /** Returns true if the URL can definitely be launched. */ - @NonNull + @NonNull Boolean canLaunchUrl(@NonNull String url); /** Opens the URL externally, returning true if successful. */ - @NonNull + @NonNull Boolean launchUrl(@NonNull String url, @NonNull Map headers); - /** Opens the URL in an in-app WebView, returning true if it opens successfully. */ - @NonNull - Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options); + /** + * Opens the URL in an in-app Custom Tab or WebView, returning true if it + * opens successfully. + */ + @NonNull + Boolean openUrlInApp(@NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options); + + @NonNull + Boolean supportsCustomTabs(); /** Closes the view opened by [openUrlInSafariViewController]. */ void closeWebView(); @@ -200,12 +211,12 @@ public interface UrlLauncherApi { static @NonNull MessageCodec getCodec() { return UrlLauncherApiCodec.INSTANCE; } - /** Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ + /**Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLauncherApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", getCodec()); + binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -215,7 +226,8 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.canLaunchUrl(urlArg); wrapped.add(0, output); - } catch (Throwable exception) { + } + catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -228,7 +240,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", getCodec()); + binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -239,7 +251,8 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.launchUrl(urlArg, headersArg); wrapped.add(0, output); - } catch (Throwable exception) { + } + catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -252,18 +265,42 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView", getCodec()); + binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; String urlArg = (String) args.get(0); - WebViewOptions optionsArg = (WebViewOptions) args.get(1); + Boolean allowCustomTabArg = (Boolean) args.get(1); + WebViewOptions optionsArg = (WebViewOptions) args.get(2); + try { + Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); try { - Boolean output = api.openUrlInWebView(urlArg, optionsArg); + Boolean output = api.supportsCustomTabs(); wrapped.add(0, output); - } catch (Throwable exception) { + } + catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -276,7 +313,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", getCodec()); + binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -284,7 +321,8 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { api.closeWebView(); wrapped.add(0, null); - } catch (Throwable exception) { + } + catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java index 8ee9bffbb587..028338c6981b 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java @@ -16,9 +16,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; import io.flutter.plugins.urllauncher.Messages.UrlLauncherApi; import io.flutter.plugins.urllauncher.Messages.WebViewOptions; +import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -95,14 +97,16 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options) { + public @NonNull Boolean openUrlInApp( + @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options) { ensureActivity(); assert activity != null; Bundle headersBundle = extractBundle(options.getHeaders()); - // Try to launch using Custom Tabs if they have the necessary functionality. - if (!containsRestrictedHeader(options.getHeaders())) { + // Try to launch using Custom Tabs if they have the necessary functionality, unless the caller + // specifically requested a web view. + if (allowCustomTab && !containsRestrictedHeader(options.getHeaders())) { Uri uri = Uri.parse(url); if (openCustomTab(activity, uri, headersBundle)) { return true; @@ -131,6 +135,11 @@ public void closeWebView() { applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE)); } + @Override + public @NonNull Boolean supportsCustomTabs() { + return CustomTabsClient.getPackageName(applicationContext, Collections.emptyList()) != null; + } + private static boolean openCustomTab( @NonNull Context context, @NonNull Uri uri, @NonNull Bundle headersBundle) { CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build(); diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java index b8bb3b4f21ef..0ad7422a945c 100644 --- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java +++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java @@ -130,7 +130,7 @@ public void launch_returnsTrue() { } @Test - public void openWebView_opensUrl_inWebView() { + public void openUrlInApp_opensUrlInWebViewIfNecessary() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -141,8 +141,9 @@ public void openWebView_opensUrl_inWebView() { headers.put("key", "value"); boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(enableDomStorage) @@ -162,20 +163,44 @@ public void openWebView_opensUrl_inWebView() { } @Test - public void openWebView_opensUrl_inCustomTabs() { + public void openWebView_opensUrlInWebViewIfRequested() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); String url = "https://flutter.dev"; boolean result = - api.openUrlInWebView( - url, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build()); + api.openUrlInApp( + url, + false, + new Messages.WebViewOptions.Builder() + .setEnableJavaScript(false) + .setEnableDomStorage(false) + .setHeaders(new HashMap<>()) + .build()); + + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(activity).startActivity(intentCaptor.capture()); + assertTrue(result); + assertEquals(url, intentCaptor.getValue().getExtras().getString(WebViewActivity.URL_EXTRA)); + } + + @Test + public void openWebView_opensUrlInCustomTabs() { + Activity activity = mock(Activity.class); + UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); + api.setActivity(activity); + String url = "https://flutter.dev"; + + boolean result = + api.openUrlInApp( + url, + true, + new Messages.WebViewOptions.Builder() + .setEnableJavaScript(false) + .setEnableDomStorage(false) + .setHeaders(new HashMap<>()) + .build()); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); @@ -185,7 +210,7 @@ public void openWebView_opensUrl_inCustomTabs() { } @Test - public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { + public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -195,8 +220,9 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { headers.put(headerKey, "text/plain"); boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -214,7 +240,7 @@ public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { } @Test - public void openWebView_fallsbackTo_inWebView() { + public void openWebView_fallsBackToWebViewIfCustomTabFails() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -224,8 +250,9 @@ public void openWebView_fallsbackTo_inWebView() { .startActivity(any(), isNull()); // for custom tabs intent boolean result = - api.openUrlInWebView( + api.openUrlInApp( url, + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -251,8 +278,9 @@ public void openWebView_handlesEnableJavaScript() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(false) @@ -277,8 +305,9 @@ public void openWebView_handlesHeaders() { headers.put(key1, "value"); headers.put(key2, "value2"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -303,8 +332,9 @@ public void openWebView_handlesEnableDomStorage() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(enableDomStorage) @@ -327,8 +357,9 @@ public void openWebView_throwsForNoCurrentActivity() { assertThrows( Messages.FlutterError.class, () -> - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -350,8 +381,9 @@ public void openWebView_returnsFalse() { .startActivity(any()); // for webview intent boolean result = - api.openUrlInWebView( + api.openUrlInApp( "https://flutter.dev", + true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) diff --git a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart index 9aed8f7f60f0..850ae171ff13 100644 --- a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.0), do not edit directly. +// Autogenerated from Pigeon (v10.1.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import @@ -58,7 +58,7 @@ class _UrlLauncherApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 128: return WebViewOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -79,7 +79,7 @@ class UrlLauncherApi { /// Returns true if the URL can definitely be launched. Future canLaunchUrl(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url]) as List?; @@ -105,10 +105,9 @@ class UrlLauncherApi { } /// Opens the URL externally, returning true if successful. - Future launchUrl( - String arg_url, Map arg_headers) async { + Future launchUrl(String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url, arg_headers]) as List?; @@ -133,15 +132,41 @@ class UrlLauncherApi { } } - /// Opens the URL in an in-app WebView, returning true if it opens - /// successfully. - Future openUrlInWebView( - String arg_url, WebViewOptions arg_options) async { + /// Opens the URL in an in-app Custom Tab or WebView, returning true if it + /// opens successfully. + Future openUrlInApp(String arg_url, bool arg_allowCustomTab, WebViewOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_url, arg_options]) as List?; + await channel.send([arg_url, arg_allowCustomTab, arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future supportsCustomTabs() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -166,9 +191,10 @@ class UrlLauncherApi { /// Closes the view opened by [openUrlInSafariViewController]. Future closeWebView() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.UrlLauncherApi.closeWebView', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + final List? replyList = + await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart index 7b53b85f7936..f121084b7578 100644 --- a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart +++ b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart @@ -49,8 +49,6 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return _hostApi.closeWebView(); } - // TODO(stuartmorgan): Implement launchUrl, and make this a passthrough - // to launchUrl. See also https://github.com/flutter/flutter/issues/66721 @override Future launch( String url, { @@ -62,16 +60,57 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { required Map headers, String? webOnlyWindowName, }) async { + return launchUrl( + url, + LaunchOptions( + mode: useWebView + ? PreferredLaunchMode.inAppWebView + : PreferredLaunchMode.externalApplication, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: enableDomStorage, + enableJavaScript: enableJavaScript, + headers: headers))); + } + + @override + Future launchUrl(String url, LaunchOptions options) async { + final bool inApp; + switch (options.mode) { + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + inApp = true; + break; + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + // TODO(stuartmorgan): Add full support for + // externalNonBrowsingApplication; see + // https://github.com/flutter/flutter/issues/66721. + // Currently it's treated the same as externalApplication. + inApp = false; + break; + case PreferredLaunchMode.platformDefault: + // Intentionally treat any new values as platformDefault; see comment in + // supportsMode. + // ignore: no_default_cases + default: + // By default, open web URLs in the application. + inApp = url.startsWith('http:') || url.startsWith('https:'); + break; + } + final bool succeeded; - if (useWebView) { - succeeded = await _hostApi.openUrlInWebView( + if (inApp) { + succeeded = await _hostApi.openUrlInApp( url, + // Prefer custom tabs unless a webview was specifically requested. + options.mode != PreferredLaunchMode.inAppWebView, WebViewOptions( - enableJavaScript: enableJavaScript, - enableDomStorage: enableDomStorage, - headers: headers)); + enableJavaScript: options.webViewConfiguration.enableJavaScript, + enableDomStorage: options.webViewConfiguration.enableDomStorage, + headers: options.webViewConfiguration.headers)); } else { - succeeded = await _hostApi.launchUrl(url, headers); + succeeded = + await _hostApi.launchUrl(url, options.webViewConfiguration.headers); } // TODO(stuartmorgan): Remove this special handling as part of a // breaking change to rework failure handling across all platform. The @@ -84,6 +123,29 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return succeeded; } + @override + Future supportsMode(PreferredLaunchMode mode) async { + switch (mode) { + case PreferredLaunchMode.platformDefault: + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.externalApplication: + return true; + case PreferredLaunchMode.inAppBrowserView: + return _hostApi.supportsCustomTabs(); + // Default is a desired behavior here since support for new modes is + // always opt-in, and the enum lives in a different package, so silently + // adding "false" for new values is the correct behavior. + // ignore: no_default_cases + default: + return false; + } + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.inAppWebView; + } + // Returns the part of [url] up to the first ':', or an empty string if there // is no ':'. This deliberately does not use [Uri] to extract the scheme // so that it works on strings that aren't actually valid URLs, since Android diff --git a/packages/url_launcher/url_launcher_android/pigeons/messages.dart b/packages/url_launcher/url_launcher_android/pigeons/messages.dart index 84e507d70248..d71844134092 100644 --- a/packages/url_launcher/url_launcher_android/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_android/pigeons/messages.dart @@ -33,9 +33,11 @@ abstract class UrlLauncherApi { /// Opens the URL externally, returning true if successful. bool launchUrl(String url, Map headers); - /// Opens the URL in an in-app WebView, returning true if it opens - /// successfully. - bool openUrlInWebView(String url, WebViewOptions options); + /// Opens the URL in an in-app Custom Tab or WebView, returning true if it + /// opens successfully. + bool openUrlInApp(String url, bool allowCustomTab, WebViewOptions options); + + bool supportsCustomTabs(); /// Closes the view opened by [openUrlInSafariViewController]. void closeWebView(); diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart index 3b3d012a1683..2b331cbd027b 100644 --- a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart +++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart @@ -52,7 +52,7 @@ void main() { }); }); - group('launch without webview', () { + group('legacy launch without webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -88,7 +88,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'noactivity://', + 'https://noactivity', useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -116,7 +116,7 @@ void main() { }); }); - group('launch with webview', () { + group('legacy launch with webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -130,6 +130,7 @@ void main() { ); expect(launched, true); expect(api.usedWebView, true); + expect(api.allowedCustomTab, false); expect(api.passedWebViewOptions?.enableDomStorage, false); expect(api.passedWebViewOptions?.enableJavaScript, false); expect(api.passedWebViewOptions?.headers, isEmpty); @@ -169,7 +170,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'noactivity://scheme', + 'https://noactivity', useSafariVC: false, useWebView: true, enableJavaScript: false, @@ -197,12 +198,198 @@ void main() { }); }); - group('closeWebView', () { + group('launch without webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await launcher.closeWebView(); + final bool launched = await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + ); + expect(launched, true); + expect(api.usedWebView, false); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); - expect(api.closed, true); + test('passes headers', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.externalApplication, + webViewConfiguration: InAppWebViewConfiguration( + headers: {'key': 'value'})), + ); + expect(api.passedWebViewOptions?.headers.length, 1); + expect(api.passedWebViewOptions?.headers['key'], 'value'); + }); + + test('passes through no-activity exception', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('https://noactivity', const LaunchOptions()), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('unknown://scheme', const LaunchOptions()), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('launch with webview', () { + test('calls through', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, false); + expect(api.passedWebViewOptions?.enableDomStorage, true); + expect(api.passedWebViewOptions?.enableJavaScript, true); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); + + test('passes enableJavaScript to webview', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: + InAppWebViewConfiguration(enableJavaScript: false))); + + expect(api.passedWebViewOptions?.enableJavaScript, false); + }); + + test('passes enableDomStorage to webview', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: + InAppWebViewConfiguration(enableDomStorage: false))); + + expect(api.passedWebViewOptions?.enableDomStorage, false); + }); + + test('passes through no-activity exception', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('https://noactivity', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + await expectLater( + launcher.launchUrl('unknown://scheme', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('launch with custom tab', () { + test('calls through', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + }); + + group('launch with platform default', () { + test('uses custom tabs for http', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'http://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + + test('uses custom tabs for https', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'https://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.allowedCustomTab, true); + }); + + test('uses external for other schemes', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + final bool launched = await launcher.launchUrl( + 'supportedcustomscheme://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedWebView, false); + }); + }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns true for in app web view', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); + }); + + test('returns true for in app browser view when available', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + api.hasCustomTabSupport = true; + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + + test('returns false for in app browser view when not available', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + api.hasCustomTabSupport = false; + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + false); + }); + }); + + group('supportsCloseForMode', () { + test('returns true for in app web view', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher.supportsCloseForMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), + false); }); }); } @@ -211,8 +398,10 @@ void main() { /// /// See _launch for the behaviors. class _FakeUrlLauncherApi implements UrlLauncherApi { + bool hasCustomTabSupport = true; WebViewOptions? passedWebViewOptions; bool? usedWebView; + bool? allowedCustomTab; bool? closed; /// A domain that will be treated as having no handler, even for http(s). @@ -237,20 +426,29 @@ class _FakeUrlLauncherApi implements UrlLauncherApi { } @override - Future openUrlInWebView(String url, WebViewOptions options) async { + Future openUrlInApp( + String url, bool allowCustomTab, WebViewOptions options) async { passedWebViewOptions = options; usedWebView = true; + allowedCustomTab = allowCustomTab; return _launch(url); } + @override + Future supportsCustomTabs() async { + return hasCustomTabSupport; + } + bool _launch(String url) { final String scheme = url.split(':')[0]; switch (scheme) { case 'http': case 'https': + case 'supportedcustomscheme': + if (url.endsWith('noactivity')) { + throw PlatformException(code: 'NO_ACTIVITY'); + } return !url.contains(specialHandlerDomain); - case 'noactivity': - throw PlatformException(code: 'NO_ACTIVITY'); default: return false; } From 133482812f0783348e8ba90a9efe3028b8a75a1e Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 08:32:21 -0400 Subject: [PATCH 06/14] Implement iOS --- .../lib/url_launcher_ios.dart | 72 +++++- .../test/url_launcher_ios_test.dart | 206 ++++++++++++++++-- 2 files changed, 254 insertions(+), 24 deletions(-) diff --git a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart index 2f0e9f47b940..66969787fba3 100644 --- a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart +++ b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart @@ -45,11 +45,79 @@ class UrlLauncherIOS extends UrlLauncherPlatform { required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, - }) { + }) async { + final PreferredLaunchMode mode; if (useSafariVC) { + mode = PreferredLaunchMode.inAppBrowserView; + } else if (universalLinksOnly) { + mode = PreferredLaunchMode.externalNonBrowserApplication; + } else { + mode = PreferredLaunchMode.externalApplication; + } + return launchUrl( + url, + LaunchOptions( + mode: mode, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: enableDomStorage, + enableJavaScript: enableJavaScript, + headers: headers))); + } + + @override + Future launchUrl(String url, LaunchOptions options) async { + final bool inApp; + switch (options.mode) { + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + // The iOS implementation doesn't distinguish between these two modes; + // both are treated as inAppBrowserView. + inApp = true; + break; + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + inApp = false; + break; + case PreferredLaunchMode.platformDefault: + // Intentionally treat any new values as platformDefault; support for any + // new mode requires intentional opt-in, otherwise falling back is the + // documented behavior. + // ignore: no_default_cases + default: + // By default, open web URLs in the application. + inApp = url.startsWith('http:') || url.startsWith('https:'); + break; + } + + if (inApp) { return _hostApi.openUrlInSafariViewController(url); } else { - return _hostApi.launchUrl(url, universalLinksOnly); + return _hostApi.launchUrl(url, + options.mode == PreferredLaunchMode.externalNonBrowserApplication); } } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + switch (mode) { + case PreferredLaunchMode.platformDefault: + case PreferredLaunchMode.inAppWebView: + case PreferredLaunchMode.inAppBrowserView: + case PreferredLaunchMode.externalApplication: + case PreferredLaunchMode.externalNonBrowserApplication: + return true; + // Default is a desired behavior here since support for new modes is + // always opt-in, and the enum lives in a different package, so silently + // adding "false" for new values is the correct behavior. + // ignore: no_default_cases + default: + return false; + } + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + return mode == PreferredLaunchMode.inAppWebView || + mode == PreferredLaunchMode.inAppBrowserView; + } } diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart index 9274173f90b4..bacea3132c13 100644 --- a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart +++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart @@ -11,36 +11,37 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('UrlLauncherIOS', () { - late _FakeUrlLauncherApi api; + late _FakeUrlLauncherApi api; - setUp(() { - api = _FakeUrlLauncherApi(); - }); + setUp(() { + api = _FakeUrlLauncherApi(); + }); - test('registers instance', () { - UrlLauncherIOS.registerWith(); - expect(UrlLauncherPlatform.instance, isA()); - }); + test('registers instance', () { + UrlLauncherIOS.registerWith(); + expect(UrlLauncherPlatform.instance, isA()); + }); - test('canLaunch success', () async { + group('canLaunch', () { + test('handles success', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect(await launcher.canLaunch('http://example.com/'), true); }); - test('canLaunch failure', () async { + test('handles failure', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect(await launcher.canLaunch('unknown://scheme'), false); }); - test('canLaunch invalid URL passes the PlatformException through', - () async { + test('passes invalid URL PlatformException through', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); await expectLater(launcher.canLaunch('invalid://u r l'), throwsA(isA())); }); + }); - test('launch success', () async { + group('legacy launch', () { + test('handles success', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -56,7 +57,7 @@ void main() { expect(api.passedUniversalLinksOnly, false); }); - test('launch failure', () async { + test('handles failure', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -72,7 +73,7 @@ void main() { expect(api.passedUniversalLinksOnly, false); }); - test('launch invalid URL passes the PlatformException through', () async { + test('passes invalid URL PlatformException through', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); await expectLater( launcher.launch( @@ -87,7 +88,7 @@ void main() { throwsA(isA())); }); - test('launch force SafariVC', () async { + test('force SafariVC is handled', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -103,7 +104,7 @@ void main() { expect(api.usedSafariViewController, true); }); - test('launch universal links only', () async { + test('universal links only is handled', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -119,7 +120,7 @@ void main() { expect(api.passedUniversalLinksOnly, true); }); - test('launch force SafariVC to false', () async { + test('disallowing SafariVC is handled', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -134,11 +135,171 @@ void main() { true); expect(api.usedSafariViewController, false); }); + }); + + test('closeWebView calls through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await launcher.closeWebView(); + expect(api.closed, true); + }); + + group('launch without webview', () { + test('calls through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + ); + expect(launched, true); + expect(api.usedSafariViewController, false); + }); + + test('passes invalid URL PlatformException through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl('invalid://u r l', const LaunchOptions()), + throwsA(isA())); + }); + }); + + group('launch with Safari view controller', () { + test('calls through with inAppWebView', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); + expect(launched, true); + expect(api.usedSafariViewController, true); + }); + + test('calls through with inAppBrowserView', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl('http://example.com/', + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); + expect(launched, true); + expect(api.usedSafariViewController, true); + }); + + test('passes invalid URL PlatformException through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl('invalid://u r l', + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), + throwsA(isA())); + }); + }); + + group('launch with universal links', () { + test('calls through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + 'http://example.com/', + const LaunchOptions( + mode: PreferredLaunchMode.externalNonBrowserApplication), + ); + expect(launched, true); + expect(api.usedSafariViewController, false); + expect(api.passedUniversalLinksOnly, true); + }); + + test('passes invalid URL PlatformException through', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + await expectLater( + launcher.launchUrl( + 'invalid://u r l', + const LaunchOptions( + mode: PreferredLaunchMode.externalNonBrowserApplication)), + throwsA(isA())); + }); + }); + + group('launch with platform default', () { + test('uses Safari view controller for http', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + 'http://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedSafariViewController, true); + }); + + test('uses Safari view controller for https', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + 'https://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedSafariViewController, true); + }); + + test('uses standard external for other schemes', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + final bool launched = await launcher.launchUrl( + 'supportedcustomscheme://example.com/', const LaunchOptions()); + expect(launched, true); + expect(api.usedSafariViewController, false); + expect(api.passedUniversalLinksOnly, false); + }); + }); + + group('supportsMode', () { + test('returns true for platformDefault', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), + true); + }); + + test('returns true for external application', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher.supportsMode(PreferredLaunchMode.externalApplication), + true); + }); + + test('returns true for external non-browser application', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), + true); + }); - test('closeWebView default behavior', () async { + test('returns true for in app web view', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await launcher.closeWebView(); - expect(api.closed, true); + expect( + await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); + }); + + test('returns true for in app browser view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + }); + + group('supportsCloseForMode', () { + test('returns true for in app web view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), + true); + }); + + test('returns true for in app browser view', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), + true); + }); + + test('returns false for other modes', () async { + final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); + expect( + await launcher + .supportsCloseForMode(PreferredLaunchMode.externalApplication), + false); + expect( + await launcher.supportsCloseForMode( + PreferredLaunchMode.externalNonBrowserApplication), + false); }); }); } @@ -179,6 +340,7 @@ class _FakeUrlLauncherApi implements UrlLauncherApi { switch (scheme) { case 'http': case 'https': + case 'supportedcustomscheme': return true; case 'invalid': throw PlatformException(code: 'argument_error'); From f70a5eee8ce02597d87f2b4b4366f89e1901461e Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 08:46:12 -0400 Subject: [PATCH 07/14] Add web support for new methos --- .../url_launcher_web_test.dart | 26 +++++++++++++++++++ .../lib/url_launcher_web.dart | 13 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart index f2dac2bb1434..a6ade3ebd3b4 100644 --- a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart +++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; import 'url_launcher_web_test.mocks.dart'; @@ -198,5 +199,30 @@ void main() { }); }); }); + + group('supportsMode', () { + testWidgets('returns true for platformDefault', (WidgetTester _) async { + expect(plugin.supportsMode(PreferredLaunchMode.platformDefault), + completion(isTrue)); + }); + + testWidgets('returns false for other modes', (WidgetTester _) async { + expect(plugin.supportsMode(PreferredLaunchMode.externalApplication), + completion(isFalse)); + expect( + plugin.supportsMode( + PreferredLaunchMode.externalNonBrowserApplication), + completion(isFalse)); + expect(plugin.supportsMode(PreferredLaunchMode.inAppBrowserView), + completion(isFalse)); + expect(plugin.supportsMode(PreferredLaunchMode.inAppWebView), + completion(isFalse)); + }); + }); + + testWidgets('supportsCloseForMode returns false', (WidgetTester _) async { + expect(plugin.supportsCloseForMode(PreferredLaunchMode.platformDefault), + completion(isFalse)); + }); }); } diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart index be40539d4737..c757e96f08bc 100644 --- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart +++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart @@ -90,4 +90,17 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { openNewWindow(url, webOnlyWindowName: webOnlyWindowName); return true; } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + // Web doesn't allow any control over the destination beyond + // webOnlyWindowName, so don't claim support for any mode beyond default. + return mode == PreferredLaunchMode.platformDefault; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + // No supported mode is closeable. + return false; + } } From 3cbffcd749107116519c54408d959c832cd11921 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 08:47:51 -0400 Subject: [PATCH 08/14] Handle new mode in backwards compat call --- .../lib/src/url_launcher_platform.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart b/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart index b6982bc6e313..da7404f98a1b 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart @@ -73,6 +73,7 @@ abstract class UrlLauncherPlatform extends PlatformInterface { Future launchUrl(String url, LaunchOptions options) { final bool isWebURL = url.startsWith('http:') || url.startsWith('https:'); final bool useWebView = options.mode == PreferredLaunchMode.inAppWebView || + options.mode == PreferredLaunchMode.inAppBrowserView || (isWebURL && options.mode == PreferredLaunchMode.platformDefault); return launch( From 4704684cf078b04a6631ec87f1102df00a6bc7da Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 09:59:22 -0400 Subject: [PATCH 09/14] Version bumps --- packages/url_launcher/url_launcher_android/CHANGELOG.md | 7 +++++++ packages/url_launcher/url_launcher_android/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_ios/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_ios/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_linux/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_linux/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_macos/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_macos/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_web/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_web/pubspec.yaml | 2 +- packages/url_launcher/url_launcher_windows/CHANGELOG.md | 4 ++++ packages/url_launcher/url_launcher_windows/pubspec.yaml | 2 +- 12 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index ed8a88314da1..3b56d0a448fe 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,3 +1,10 @@ +## 6.2.0 + +* Adds support for `inAppBrowserView` as a separate launch mode option from + `inAppWebView` mode. `inAppBrowserView` is the preferred in-app mode for most uses, + but does not support `closeInAppWebView`. +* Implements `supportsMode` and `supportsCloseForMode`. + ## 6.1.0 * Adds support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index 2ac42aee6efb..0984a1ab56f7 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.0 +version: 6.2.0 environment: sdk: ">=2.19.0 <4.0.0" flutter: ">=3.7.0" diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index ad52522d0021..ae630129705d 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.2.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 6.1.5 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index 43d58cb6f3cd..a76697008a92 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.1.5 +version: 6.2.0 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index a7f308737b4c..538fb7737a36 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.6 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 65e615ed2055..10713b33c21b 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.6 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index e2f5c6855ed1..ac8a05af5c9e 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.7 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 45e09ee396d0..6ba3d715d279 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.7 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 4fd4abb5ec96..99fe6599bc5a 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 2.0.20 * Migrates to `dart:ui_web` APIs. diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 8c6f3e65b61d..bc38daf8e795 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web description: Web platform implementation of url_launcher repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.0.20 +version: 2.1.0 environment: sdk: ">=3.1.0 <4.0.0" diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index 933cc1055274..b63c9e19e27c 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.1.0 + +* Implements `supportsMode` and `supportsCloseForMode`. + ## 3.0.8 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index ddc595ca9076..49a9e8c5d8ee 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.0.8 +version: 3.1.0 environment: sdk: ">=2.19.0 <4.0.0" From 8762abeb3416b418c2fba73e1959204feb3431e5 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 10:08:23 -0400 Subject: [PATCH 10/14] Update README --- packages/url_launcher/url_launcher/README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index afd60143a54b..1eeee6da4fe3 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -66,7 +66,9 @@ See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/u Add any URL schemes passed to `canLaunchUrl` as `` entries in your `AndroidManifest.xml`, otherwise it will return false in most cases starting -on Android 11 (API 30) or higher. A `` +on Android 11 (API 30) or higher. Checking for +`supportsLaunchMode(PreferredLaunchMode.inAppBrowserView)` also requires +a `` entry to return anything but false. A `` element must be added to your manifest as a child of the root element. Example: @@ -85,6 +87,10 @@ Example: + + + + ``` @@ -210,10 +216,16 @@ if (!await launchUrl(uri)) { If you need to access files outside of your application's sandbox, you will need to have the necessary [entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox). -## Browser vs in-app Handling +## Browser vs in-app handling On some platforms, web URLs can be launched either in an in-app web view, or in the default browser. The default behavior depends on the platform (see [`launchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launchUrl.html) for details), but a specific mode can be used on supported platforms by -passing a `LaunchMode`. +passing a `PreferredLaunchMode`. + +Platforms that do no support a requested `PreferredLaunchMode` will +automatically fall back to a supported mode (usually `platformDefault`). If +your application needs to avoid that fallback behavior, however, you can check +if the current platform supports a given mode with `supportsLaunchMode` before +calling `launchUrl`. From 1f70e218d413e6af3c76c81351671bf0af9a5c09 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 10:20:06 -0400 Subject: [PATCH 11/14] Update Android example to test Custom Tab --- .../example/lib/main.dart | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/packages/url_launcher/url_launcher_android/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart index df28069ca1de..36c32f2ba2b0 100644 --- a/packages/url_launcher/url_launcher_android/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_android/example/lib/main.dart @@ -39,6 +39,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; bool _hasCallSupport = false; + bool _hasCustomTabSupport = false; Future? _launched; String _phone = ''; @@ -51,73 +52,77 @@ class _MyHomePageState extends State { _hasCallSupport = result; }); }); + // Check for Android Custom Tab support. + launcher + .supportsMode(PreferredLaunchMode.inAppBrowserView) + .then((bool result) { + setState(() { + _hasCustomTabSupport = result; + }); + }); } Future _launchInBrowser(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( + url, + const LaunchOptions(mode: PreferredLaunchMode.externalApplication), + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInCustomTab(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView), )) { throw Exception('Could not launch $url'); } } Future _launchInWebView(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions(mode: PreferredLaunchMode.inAppWebView), )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithCustomHeaders(String url) async { - if (!await launcher.launch( + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: false, - headers: {'my_header_key': 'my_header_value'}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + headers: {'my_header_key': 'my_header_value'}, + )), )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithJavaScript(String url) async { - if (!await launcher.launch( + Future _launchInWebViewWithoutJavaScript(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: true, - enableDomStorage: false, - universalLinksOnly: false, - headers: {}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + enableJavaScript: false, + )), )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithDomStorage(String url) async { - if (!await launcher.launch( + Future _launchInWebViewWithoutDomStorage(String url) async { + if (!await launcher.launchUrl( url, - useSafariVC: true, - useWebView: true, - enableJavaScript: false, - enableDomStorage: true, - universalLinksOnly: false, - headers: {}, + const LaunchOptions( + mode: PreferredLaunchMode.inAppWebView, + webViewConfiguration: InAppWebViewConfiguration( + enableDomStorage: false, + )), )) { throw Exception('Could not launch $url'); } @@ -133,22 +138,12 @@ class _MyHomePageState extends State { Future _makePhoneCall(String phoneNumber) async { // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded. - // Just using 'tel:$phoneNumber' would create invalid URLs in some cases, - // such as spaces in the input, which would cause `launch` to fail on some - // platforms. + // Just using 'tel:$phoneNumber' would create invalid URLs in some cases. final Uri launchUri = Uri( scheme: 'tel', path: phoneNumber, ); - await launcher.launch( - launchUri.toString(), - useSafariVC: false, - useWebView: false, - enableJavaScript: false, - enableDomStorage: false, - universalLinksOnly: true, - headers: {}, - ); + await launcher.launchUrl(launchUri.toString(), const LaunchOptions()); } @override @@ -186,36 +181,45 @@ class _MyHomePageState extends State { padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), + ElevatedButton( + onPressed: _hasCustomTabSupport + ? () => setState(() { + _launched = _launchInBrowser(toLaunch); + }) + : null, + child: const Text('Launch in browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInBrowser(toLaunch); + _launched = _launchInCustomTab(toLaunch); }), - child: const Text('Launch in browser'), + child: const Text('Launch in Android Custom Tab'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebView(toLaunch); }), - child: const Text('Launch in app'), + child: const Text('Launch in web view'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithCustomHeaders(toLaunch); }), - child: const Text('Launch in app (Custom headers)'), + child: const Text('Launch in web view (Custom headers)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithJavaScript(toLaunch); + _launched = _launchInWebViewWithoutJavaScript(toLaunch); }), - child: const Text('Launch in app (JavaScript ON)'), + child: const Text('Launch in web view (JavaScript OFF)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithDomStorage(toLaunch); + _launched = _launchInWebViewWithoutDomStorage(toLaunch); }), - child: const Text('Launch in app (DOM storage ON)'), + child: const Text('Launch in web view (DOM storage OFF)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( @@ -225,7 +229,7 @@ class _MyHomePageState extends State { launcher.closeWebView(); }); }), - child: const Text('Launch in app + close after 5 seconds'), + child: const Text('Launch in web view + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), From ed562cdaa29b1895068b852a2e4738806548f845 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 10:21:15 -0400 Subject: [PATCH 12/14] Autoformat generated code --- .../flutter/plugins/urllauncher/Messages.java | 62 +++++++++---------- .../plugins/urllauncher/UrlLauncherTest.java | 32 +++++----- .../lib/src/messages.g.dart | 32 ++++++---- 3 files changed, 66 insertions(+), 60 deletions(-) diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java index 61506d826a6f..f2294f048f82 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java @@ -16,10 +16,6 @@ import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @@ -35,8 +31,7 @@ public static class FlutterError extends RuntimeException { /** The error details. Must be a datatype supported by the api codec. */ public final Object details; - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) - { + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { super(message); this.code = code; this.details = details; @@ -55,7 +50,7 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { errorList.add(exception.toString()); errorList.add(exception.getClass().getSimpleName()); errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); } return errorList; } @@ -63,7 +58,7 @@ protected static ArrayList wrapError(@NonNull Throwable exception) { /** * Configuration options for an in-app WebView. * - * Generated class from Pigeon that represents data sent in messages. + *

Generated class from Pigeon that represents data sent in messages. */ public static final class WebViewOptions { private @NonNull Boolean enableJavaScript; @@ -190,19 +185,19 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface UrlLauncherApi { /** Returns true if the URL can definitely be launched. */ - @NonNull + @NonNull Boolean canLaunchUrl(@NonNull String url); /** Opens the URL externally, returning true if successful. */ - @NonNull + @NonNull Boolean launchUrl(@NonNull String url, @NonNull Map headers); /** - * Opens the URL in an in-app Custom Tab or WebView, returning true if it - * opens successfully. + * Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully. */ - @NonNull - Boolean openUrlInApp(@NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options); + @NonNull + Boolean openUrlInApp( + @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options); - @NonNull + @NonNull Boolean supportsCustomTabs(); /** Closes the view opened by [openUrlInSafariViewController]. */ void closeWebView(); @@ -211,12 +206,14 @@ public interface UrlLauncherApi { static @NonNull MessageCodec getCodec() { return UrlLauncherApiCodec.INSTANCE; } - /**Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ + /** Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLauncherApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -226,8 +223,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.canLaunchUrl(urlArg); wrapped.add(0, output); - } - catch (Throwable exception) { + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -240,7 +236,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -251,8 +249,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.launchUrl(urlArg, headersArg); wrapped.add(0, output); - } - catch (Throwable exception) { + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -265,7 +262,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -277,8 +276,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg); wrapped.add(0, output); - } - catch (Throwable exception) { + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -291,7 +289,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -299,8 +299,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { Boolean output = api.supportsCustomTabs(); wrapped.add(0, output); - } - catch (Throwable exception) { + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } @@ -313,7 +312,9 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView", getCodec()); + binaryMessenger, + "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView", + getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -321,8 +322,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche try { api.closeWebView(); wrapped.add(0, null); - } - catch (Throwable exception) { + } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java index 0ad7422a945c..3bffbc614f09 100644 --- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java +++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java @@ -170,14 +170,14 @@ public void openWebView_opensUrlInWebViewIfRequested() { String url = "https://flutter.dev"; boolean result = - api.openUrlInApp( - url, - false, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build()); + api.openUrlInApp( + url, + false, + new Messages.WebViewOptions.Builder() + .setEnableJavaScript(false) + .setEnableDomStorage(false) + .setHeaders(new HashMap<>()) + .build()); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture()); @@ -193,14 +193,14 @@ public void openWebView_opensUrlInCustomTabs() { String url = "https://flutter.dev"; boolean result = - api.openUrlInApp( - url, - true, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build()); + api.openUrlInApp( + url, + true, + new Messages.WebViewOptions.Builder() + .setEnableJavaScript(false) + .setEnableDomStorage(false) + .setHeaders(new HashMap<>()) + .build()); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); verify(activity).startActivity(intentCaptor.capture(), isNull()); diff --git a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart index 850ae171ff13..9d6ce26ed85b 100644 --- a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart @@ -58,7 +58,7 @@ class _UrlLauncherApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 128: return WebViewOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -79,7 +79,8 @@ class UrlLauncherApi { /// Returns true if the URL can definitely be launched. Future canLaunchUrl(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url]) as List?; @@ -105,9 +106,11 @@ class UrlLauncherApi { } /// Opens the URL externally, returning true if successful. - Future launchUrl(String arg_url, Map arg_headers) async { + Future launchUrl( + String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl', + codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url, arg_headers]) as List?; @@ -134,12 +137,15 @@ class UrlLauncherApi { /// Opens the URL in an in-app Custom Tab or WebView, returning true if it /// opens successfully. - Future openUrlInApp(String arg_url, bool arg_allowCustomTab, WebViewOptions arg_options) async { + Future openUrlInApp(String arg_url, bool arg_allowCustomTab, + WebViewOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp', + codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_url, arg_allowCustomTab, arg_options]) as List?; + await channel.send([arg_url, arg_allowCustomTab, arg_options]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -163,10 +169,10 @@ class UrlLauncherApi { Future supportsCustomTabs() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs', + codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send(null) as List?; + final List? replyList = await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -191,10 +197,10 @@ class UrlLauncherApi { /// Closes the view opened by [openUrlInSafariViewController]. Future closeWebView() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView', codec, + 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView', + codec, binaryMessenger: _binaryMessenger); - final List? replyList = - await channel.send(null) as List?; + final List? replyList = await channel.send(null) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', From 28f0553798cf335d09437204763b2502896d5113 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 16 Oct 2023 10:49:21 -0400 Subject: [PATCH 13/14] Add query support to example to match README --- .../example/android/app/src/main/AndroidManifest.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml index fe01f2fba9a8..5cfc75883726 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml @@ -20,6 +20,10 @@ + + + + From 19b8d56d3f2c287673f1c2f686f3d908e6de3780 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 23 Oct 2023 10:03:43 -0400 Subject: [PATCH 14/14] Revert everything but platform interface --- .../url_launcher/url_launcher/CHANGELOG.md | 14 -- packages/url_launcher/url_launcher/README.md | 18 +- .../android/app/src/main/AndroidManifest.xml | 4 - .../url_launcher/example/lib/main.dart | 6 +- .../url_launcher/example/pubspec.yaml | 5 - .../url_launcher/lib/src/link.dart | 2 +- .../url_launcher/lib/src/type_conversion.dart | 2 - .../url_launcher/lib/src/types.dart | 5 +- .../lib/src/url_launcher_string.dart | 3 +- .../lib/src/url_launcher_uri.dart | 51 ++-- .../url_launcher/url_launcher/pubspec.yaml | 7 +- .../url_launcher/test/link_test.dart | 2 +- .../mocks/mock_url_launcher_platform.dart | 12 - .../test/src/url_launcher_uri_test.dart | 36 --- .../url_launcher_android/CHANGELOG.md | 7 - .../flutter/plugins/urllauncher/Messages.java | 56 +---- .../plugins/urllauncher/UrlLauncher.java | 15 +- .../plugins/urllauncher/UrlLauncherTest.java | 58 ++--- .../example/lib/main.dart | 120 +++++----- .../url_launcher_android/example/pubspec.yaml | 5 - .../lib/src/messages.g.dart | 52 +---- .../lib/url_launcher_android.dart | 78 +------ .../pigeons/messages.dart | 8 +- .../url_launcher_android/pubspec.yaml | 7 +- .../test/url_launcher_android_test.dart | 218 +----------------- .../url_launcher_ios/CHANGELOG.md | 4 - .../url_launcher_ios/example/pubspec.yaml | 5 - .../lib/url_launcher_ios.dart | 72 +----- .../url_launcher_ios/pubspec.yaml | 7 +- .../test/url_launcher_ios_test.dart | 206 ++--------------- .../url_launcher_linux/CHANGELOG.md | 4 - .../url_launcher_linux/example/pubspec.yaml | 5 - .../lib/url_launcher_linux.dart | 12 - .../url_launcher_linux/pubspec.yaml | 7 +- .../test/url_launcher_linux_test.dart | 43 +--- .../url_launcher_macos/CHANGELOG.md | 4 - .../url_launcher_macos/example/pubspec.yaml | 5 - .../lib/url_launcher_macos.dart | 12 - .../url_launcher_macos/pubspec.yaml | 7 +- .../test/url_launcher_macos_test.dart | 41 ---- .../url_launcher_web/CHANGELOG.md | 4 - .../url_launcher_web_test.dart | 26 --- .../url_launcher_web/example/pubspec.yaml | 5 - .../lib/url_launcher_web.dart | 13 -- .../url_launcher_web/pubspec.yaml | 7 +- .../url_launcher_windows/CHANGELOG.md | 4 - .../url_launcher_windows/example/pubspec.yaml | 5 - .../lib/url_launcher_windows.dart | 12 - .../url_launcher_windows/pubspec.yaml | 7 +- .../test/url_launcher_windows_test.dart | 39 ---- 50 files changed, 178 insertions(+), 1169 deletions(-) diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 75e60861227c..cebc8177291b 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,17 +1,3 @@ -## 6.2.0 - -* Adds `supportsLaunchMode` for checking whether the current platform supports a - given launch mode, to allow clients that will only work with specific modes - to avoid fallback to a different mode. -* Adds `supportsCloseForLaunchMode` to allow checking programatically if a - launched URL will be able to be closed. Previously the documented behvaior was - that it worked only with the `inAppWebView` launch mode, but this is no longer - true on all platforms with the addition of `inAppBrowserView`. -* Updates the documention for `launchUrl` to clarify that clients should not - rely on any specific behavior of the `platformDefault` launch mode. Changes - to the handling of `platformDefault`, such as Android's recent change from - `inAppBrowserView` to the new `inAppBrowserView`, are not considered breaking. - ## 6.1.14 * Updates documentation to mention support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index 1eeee6da4fe3..afd60143a54b 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -66,9 +66,7 @@ See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/u Add any URL schemes passed to `canLaunchUrl` as `` entries in your `AndroidManifest.xml`, otherwise it will return false in most cases starting -on Android 11 (API 30) or higher. Checking for -`supportsLaunchMode(PreferredLaunchMode.inAppBrowserView)` also requires -a `` entry to return anything but false. A `` +on Android 11 (API 30) or higher. A `` element must be added to your manifest as a child of the root element. Example: @@ -87,10 +85,6 @@ Example: - - - - ``` @@ -216,16 +210,10 @@ if (!await launchUrl(uri)) { If you need to access files outside of your application's sandbox, you will need to have the necessary [entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox). -## Browser vs in-app handling +## Browser vs in-app Handling On some platforms, web URLs can be launched either in an in-app web view, or in the default browser. The default behavior depends on the platform (see [`launchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launchUrl.html) for details), but a specific mode can be used on supported platforms by -passing a `PreferredLaunchMode`. - -Platforms that do no support a requested `PreferredLaunchMode` will -automatically fall back to a supported mode (usually `platformDefault`). If -your application needs to avoid that fallback behavior, however, you can check -if the current platform supports a given mode with `supportsLaunchMode` before -calling `launchUrl`. +passing a `LaunchMode`. diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml index 5cfc75883726..fe01f2fba9a8 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml @@ -20,10 +20,6 @@ - - - - diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index 816eb29c58e9..57a0ce9ef470 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -99,7 +99,7 @@ class _MyHomePageState extends State { } } - Future _launchUniversalLinkIOS(Uri url) async { + Future _launchUniversalLinkIos(Uri url) async { final bool nativeAppLaunchSucceeded = await launchUrl( url, mode: LaunchMode.externalNonBrowserApplication, @@ -107,7 +107,7 @@ class _MyHomePageState extends State { if (!nativeAppLaunchSucceeded) { await launchUrl( url, - mode: LaunchMode.inAppBrowserView, + mode: LaunchMode.inAppWebView, ); } } @@ -198,7 +198,7 @@ class _MyHomePageState extends State { const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchUniversalLinkIOS(toLaunch); + _launched = _launchUniversalLinkIos(toLaunch); }), child: const Text( 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index f61bda8b076d..e09df486b417 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -29,8 +29,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher/lib/src/link.dart b/packages/url_launcher/url_launcher/lib/src/link.dart index b5c47403097f..cea6845b12ea 100644 --- a/packages/url_launcher/url_launcher/lib/src/link.dart +++ b/packages/url_launcher/url_launcher/lib/src/link.dart @@ -126,7 +126,7 @@ class DefaultLinkDelegate extends StatelessWidget { success = await launchUrl( url, mode: _useWebView - ? LaunchMode.inAppBrowserView + ? LaunchMode.inAppWebView : LaunchMode.externalApplication, ); } on PlatformException { diff --git a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart index 3169e25dfa7a..970f04dced57 100644 --- a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart +++ b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart @@ -22,8 +22,6 @@ PreferredLaunchMode convertLaunchMode(LaunchMode mode) { switch (mode) { case LaunchMode.platformDefault: return PreferredLaunchMode.platformDefault; - case LaunchMode.inAppBrowserView: - return PreferredLaunchMode.inAppBrowserView; case LaunchMode.inAppWebView: return PreferredLaunchMode.inAppWebView; case LaunchMode.externalApplication: diff --git a/packages/url_launcher/url_launcher/lib/src/types.dart b/packages/url_launcher/url_launcher/lib/src/types.dart index 2bf56e1b5d8b..359e293ef82e 100644 --- a/packages/url_launcher/url_launcher/lib/src/types.dart +++ b/packages/url_launcher/url_launcher/lib/src/types.dart @@ -14,12 +14,9 @@ enum LaunchMode { /// implementation. platformDefault, - /// Loads the URL in an in-app web view (e.g., Android WebView). + /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, Safari View Controller). inAppWebView, - /// Loads the URL in an in-app web view (e.g., Android Custom Tabs, SFSafariViewController). - inAppBrowserView, - /// Passes the URL to the OS to be handled by another application. externalApplication, diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart index ca0bcc6109d0..45193ff17cb3 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart @@ -25,8 +25,7 @@ Future launchUrlString( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if ((mode == LaunchMode.inAppWebView || - mode == LaunchMode.inAppBrowserView) && + if (mode == LaunchMode.inAppWebView && !(urlString.startsWith('https:') || urlString.startsWith('http:'))) { throw ArgumentError.value(urlString, 'urlString', 'To use an in-app web view, you must provide an http(s) URL.'); diff --git a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart index 062621656932..b3ce6c279f39 100644 --- a/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart +++ b/packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart @@ -11,13 +11,25 @@ import 'type_conversion.dart'; /// Passes [url] to the underlying platform for handling. /// -/// [mode] support varies significantly by platform. Clients can use -/// [supportsLaunchMode] to query for support, but platforms will fall back to -/// other modes if the requested mode is not supported, so checking is not -/// required. The default behavior of [LaunchMode.platformDefault] is up to each -/// platform, and its behavior for a given platform may change over time as new -/// modes are supported, so clients that want a specific mode should request it -/// rather than rely on any currently observed default behavior. +/// [mode] support varies significantly by platform: +/// - [LaunchMode.platformDefault] is supported on all platforms: +/// - On iOS and Android, this treats web URLs as +/// [LaunchMode.inAppWebView], and all other URLs as +/// [LaunchMode.externalApplication]. +/// - On Windows, macOS, and Linux this behaves like +/// [LaunchMode.externalApplication]. +/// - On web, this uses `webOnlyWindowName` for web URLs, and behaves like +/// [LaunchMode.externalApplication] for any other content. +/// - [LaunchMode.inAppWebView] is currently only supported on iOS and +/// Android. If a non-web URL is passed with this mode, an [ArgumentError] +/// will be thrown. +/// - [LaunchMode.externalApplication] is supported on all platforms. +/// On iOS, this should be used in cases where sharing the cookies of the +/// user's browser is important, such as SSO flows, since Safari View +/// Controller does not share the browser's context. +/// - [LaunchMode.externalNonBrowserApplication] is supported on iOS 10+. +/// This setting is used to require universal links to open in a non-browser +/// application. /// /// For web, [webOnlyWindowName] specifies a target for the launch. This /// supports the standard special link target names. For example: @@ -33,8 +45,7 @@ Future launchUrl( WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { - if ((mode == LaunchMode.inAppWebView || - mode == LaunchMode.inAppBrowserView) && + if (mode == LaunchMode.inAppWebView && !(url.scheme == 'https' || url.scheme == 'http')) { throw ArgumentError.value(url, 'url', 'To use an in-app web view, you must provide an http(s) URL.'); @@ -70,26 +81,8 @@ Future canLaunchUrl(Uri url) async { /// Closes the current in-app web view, if one was previously opened by /// [launchUrl]. /// -/// This works only if [supportsCloseForLaunchMode] returns true for the mode -/// that was used by [launchUrl]. +/// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this +/// call will have no effect. Future closeInAppWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } - -/// Returns true if [mode] is supported by the current platform implementation. -/// -/// Calling [launchUrl] with an unsupported mode will fall back to a supported -/// mode, so calling this method is only necessary for cases where the caller -/// needs to know which mode will be used. -Future supportsLaunchMode(PreferredLaunchMode mode) { - return UrlLauncherPlatform.instance.supportsMode(mode); -} - -/// Returns true if [closeInAppWebView] is supported for [mode] in the current -/// platform implementation. -/// -/// If this returns false, [closeInAppWebView] will not work when launching -/// URLs with [mode]. -Future supportsCloseForLaunchMode(PreferredLaunchMode mode) { - return UrlLauncherPlatform.instance.supportsMode(mode); -} diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index fb3e46e4a2cb..9feae723f2f1 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.0 +version: 6.1.14 environment: sdk: ">=3.0.0 <4.0.0" @@ -50,8 +50,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index 052ca2556e39..1585420d9b29 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -86,7 +86,7 @@ void main() { mock ..setLaunchExpectations( url: 'http://example.com/foobar', - launchMode: PreferredLaunchMode.inAppBrowserView, + launchMode: PreferredLaunchMode.inAppWebView, universalLinksOnly: false, enableJavaScript: true, enableDomStorage: true, diff --git a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart index fc0181d4a4ed..05c8b5e4b375 100644 --- a/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart +++ b/packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart @@ -107,16 +107,4 @@ class MockUrlLauncher extends Fake Future closeWebView() async { closeWebViewCalled = true; } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - launchMode = mode; - return response!; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - launchMode = mode; - return response!; - } } diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart index 0e6e76c38ea4..d71d07fc8fc4 100644 --- a/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart +++ b/packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart @@ -248,40 +248,4 @@ void main() { expect(await launchUrl(emailLaunchUrl), isTrue); }); }); - - group('supportsLaunchMode', () { - test('handles returning true', () async { - const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; - mock.setResponse(true); - - expect(await supportsLaunchMode(mode), true); - expect(mock.launchMode, mode); - }); - - test('handles returning false', () async { - const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; - mock.setResponse(false); - - expect(await supportsLaunchMode(mode), false); - expect(mock.launchMode, mode); - }); - }); - - group('supportsCloseForLaunchMode', () { - test('handles returning true', () async { - const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; - mock.setResponse(true); - - expect(await supportsCloseForLaunchMode(mode), true); - expect(mock.launchMode, mode); - }); - - test('handles returning false', () async { - const PreferredLaunchMode mode = PreferredLaunchMode.inAppBrowserView; - mock.setResponse(false); - - expect(await supportsCloseForLaunchMode(mode), false); - expect(mock.launchMode, mode); - }); - }); } diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md index 3b56d0a448fe..ed8a88314da1 100644 --- a/packages/url_launcher/url_launcher_android/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_android/CHANGELOG.md @@ -1,10 +1,3 @@ -## 6.2.0 - -* Adds support for `inAppBrowserView` as a separate launch mode option from - `inAppWebView` mode. `inAppBrowserView` is the preferred in-app mode for most uses, - but does not support `closeInAppWebView`. -* Implements `supportsMode` and `supportsCloseForMode`. - ## 6.1.0 * Adds support for Android Custom Tabs. diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java index f2294f048f82..eab0d87f5b35 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/Messages.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.1.4), do not edit directly. +// Autogenerated from Pigeon (v10.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.urllauncher; @@ -190,15 +190,9 @@ public interface UrlLauncherApi { /** Opens the URL externally, returning true if successful. */ @NonNull Boolean launchUrl(@NonNull String url, @NonNull Map headers); - /** - * Opens the URL in an in-app Custom Tab or WebView, returning true if it opens successfully. - */ + /** Opens the URL in an in-app WebView, returning true if it opens successfully. */ @NonNull - Boolean openUrlInApp( - @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options); - - @NonNull - Boolean supportsCustomTabs(); + Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options); /** Closes the view opened by [openUrlInSafariViewController]. */ void closeWebView(); @@ -211,9 +205,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl", - getCodec()); + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -236,9 +228,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl", - getCodec()); + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { @@ -262,42 +252,16 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp", - getCodec()); + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList(); ArrayList args = (ArrayList) message; String urlArg = (String) args.get(0); - Boolean allowCustomTabArg = (Boolean) args.get(1); - WebViewOptions optionsArg = (WebViewOptions) args.get(2); - try { - Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg); - wrapped.add(0, output); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); + WebViewOptions optionsArg = (WebViewOptions) args.get(1); try { - Boolean output = api.supportsCustomTabs(); + Boolean output = api.openUrlInWebView(urlArg, optionsArg); wrapped.add(0, output); } catch (Throwable exception) { ArrayList wrappedError = wrapError(exception); @@ -312,9 +276,7 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable UrlLaunche { BasicMessageChannel channel = new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView", - getCodec()); + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { diff --git a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java index 028338c6981b..8ee9bffbb587 100644 --- a/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java +++ b/packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java @@ -16,11 +16,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.browser.customtabs.CustomTabsClient; import androidx.browser.customtabs.CustomTabsIntent; import io.flutter.plugins.urllauncher.Messages.UrlLauncherApi; import io.flutter.plugins.urllauncher.Messages.WebViewOptions; -import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -97,16 +95,14 @@ void setActivity(@Nullable Activity activity) { } @Override - public @NonNull Boolean openUrlInApp( - @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options) { + public @NonNull Boolean openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options) { ensureActivity(); assert activity != null; Bundle headersBundle = extractBundle(options.getHeaders()); - // Try to launch using Custom Tabs if they have the necessary functionality, unless the caller - // specifically requested a web view. - if (allowCustomTab && !containsRestrictedHeader(options.getHeaders())) { + // Try to launch using Custom Tabs if they have the necessary functionality. + if (!containsRestrictedHeader(options.getHeaders())) { Uri uri = Uri.parse(url); if (openCustomTab(activity, uri, headersBundle)) { return true; @@ -135,11 +131,6 @@ public void closeWebView() { applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE)); } - @Override - public @NonNull Boolean supportsCustomTabs() { - return CustomTabsClient.getPackageName(applicationContext, Collections.emptyList()) != null; - } - private static boolean openCustomTab( @NonNull Context context, @NonNull Uri uri, @NonNull Bundle headersBundle) { CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build(); diff --git a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java index 3bffbc614f09..b8bb3b4f21ef 100644 --- a/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java +++ b/packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/UrlLauncherTest.java @@ -130,7 +130,7 @@ public void launch_returnsTrue() { } @Test - public void openUrlInApp_opensUrlInWebViewIfNecessary() { + public void openWebView_opensUrl_inWebView() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -141,9 +141,8 @@ public void openUrlInApp_opensUrlInWebViewIfNecessary() { headers.put("key", "value"); boolean result = - api.openUrlInApp( + api.openUrlInWebView( url, - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(enableDomStorage) @@ -163,39 +162,15 @@ public void openUrlInApp_opensUrlInWebViewIfNecessary() { } @Test - public void openWebView_opensUrlInWebViewIfRequested() { + public void openWebView_opensUrl_inCustomTabs() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); String url = "https://flutter.dev"; boolean result = - api.openUrlInApp( + api.openUrlInWebView( url, - false, - new Messages.WebViewOptions.Builder() - .setEnableJavaScript(false) - .setEnableDomStorage(false) - .setHeaders(new HashMap<>()) - .build()); - - final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(activity).startActivity(intentCaptor.capture()); - assertTrue(result); - assertEquals(url, intentCaptor.getValue().getExtras().getString(WebViewActivity.URL_EXTRA)); - } - - @Test - public void openWebView_opensUrlInCustomTabs() { - Activity activity = mock(Activity.class); - UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); - api.setActivity(activity); - String url = "https://flutter.dev"; - - boolean result = - api.openUrlInApp( - url, - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -210,7 +185,7 @@ public void openWebView_opensUrlInCustomTabs() { } @Test - public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { + public void openWebView_opensUrl_inCustomTabs_withCORSAllowedHeader() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -220,9 +195,8 @@ public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { headers.put(headerKey, "text/plain"); boolean result = - api.openUrlInApp( + api.openUrlInWebView( url, - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -240,7 +214,7 @@ public void openWebView_opensUrlInCustomTabsWithCORSAllowedHeader() { } @Test - public void openWebView_fallsBackToWebViewIfCustomTabFails() { + public void openWebView_fallsbackTo_inWebView() { Activity activity = mock(Activity.class); UrlLauncher api = new UrlLauncher(ApplicationProvider.getApplicationContext()); api.setActivity(activity); @@ -250,9 +224,8 @@ public void openWebView_fallsBackToWebViewIfCustomTabFails() { .startActivity(any(), isNull()); // for custom tabs intent boolean result = - api.openUrlInApp( + api.openUrlInWebView( url, - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -278,9 +251,8 @@ public void openWebView_handlesEnableJavaScript() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInApp( + api.openUrlInWebView( "https://flutter.dev", - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(enableJavaScript) .setEnableDomStorage(false) @@ -305,9 +277,8 @@ public void openWebView_handlesHeaders() { headers.put(key1, "value"); headers.put(key2, "value2"); - api.openUrlInApp( + api.openUrlInWebView( "https://flutter.dev", - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -332,9 +303,8 @@ public void openWebView_handlesEnableDomStorage() { HashMap headers = new HashMap<>(); headers.put("key", "value"); - api.openUrlInApp( + api.openUrlInWebView( "https://flutter.dev", - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(enableDomStorage) @@ -357,9 +327,8 @@ public void openWebView_throwsForNoCurrentActivity() { assertThrows( Messages.FlutterError.class, () -> - api.openUrlInApp( + api.openUrlInWebView( "https://flutter.dev", - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) @@ -381,9 +350,8 @@ public void openWebView_returnsFalse() { .startActivity(any()); // for webview intent boolean result = - api.openUrlInApp( + api.openUrlInWebView( "https://flutter.dev", - true, new Messages.WebViewOptions.Builder() .setEnableJavaScript(false) .setEnableDomStorage(false) diff --git a/packages/url_launcher/url_launcher_android/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart index 36c32f2ba2b0..df28069ca1de 100644 --- a/packages/url_launcher/url_launcher_android/example/lib/main.dart +++ b/packages/url_launcher/url_launcher_android/example/lib/main.dart @@ -39,7 +39,6 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; bool _hasCallSupport = false; - bool _hasCustomTabSupport = false; Future? _launched; String _phone = ''; @@ -52,77 +51,73 @@ class _MyHomePageState extends State { _hasCallSupport = result; }); }); - // Check for Android Custom Tab support. - launcher - .supportsMode(PreferredLaunchMode.inAppBrowserView) - .then((bool result) { - setState(() { - _hasCustomTabSupport = result; - }); - }); } Future _launchInBrowser(String url) async { - if (!await launcher.launchUrl( - url, - const LaunchOptions(mode: PreferredLaunchMode.externalApplication), - )) { - throw Exception('Could not launch $url'); - } - } - - Future _launchInCustomTab(String url) async { - if (!await launcher.launchUrl( + if (!await launcher.launch( url, - const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView), + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}, )) { throw Exception('Could not launch $url'); } } Future _launchInWebView(String url) async { - if (!await launcher.launchUrl( + if (!await launcher.launch( url, - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView), + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}, )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithCustomHeaders(String url) async { - if (!await launcher.launchUrl( + if (!await launcher.launch( url, - const LaunchOptions( - mode: PreferredLaunchMode.inAppWebView, - webViewConfiguration: InAppWebViewConfiguration( - headers: {'my_header_key': 'my_header_value'}, - )), + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {'my_header_key': 'my_header_value'}, )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithoutJavaScript(String url) async { - if (!await launcher.launchUrl( + Future _launchInWebViewWithJavaScript(String url) async { + if (!await launcher.launch( url, - const LaunchOptions( - mode: PreferredLaunchMode.inAppWebView, - webViewConfiguration: InAppWebViewConfiguration( - enableJavaScript: false, - )), + useSafariVC: true, + useWebView: true, + enableJavaScript: true, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}, )) { throw Exception('Could not launch $url'); } } - Future _launchInWebViewWithoutDomStorage(String url) async { - if (!await launcher.launchUrl( + Future _launchInWebViewWithDomStorage(String url) async { + if (!await launcher.launch( url, - const LaunchOptions( - mode: PreferredLaunchMode.inAppWebView, - webViewConfiguration: InAppWebViewConfiguration( - enableDomStorage: false, - )), + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, )) { throw Exception('Could not launch $url'); } @@ -138,12 +133,22 @@ class _MyHomePageState extends State { Future _makePhoneCall(String phoneNumber) async { // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded. - // Just using 'tel:$phoneNumber' would create invalid URLs in some cases. + // Just using 'tel:$phoneNumber' would create invalid URLs in some cases, + // such as spaces in the input, which would cause `launch` to fail on some + // platforms. final Uri launchUri = Uri( scheme: 'tel', path: phoneNumber, ); - await launcher.launchUrl(launchUri.toString(), const LaunchOptions()); + await launcher.launch( + launchUri.toString(), + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: true, + headers: {}, + ); } @override @@ -181,45 +186,36 @@ class _MyHomePageState extends State { padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), - ElevatedButton( - onPressed: _hasCustomTabSupport - ? () => setState(() { - _launched = _launchInBrowser(toLaunch); - }) - : null, - child: const Text('Launch in browser'), - ), - const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInCustomTab(toLaunch); + _launched = _launchInBrowser(toLaunch); }), - child: const Text('Launch in Android Custom Tab'), + child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebView(toLaunch); }), - child: const Text('Launch in web view'), + child: const Text('Launch in app'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithCustomHeaders(toLaunch); }), - child: const Text('Launch in web view (Custom headers)'), + child: const Text('Launch in app (Custom headers)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithoutJavaScript(toLaunch); + _launched = _launchInWebViewWithJavaScript(toLaunch); }), - child: const Text('Launch in web view (JavaScript OFF)'), + child: const Text('Launch in app (JavaScript ON)'), ), ElevatedButton( onPressed: () => setState(() { - _launched = _launchInWebViewWithoutDomStorage(toLaunch); + _launched = _launchInWebViewWithDomStorage(toLaunch); }), - child: const Text('Launch in web view (DOM storage OFF)'), + child: const Text('Launch in app (DOM storage ON)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( @@ -229,7 +225,7 @@ class _MyHomePageState extends State { launcher.closeWebView(); }); }), - child: const Text('Launch in web view + close after 5 seconds'), + child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), diff --git a/packages/url_launcher/url_launcher_android/example/pubspec.yaml b/packages/url_launcher/url_launcher_android/example/pubspec.yaml index 15ccdee82476..e8aee1779b0d 100644 --- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml @@ -28,8 +28,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart index 9d6ce26ed85b..9aed8f7f60f0 100644 --- a/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart +++ b/packages/url_launcher/url_launcher_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.1.4), do not edit directly. +// Autogenerated from Pigeon (v10.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import @@ -79,8 +79,7 @@ class UrlLauncherApi { /// Returns true if the URL can definitely be launched. Future canLaunchUrl(String arg_url) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl', - codec, + 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url]) as List?; @@ -109,8 +108,7 @@ class UrlLauncherApi { Future launchUrl( String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl', - codec, + 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_url, arg_headers]) as List?; @@ -135,44 +133,15 @@ class UrlLauncherApi { } } - /// Opens the URL in an in-app Custom Tab or WebView, returning true if it - /// opens successfully. - Future openUrlInApp(String arg_url, bool arg_allowCustomTab, - WebViewOptions arg_options) async { + /// Opens the URL in an in-app WebView, returning true if it opens + /// successfully. + Future openUrlInWebView( + String arg_url, WebViewOptions arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp', - codec, + 'dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_url, arg_allowCustomTab, arg_options]) - as List?; - if (replyList == null) { - throw PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel.', - ); - } else if (replyList.length > 1) { - throw PlatformException( - code: replyList[0]! as String, - message: replyList[1] as String?, - details: replyList[2], - ); - } else if (replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (replyList[0] as bool?)!; - } - } - - Future supportsCustomTabs() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs', - codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send(null) as List?; + await channel.send([arg_url, arg_options]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -197,8 +166,7 @@ class UrlLauncherApi { /// Closes the view opened by [openUrlInSafariViewController]. Future closeWebView() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView', - codec, + 'dev.flutter.pigeon.UrlLauncherApi.closeWebView', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { diff --git a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart index f121084b7578..7b53b85f7936 100644 --- a/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart +++ b/packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart @@ -49,6 +49,8 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return _hostApi.closeWebView(); } + // TODO(stuartmorgan): Implement launchUrl, and make this a passthrough + // to launchUrl. See also https://github.com/flutter/flutter/issues/66721 @override Future launch( String url, { @@ -60,57 +62,16 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { required Map headers, String? webOnlyWindowName, }) async { - return launchUrl( - url, - LaunchOptions( - mode: useWebView - ? PreferredLaunchMode.inAppWebView - : PreferredLaunchMode.externalApplication, - webViewConfiguration: InAppWebViewConfiguration( - enableDomStorage: enableDomStorage, - enableJavaScript: enableJavaScript, - headers: headers))); - } - - @override - Future launchUrl(String url, LaunchOptions options) async { - final bool inApp; - switch (options.mode) { - case PreferredLaunchMode.inAppWebView: - case PreferredLaunchMode.inAppBrowserView: - inApp = true; - break; - case PreferredLaunchMode.externalApplication: - case PreferredLaunchMode.externalNonBrowserApplication: - // TODO(stuartmorgan): Add full support for - // externalNonBrowsingApplication; see - // https://github.com/flutter/flutter/issues/66721. - // Currently it's treated the same as externalApplication. - inApp = false; - break; - case PreferredLaunchMode.platformDefault: - // Intentionally treat any new values as platformDefault; see comment in - // supportsMode. - // ignore: no_default_cases - default: - // By default, open web URLs in the application. - inApp = url.startsWith('http:') || url.startsWith('https:'); - break; - } - final bool succeeded; - if (inApp) { - succeeded = await _hostApi.openUrlInApp( + if (useWebView) { + succeeded = await _hostApi.openUrlInWebView( url, - // Prefer custom tabs unless a webview was specifically requested. - options.mode != PreferredLaunchMode.inAppWebView, WebViewOptions( - enableJavaScript: options.webViewConfiguration.enableJavaScript, - enableDomStorage: options.webViewConfiguration.enableDomStorage, - headers: options.webViewConfiguration.headers)); + enableJavaScript: enableJavaScript, + enableDomStorage: enableDomStorage, + headers: headers)); } else { - succeeded = - await _hostApi.launchUrl(url, options.webViewConfiguration.headers); + succeeded = await _hostApi.launchUrl(url, headers); } // TODO(stuartmorgan): Remove this special handling as part of a // breaking change to rework failure handling across all platform. The @@ -123,29 +84,6 @@ class UrlLauncherAndroid extends UrlLauncherPlatform { return succeeded; } - @override - Future supportsMode(PreferredLaunchMode mode) async { - switch (mode) { - case PreferredLaunchMode.platformDefault: - case PreferredLaunchMode.inAppWebView: - case PreferredLaunchMode.externalApplication: - return true; - case PreferredLaunchMode.inAppBrowserView: - return _hostApi.supportsCustomTabs(); - // Default is a desired behavior here since support for new modes is - // always opt-in, and the enum lives in a different package, so silently - // adding "false" for new values is the correct behavior. - // ignore: no_default_cases - default: - return false; - } - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - return mode == PreferredLaunchMode.inAppWebView; - } - // Returns the part of [url] up to the first ':', or an empty string if there // is no ':'. This deliberately does not use [Uri] to extract the scheme // so that it works on strings that aren't actually valid URLs, since Android diff --git a/packages/url_launcher/url_launcher_android/pigeons/messages.dart b/packages/url_launcher/url_launcher_android/pigeons/messages.dart index d71844134092..84e507d70248 100644 --- a/packages/url_launcher/url_launcher_android/pigeons/messages.dart +++ b/packages/url_launcher/url_launcher_android/pigeons/messages.dart @@ -33,11 +33,9 @@ abstract class UrlLauncherApi { /// Opens the URL externally, returning true if successful. bool launchUrl(String url, Map headers); - /// Opens the URL in an in-app Custom Tab or WebView, returning true if it - /// opens successfully. - bool openUrlInApp(String url, bool allowCustomTab, WebViewOptions options); - - bool supportsCustomTabs(); + /// Opens the URL in an in-app WebView, returning true if it opens + /// successfully. + bool openUrlInWebView(String url, WebViewOptions options); /// Closes the view opened by [openUrlInSafariViewController]. void closeWebView(); diff --git a/packages/url_launcher/url_launcher_android/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml index 0984a1ab56f7..f5db21c93b4a 100644 --- a/packages/url_launcher/url_launcher_android/pubspec.yaml +++ b/packages/url_launcher/url_launcher_android/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_android description: Android implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.0 +version: 6.1.0 environment: sdk: ">=2.19.0 <4.0.0" flutter: ">=3.7.0" @@ -34,8 +34,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart index 2b331cbd027b..3b3d012a1683 100644 --- a/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart +++ b/packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart @@ -52,7 +52,7 @@ void main() { }); }); - group('legacy launch without webview', () { + group('launch without webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -88,7 +88,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'https://noactivity', + 'noactivity://', useSafariVC: false, useWebView: false, enableJavaScript: false, @@ -116,7 +116,7 @@ void main() { }); }); - group('legacy launch with webview', () { + group('launch with webview', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); final bool launched = await launcher.launch( @@ -130,7 +130,6 @@ void main() { ); expect(launched, true); expect(api.usedWebView, true); - expect(api.allowedCustomTab, false); expect(api.passedWebViewOptions?.enableDomStorage, false); expect(api.passedWebViewOptions?.enableJavaScript, false); expect(api.passedWebViewOptions?.headers, isEmpty); @@ -170,7 +169,7 @@ void main() { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); await expectLater( launcher.launch( - 'https://noactivity', + 'noactivity://scheme', useSafariVC: false, useWebView: true, enableJavaScript: false, @@ -198,198 +197,12 @@ void main() { }); }); - group('launch without webview', () { + group('closeWebView', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.externalApplication), - ); - expect(launched, true); - expect(api.usedWebView, false); - expect(api.passedWebViewOptions?.headers, isEmpty); - }); + await launcher.closeWebView(); - test('passes headers', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions( - mode: PreferredLaunchMode.externalApplication, - webViewConfiguration: InAppWebViewConfiguration( - headers: {'key': 'value'})), - ); - expect(api.passedWebViewOptions?.headers.length, 1); - expect(api.passedWebViewOptions?.headers['key'], 'value'); - }); - - test('passes through no-activity exception', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await expectLater( - launcher.launchUrl('https://noactivity', const LaunchOptions()), - throwsA(isA())); - }); - - test('throws if there is no handling activity', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await expectLater( - launcher.launchUrl('unknown://scheme', const LaunchOptions()), - throwsA(isA().having( - (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); - }); - }); - - group('launch with webview', () { - test('calls through', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl('http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); - expect(launched, true); - expect(api.usedWebView, true); - expect(api.allowedCustomTab, false); - expect(api.passedWebViewOptions?.enableDomStorage, true); - expect(api.passedWebViewOptions?.enableJavaScript, true); - expect(api.passedWebViewOptions?.headers, isEmpty); - }); - - test('passes enableJavaScript to webview', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions( - mode: PreferredLaunchMode.inAppWebView, - webViewConfiguration: - InAppWebViewConfiguration(enableJavaScript: false))); - - expect(api.passedWebViewOptions?.enableJavaScript, false); - }); - - test('passes enableDomStorage to webview', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions( - mode: PreferredLaunchMode.inAppWebView, - webViewConfiguration: - InAppWebViewConfiguration(enableDomStorage: false))); - - expect(api.passedWebViewOptions?.enableDomStorage, false); - }); - - test('passes through no-activity exception', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await expectLater( - launcher.launchUrl('https://noactivity', - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), - throwsA(isA())); - }); - - test('throws if there is no handling activity', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - await expectLater( - launcher.launchUrl('unknown://scheme', - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), - throwsA(isA().having( - (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); - }); - }); - - group('launch with custom tab', () { - test('calls through', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl('http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); - expect(launched, true); - expect(api.usedWebView, true); - expect(api.allowedCustomTab, true); - }); - }); - - group('launch with platform default', () { - test('uses custom tabs for http', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl( - 'http://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedWebView, true); - expect(api.allowedCustomTab, true); - }); - - test('uses custom tabs for https', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl( - 'https://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedWebView, true); - expect(api.allowedCustomTab, true); - }); - - test('uses external for other schemes', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - final bool launched = await launcher.launchUrl( - 'supportedcustomscheme://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedWebView, false); - }); - }); - - group('supportsMode', () { - test('returns true for platformDefault', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), - true); - }); - - test('returns true for external application', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - expect( - await launcher.supportsMode(PreferredLaunchMode.externalApplication), - true); - }); - - test('returns true for in app web view', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - expect( - await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); - }); - - test('returns true for in app browser view when available', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - api.hasCustomTabSupport = true; - expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - true); - }); - - test('returns false for in app browser view when not available', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - api.hasCustomTabSupport = false; - expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - false); - }); - }); - - group('supportsCloseForMode', () { - test('returns true for in app web view', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - expect( - await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), - true); - }); - - test('returns false for other modes', () async { - final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.externalApplication), - false); - expect( - await launcher.supportsCloseForMode( - PreferredLaunchMode.externalNonBrowserApplication), - false); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), - false); + expect(api.closed, true); }); }); } @@ -398,10 +211,8 @@ void main() { /// /// See _launch for the behaviors. class _FakeUrlLauncherApi implements UrlLauncherApi { - bool hasCustomTabSupport = true; WebViewOptions? passedWebViewOptions; bool? usedWebView; - bool? allowedCustomTab; bool? closed; /// A domain that will be treated as having no handler, even for http(s). @@ -426,29 +237,20 @@ class _FakeUrlLauncherApi implements UrlLauncherApi { } @override - Future openUrlInApp( - String url, bool allowCustomTab, WebViewOptions options) async { + Future openUrlInWebView(String url, WebViewOptions options) async { passedWebViewOptions = options; usedWebView = true; - allowedCustomTab = allowCustomTab; return _launch(url); } - @override - Future supportsCustomTabs() async { - return hasCustomTabSupport; - } - bool _launch(String url) { final String scheme = url.split(':')[0]; switch (scheme) { case 'http': case 'https': - case 'supportedcustomscheme': - if (url.endsWith('noactivity')) { - throw PlatformException(code: 'NO_ACTIVITY'); - } return !url.contains(specialHandlerDomain); + case 'noactivity': + throw PlatformException(code: 'NO_ACTIVITY'); default: return false; } diff --git a/packages/url_launcher/url_launcher_ios/CHANGELOG.md b/packages/url_launcher/url_launcher_ios/CHANGELOG.md index ae630129705d..ad52522d0021 100644 --- a/packages/url_launcher/url_launcher_ios/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_ios/CHANGELOG.md @@ -1,7 +1,3 @@ -## 6.2.0 - -* Implements `supportsMode` and `supportsCloseForMode`. - ## 6.1.5 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml index 793cde0144d5..d755c553f6af 100644 --- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml @@ -27,8 +27,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart index 66969787fba3..2f0e9f47b940 100644 --- a/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart +++ b/packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart @@ -45,79 +45,11 @@ class UrlLauncherIOS extends UrlLauncherPlatform { required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, - }) async { - final PreferredLaunchMode mode; + }) { if (useSafariVC) { - mode = PreferredLaunchMode.inAppBrowserView; - } else if (universalLinksOnly) { - mode = PreferredLaunchMode.externalNonBrowserApplication; - } else { - mode = PreferredLaunchMode.externalApplication; - } - return launchUrl( - url, - LaunchOptions( - mode: mode, - webViewConfiguration: InAppWebViewConfiguration( - enableDomStorage: enableDomStorage, - enableJavaScript: enableJavaScript, - headers: headers))); - } - - @override - Future launchUrl(String url, LaunchOptions options) async { - final bool inApp; - switch (options.mode) { - case PreferredLaunchMode.inAppWebView: - case PreferredLaunchMode.inAppBrowserView: - // The iOS implementation doesn't distinguish between these two modes; - // both are treated as inAppBrowserView. - inApp = true; - break; - case PreferredLaunchMode.externalApplication: - case PreferredLaunchMode.externalNonBrowserApplication: - inApp = false; - break; - case PreferredLaunchMode.platformDefault: - // Intentionally treat any new values as platformDefault; support for any - // new mode requires intentional opt-in, otherwise falling back is the - // documented behavior. - // ignore: no_default_cases - default: - // By default, open web URLs in the application. - inApp = url.startsWith('http:') || url.startsWith('https:'); - break; - } - - if (inApp) { return _hostApi.openUrlInSafariViewController(url); } else { - return _hostApi.launchUrl(url, - options.mode == PreferredLaunchMode.externalNonBrowserApplication); + return _hostApi.launchUrl(url, universalLinksOnly); } } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - switch (mode) { - case PreferredLaunchMode.platformDefault: - case PreferredLaunchMode.inAppWebView: - case PreferredLaunchMode.inAppBrowserView: - case PreferredLaunchMode.externalApplication: - case PreferredLaunchMode.externalNonBrowserApplication: - return true; - // Default is a desired behavior here since support for new modes is - // always opt-in, and the enum lives in a different package, so silently - // adding "false" for new values is the correct behavior. - // ignore: no_default_cases - default: - return false; - } - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - return mode == PreferredLaunchMode.inAppWebView || - mode == PreferredLaunchMode.inAppBrowserView; - } } diff --git a/packages/url_launcher/url_launcher_ios/pubspec.yaml b/packages/url_launcher/url_launcher_ios/pubspec.yaml index a76697008a92..8b45ed750549 100644 --- a/packages/url_launcher/url_launcher_ios/pubspec.yaml +++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_ios description: iOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 6.2.0 +version: 6.1.5 environment: sdk: ">=2.19.0 <4.0.0" @@ -33,8 +33,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart index bacea3132c13..9274173f90b4 100644 --- a/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart +++ b/packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart @@ -11,37 +11,36 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late _FakeUrlLauncherApi api; + group('UrlLauncherIOS', () { + late _FakeUrlLauncherApi api; - setUp(() { - api = _FakeUrlLauncherApi(); - }); + setUp(() { + api = _FakeUrlLauncherApi(); + }); - test('registers instance', () { - UrlLauncherIOS.registerWith(); - expect(UrlLauncherPlatform.instance, isA()); - }); + test('registers instance', () { + UrlLauncherIOS.registerWith(); + expect(UrlLauncherPlatform.instance, isA()); + }); - group('canLaunch', () { - test('handles success', () async { + test('canLaunch success', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect(await launcher.canLaunch('http://example.com/'), true); }); - test('handles failure', () async { + test('canLaunch failure', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect(await launcher.canLaunch('unknown://scheme'), false); }); - test('passes invalid URL PlatformException through', () async { + test('canLaunch invalid URL passes the PlatformException through', + () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); await expectLater(launcher.canLaunch('invalid://u r l'), throwsA(isA())); }); - }); - group('legacy launch', () { - test('handles success', () async { + test('launch success', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -57,7 +56,7 @@ void main() { expect(api.passedUniversalLinksOnly, false); }); - test('handles failure', () async { + test('launch failure', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -73,7 +72,7 @@ void main() { expect(api.passedUniversalLinksOnly, false); }); - test('passes invalid URL PlatformException through', () async { + test('launch invalid URL passes the PlatformException through', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); await expectLater( launcher.launch( @@ -88,7 +87,7 @@ void main() { throwsA(isA())); }); - test('force SafariVC is handled', () async { + test('launch force SafariVC', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -104,7 +103,7 @@ void main() { expect(api.usedSafariViewController, true); }); - test('universal links only is handled', () async { + test('launch universal links only', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -120,7 +119,7 @@ void main() { expect(api.passedUniversalLinksOnly, true); }); - test('disallowing SafariVC is handled', () async { + test('launch force SafariVC to false', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); expect( await launcher.launch( @@ -135,171 +134,11 @@ void main() { true); expect(api.usedSafariViewController, false); }); - }); - - test('closeWebView calls through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await launcher.closeWebView(); - expect(api.closed, true); - }); - - group('launch without webview', () { - test('calls through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.externalApplication), - ); - expect(launched, true); - expect(api.usedSafariViewController, false); - }); - - test('passes invalid URL PlatformException through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await expectLater( - launcher.launchUrl('invalid://u r l', const LaunchOptions()), - throwsA(isA())); - }); - }); - - group('launch with Safari view controller', () { - test('calls through with inAppWebView', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl('http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)); - expect(launched, true); - expect(api.usedSafariViewController, true); - }); - - test('calls through with inAppBrowserView', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl('http://example.com/', - const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView)); - expect(launched, true); - expect(api.usedSafariViewController, true); - }); - - test('passes invalid URL PlatformException through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await expectLater( - launcher.launchUrl('invalid://u r l', - const LaunchOptions(mode: PreferredLaunchMode.inAppWebView)), - throwsA(isA())); - }); - }); - - group('launch with universal links', () { - test('calls through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl( - 'http://example.com/', - const LaunchOptions( - mode: PreferredLaunchMode.externalNonBrowserApplication), - ); - expect(launched, true); - expect(api.usedSafariViewController, false); - expect(api.passedUniversalLinksOnly, true); - }); - - test('passes invalid URL PlatformException through', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - await expectLater( - launcher.launchUrl( - 'invalid://u r l', - const LaunchOptions( - mode: PreferredLaunchMode.externalNonBrowserApplication)), - throwsA(isA())); - }); - }); - - group('launch with platform default', () { - test('uses Safari view controller for http', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl( - 'http://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedSafariViewController, true); - }); - - test('uses Safari view controller for https', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl( - 'https://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedSafariViewController, true); - }); - - test('uses standard external for other schemes', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - final bool launched = await launcher.launchUrl( - 'supportedcustomscheme://example.com/', const LaunchOptions()); - expect(launched, true); - expect(api.usedSafariViewController, false); - expect(api.passedUniversalLinksOnly, false); - }); - }); - - group('supportsMode', () { - test('returns true for platformDefault', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), - true); - }); - - test('returns true for external application', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher.supportsMode(PreferredLaunchMode.externalApplication), - true); - }); - - test('returns true for external non-browser application', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher - .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), - true); - }); - test('returns true for in app web view', () async { + test('closeWebView default behavior', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher.supportsMode(PreferredLaunchMode.inAppWebView), true); - }); - - test('returns true for in app browser view', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - true); - }); - }); - - group('supportsCloseForMode', () { - test('returns true for in app web view', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher.supportsCloseForMode(PreferredLaunchMode.inAppWebView), - true); - }); - - test('returns true for in app browser view', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.inAppBrowserView), - true); - }); - - test('returns false for other modes', () async { - final UrlLauncherIOS launcher = UrlLauncherIOS(api: api); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.externalApplication), - false); - expect( - await launcher.supportsCloseForMode( - PreferredLaunchMode.externalNonBrowserApplication), - false); + await launcher.closeWebView(); + expect(api.closed, true); }); }); } @@ -340,7 +179,6 @@ class _FakeUrlLauncherApi implements UrlLauncherApi { switch (scheme) { case 'http': case 'https': - case 'supportedcustomscheme': return true; case 'invalid': throw PlatformException(code: 'argument_error'); diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md index 538fb7737a36..a7f308737b4c 100644 --- a/packages/url_launcher/url_launcher_linux/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -1,7 +1,3 @@ -## 3.1.0 - -* Implements `supportsMode` and `supportsCloseForMode`. - ## 3.0.6 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml index ff6a34aabdf6..0dd4a4cf8a3f 100644 --- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -26,8 +26,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart index 286ac923ce9b..87ef3142e3f6 100644 --- a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart +++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart @@ -51,16 +51,4 @@ class UrlLauncherLinux extends UrlLauncherPlatform { }, ).then((bool? value) => value ?? false); } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - return mode == PreferredLaunchMode.platformDefault || - mode == PreferredLaunchMode.externalApplication; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - // No supported mode is closeable. - return false; - } } diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml index 10713b33c21b..2649beeecb99 100644 --- a/packages/url_launcher/url_launcher_linux/pubspec.yaml +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_linux description: Linux implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.1.0 +version: 3.0.6 environment: sdk: ">=2.19.0 <4.0.0" @@ -31,8 +31,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart index c7e6c8e328c6..4e62cc446199 100644 --- a/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart +++ b/packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart @@ -10,7 +10,7 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('UrlLauncherLinux', () { + group('$UrlLauncherLinux', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_linux'); final List log = []; @@ -142,47 +142,6 @@ void main() { expect(launched, false); }); - - group('supportsMode', () { - test('returns true for platformDefault', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), - true); - }); - - test('returns true for external application', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - expect( - await launcher - .supportsMode(PreferredLaunchMode.externalApplication), - true); - }); - - test('returns false for other modes', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - expect( - await launcher.supportsMode( - PreferredLaunchMode.externalNonBrowserApplication), - false); - expect( - await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - false); - expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), - false); - }); - }); - - test('supportsCloseForMode returns false', () async { - final UrlLauncherLinux launcher = UrlLauncherLinux(); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.platformDefault), - false); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.externalApplication), - false); - }); }); } diff --git a/packages/url_launcher/url_launcher_macos/CHANGELOG.md b/packages/url_launcher/url_launcher_macos/CHANGELOG.md index ac8a05af5c9e..e2f5c6855ed1 100644 --- a/packages/url_launcher/url_launcher_macos/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_macos/CHANGELOG.md @@ -1,7 +1,3 @@ -## 3.1.0 - -* Implements `supportsMode` and `supportsCloseForMode`. - ## 3.0.7 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml index bbe82f8c5054..0ad239027827 100644 --- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml @@ -26,8 +26,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart index 1d229737c34c..55c07b798cd8 100644 --- a/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart +++ b/packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart @@ -57,18 +57,6 @@ class UrlLauncherMacOS extends UrlLauncherPlatform { return result.value; } - @override - Future supportsMode(PreferredLaunchMode mode) async { - return mode == PreferredLaunchMode.platformDefault || - mode == PreferredLaunchMode.externalApplication; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - // No supported mode is closeable. - return false; - } - Exception _getInvalidUrlException(String url) { // TODO(stuartmorgan): Make this an actual ArgumentError. This should be // coordinated across all platforms as a breaking change to have them all diff --git a/packages/url_launcher/url_launcher_macos/pubspec.yaml b/packages/url_launcher/url_launcher_macos/pubspec.yaml index 6ba3d715d279..23d5f6a2f474 100644 --- a/packages/url_launcher/url_launcher_macos/pubspec.yaml +++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_macos description: macOS implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.1.0 +version: 3.0.7 environment: sdk: ">=2.19.0 <4.0.0" @@ -33,8 +33,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart index e9cc3c6c6dc9..a7147af7749b 100644 --- a/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart +++ b/packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart @@ -106,47 +106,6 @@ void main() { throwsA(isA())); }); }); - - group('supportsMode', () { - test('returns true for platformDefault', () async { - final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); - expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), - true); - }); - - test('returns true for external application', () async { - final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); - expect( - await launcher - .supportsMode(PreferredLaunchMode.externalApplication), - true); - }); - - test('returns false for other modes', () async { - final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); - expect( - await launcher.supportsMode( - PreferredLaunchMode.externalNonBrowserApplication), - false); - expect( - await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - false); - expect(await launcher.supportsMode(PreferredLaunchMode.inAppWebView), - false); - }); - }); - - test('supportsCloseForMode returns false', () async { - final UrlLauncherMacOS launcher = UrlLauncherMacOS(api: api); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.platformDefault), - false); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.externalApplication), - false); - }); }); } diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index 99fe6599bc5a..4fd4abb5ec96 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,7 +1,3 @@ -## 2.1.0 - -* Implements `supportsMode` and `supportsCloseForMode`. - ## 2.0.20 * Migrates to `dart:ui_web` APIs. diff --git a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart index a6ade3ebd3b4..f2dac2bb1434 100644 --- a/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart +++ b/packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart @@ -8,7 +8,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; import 'url_launcher_web_test.mocks.dart'; @@ -199,30 +198,5 @@ void main() { }); }); }); - - group('supportsMode', () { - testWidgets('returns true for platformDefault', (WidgetTester _) async { - expect(plugin.supportsMode(PreferredLaunchMode.platformDefault), - completion(isTrue)); - }); - - testWidgets('returns false for other modes', (WidgetTester _) async { - expect(plugin.supportsMode(PreferredLaunchMode.externalApplication), - completion(isFalse)); - expect( - plugin.supportsMode( - PreferredLaunchMode.externalNonBrowserApplication), - completion(isFalse)); - expect(plugin.supportsMode(PreferredLaunchMode.inAppBrowserView), - completion(isFalse)); - expect(plugin.supportsMode(PreferredLaunchMode.inAppWebView), - completion(isFalse)); - }); - }); - - testWidgets('supportsCloseForMode returns false', (WidgetTester _) async { - expect(plugin.supportsCloseForMode(PreferredLaunchMode.platformDefault), - completion(isFalse)); - }); }); } diff --git a/packages/url_launcher/url_launcher_web/example/pubspec.yaml b/packages/url_launcher/url_launcher_web/example/pubspec.yaml index adc6f4da5f32..9915164471a0 100644 --- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml @@ -19,8 +19,3 @@ dev_dependencies: url_launcher_platform_interface: ^2.0.3 url_launcher_web: path: ../ - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart index c757e96f08bc..be40539d4737 100644 --- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart +++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart @@ -90,17 +90,4 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { openNewWindow(url, webOnlyWindowName: webOnlyWindowName); return true; } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - // Web doesn't allow any control over the destination beyond - // webOnlyWindowName, so don't claim support for any mode beyond default. - return mode == PreferredLaunchMode.platformDefault; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - // No supported mode is closeable. - return false; - } } diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index bc38daf8e795..fce7da421b1b 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web description: Web platform implementation of url_launcher repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 2.1.0 +version: 2.0.20 environment: sdk: ">=3.1.0 <4.0.0" @@ -32,8 +32,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md index b63c9e19e27c..933cc1055274 100644 --- a/packages/url_launcher/url_launcher_windows/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_windows/CHANGELOG.md @@ -1,7 +1,3 @@ -## 3.1.0 - -* Implements `supportsMode` and `supportsCloseForMode`. - ## 3.0.8 * Adds pub topics to package metadata. diff --git a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml index b9e23486d631..77106bc48a74 100644 --- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml @@ -26,8 +26,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart index 790a45149be8..41c403e56f8e 100644 --- a/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart +++ b/packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart @@ -45,16 +45,4 @@ class UrlLauncherWindows extends UrlLauncherPlatform { // Failure is handled via a PlatformException from `launchUrl`. return true; } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - return mode == PreferredLaunchMode.platformDefault || - mode == PreferredLaunchMode.externalApplication; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - // No supported mode is closeable. - return false; - } } diff --git a/packages/url_launcher/url_launcher_windows/pubspec.yaml b/packages/url_launcher/url_launcher_windows/pubspec.yaml index 49a9e8c5d8ee..6b17b9d7c55b 100644 --- a/packages/url_launcher/url_launcher_windows/pubspec.yaml +++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_windows description: Windows implementation of the url_launcher plugin. repository: https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%22 -version: 3.1.0 +version: 3.0.8 environment: sdk: ">=2.19.0 <4.0.0" @@ -32,8 +32,3 @@ topics: - os-integration - url-launcher - urls - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {url_launcher_platform_interface: {path: ../../url_launcher/url_launcher_platform_interface}} diff --git a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart index 0be939bad19b..7f48f64fa92c 100644 --- a/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart +++ b/packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart @@ -77,45 +77,6 @@ void main() { expect(api.argument, 'http://example.com/'); }); }); - - group('supportsMode', () { - test('returns true for platformDefault', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); - expect(await launcher.supportsMode(PreferredLaunchMode.platformDefault), - true); - }); - - test('returns true for external application', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); - expect( - await launcher.supportsMode(PreferredLaunchMode.externalApplication), - true); - }); - - test('returns false for other modes', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); - expect( - await launcher - .supportsMode(PreferredLaunchMode.externalNonBrowserApplication), - false); - expect(await launcher.supportsMode(PreferredLaunchMode.inAppBrowserView), - false); - expect( - await launcher.supportsMode(PreferredLaunchMode.inAppWebView), false); - }); - }); - - test('supportsCloseForMode returns false', () async { - final UrlLauncherWindows launcher = UrlLauncherWindows(api: api); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.platformDefault), - false); - expect( - await launcher - .supportsCloseForMode(PreferredLaunchMode.externalApplication), - false); - }); } class _FakeUrlLauncherApi implements UrlLauncherApi {