[url_launcher] Add an `inAppBrowserView` mode in implementations (#5211)
Implementation package portion of https://github.com/flutter/packages/pull/5155
This adds:
- Android support for the new `inAppBrowserView` launch mode which is distinct from `inAppWebView`, so that use cases that require programatic close can specifically request `inAppWebView` instead.
- The default for web links is the new `inAppBrowserView` since that gives better results in most cases.
- `inAppBrowserView` will still automatically fall back to `inAppBrowserView` in cases where it's not supported. (In the future, we might want to tune that based on feedback. We could instead have three modes: the webview-only mode we now have, the dynamic mode we now have iff the user requested `platformDefault`, and a new Android Custom Tabs-only if it was explicitly requested which would fail if it doesn't work.)
- iOS support for treating `inAppBrowserView` as identical to `inAppWebView`, since in practice that's what its `inAppWebView` mode has always been.
- Support on all platforms for the new `supportsMode` and `supportsCloseForMode` support query methods.
Fixes https://github.com/flutter/flutter/issues/134208
diff --git a/packages/url_launcher/url_launcher_android/CHANGELOG.md b/packages/url_launcher/url_launcher_android/CHANGELOG.md
index b9270e6..cf0da2f 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.1
* Updates annotations lib to 1.7.0.
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 eab0d87..f2294f0 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;
@@ -190,9 +190,15 @@
/** Opens the URL externally, returning true if successful. */
@NonNull
Boolean launchUrl(@NonNull String url, @NonNull Map<String, String> headers);
- /** Opens the URL in an in-app 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 openUrlInWebView(@NonNull String url, @NonNull WebViewOptions options);
+ Boolean openUrlInApp(
+ @NonNull String url, @NonNull Boolean allowCustomTab, @NonNull WebViewOptions options);
+
+ @NonNull
+ Boolean supportsCustomTabs();
/** Closes the view opened by [openUrlInSafariViewController]. */
void closeWebView();
@@ -205,7 +211,9 @@
{
BasicMessageChannel<Object> 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) -> {
@@ -228,7 +236,9 @@
{
BasicMessageChannel<Object> 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) -> {
@@ -252,16 +262,19 @@
{
BasicMessageChannel<Object> 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<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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.openUrlInWebView(urlArg, optionsArg);
+ Boolean output = api.openUrlInApp(urlArg, allowCustomTabArg, optionsArg);
wrapped.add(0, output);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
@@ -276,7 +289,32 @@
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
- binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", getCodec());
+ binaryMessenger,
+ "dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs",
+ getCodec());
+ if (api != null) {
+ channel.setMessageHandler(
+ (message, reply) -> {
+ ArrayList<Object> wrapped = new ArrayList<Object>();
+ try {
+ Boolean output = api.supportsCustomTabs();
+ wrapped.add(0, output);
+ } catch (Throwable exception) {
+ ArrayList<Object> wrappedError = wrapError(exception);
+ wrapped = wrappedError;
+ }
+ reply.reply(wrapped);
+ });
+ } else {
+ channel.setMessageHandler(null);
+ }
+ }
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.url_launcher_android.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 8ee9bff..028338c 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 @@
}
@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 @@
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 b8bb3b4..3bffbc6 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 @@
}
@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 @@
headers.put("key", "value");
boolean result =
- api.openUrlInWebView(
+ api.openUrlInApp(
url,
+ true,
new Messages.WebViewOptions.Builder()
.setEnableJavaScript(enableJavaScript)
.setEnableDomStorage(enableDomStorage)
@@ -162,15 +163,39 @@
}
@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(
+ api.openUrlInApp(
url,
+ false,
+ new Messages.WebViewOptions.Builder()
+ .setEnableJavaScript(false)
+ .setEnableDomStorage(false)
+ .setHeaders(new HashMap<>())
+ .build());
+
+ final ArgumentCaptor<Intent> 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)
@@ -185,7 +210,7 @@
}
@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 @@
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 @@
}
@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 @@
.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 @@
HashMap<String, String> 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 @@
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 @@
HashMap<String, String> 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 @@
assertThrows(
Messages.FlutterError.class,
() ->
- api.openUrlInWebView(
+ api.openUrlInApp(
"https://flutter.dev",
+ true,
new Messages.WebViewOptions.Builder()
.setEnableJavaScript(false)
.setEnableDomStorage(false)
@@ -350,8 +381,9 @@
.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/example/lib/main.dart b/packages/url_launcher/url_launcher_android/example/lib/main.dart
index df28069..36c32f2 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 _MyHomePageState extends State<MyHomePage> {
final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance;
bool _hasCallSupport = false;
+ bool _hasCustomTabSupport = false;
Future<void>? _launched;
String _phone = '';
@@ -51,73 +52,77 @@
_hasCallSupport = result;
});
});
+ // Check for Android Custom Tab support.
+ launcher
+ .supportsMode(PreferredLaunchMode.inAppBrowserView)
+ .then((bool result) {
+ setState(() {
+ _hasCustomTabSupport = result;
+ });
+ });
}
Future<void> _launchInBrowser(String url) async {
- if (!await launcher.launch(
+ if (!await launcher.launchUrl(
url,
- useSafariVC: false,
- useWebView: false,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: <String, String>{},
+ const LaunchOptions(mode: PreferredLaunchMode.externalApplication),
+ )) {
+ throw Exception('Could not launch $url');
+ }
+ }
+
+ Future<void> _launchInCustomTab(String url) async {
+ if (!await launcher.launchUrl(
+ url,
+ const LaunchOptions(mode: PreferredLaunchMode.inAppBrowserView),
)) {
throw Exception('Could not launch $url');
}
}
Future<void> _launchInWebView(String url) async {
- if (!await launcher.launch(
+ if (!await launcher.launchUrl(
url,
- useSafariVC: true,
- useWebView: true,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: <String, String>{},
+ const LaunchOptions(mode: PreferredLaunchMode.inAppWebView),
)) {
throw Exception('Could not launch $url');
}
}
Future<void> _launchInWebViewWithCustomHeaders(String url) async {
- if (!await launcher.launch(
+ if (!await launcher.launchUrl(
url,
- useSafariVC: true,
- useWebView: true,
- enableJavaScript: false,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: <String, String>{'my_header_key': 'my_header_value'},
+ const LaunchOptions(
+ mode: PreferredLaunchMode.inAppWebView,
+ webViewConfiguration: InAppWebViewConfiguration(
+ headers: <String, String>{'my_header_key': 'my_header_value'},
+ )),
)) {
throw Exception('Could not launch $url');
}
}
- Future<void> _launchInWebViewWithJavaScript(String url) async {
- if (!await launcher.launch(
+ Future<void> _launchInWebViewWithoutJavaScript(String url) async {
+ if (!await launcher.launchUrl(
url,
- useSafariVC: true,
- useWebView: true,
- enableJavaScript: true,
- enableDomStorage: false,
- universalLinksOnly: false,
- headers: <String, String>{},
+ const LaunchOptions(
+ mode: PreferredLaunchMode.inAppWebView,
+ webViewConfiguration: InAppWebViewConfiguration(
+ enableJavaScript: false,
+ )),
)) {
throw Exception('Could not launch $url');
}
}
- Future<void> _launchInWebViewWithDomStorage(String url) async {
- if (!await launcher.launch(
+ Future<void> _launchInWebViewWithoutDomStorage(String url) async {
+ if (!await launcher.launchUrl(
url,
- useSafariVC: true,
- useWebView: true,
- enableJavaScript: false,
- enableDomStorage: true,
- universalLinksOnly: false,
- headers: <String, String>{},
+ const LaunchOptions(
+ mode: PreferredLaunchMode.inAppWebView,
+ webViewConfiguration: InAppWebViewConfiguration(
+ enableDomStorage: false,
+ )),
)) {
throw Exception('Could not launch $url');
}
@@ -133,22 +138,12 @@
Future<void> _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: <String, String>{},
- );
+ await launcher.launchUrl(launchUri.toString(), const LaunchOptions());
}
@override
@@ -187,35 +182,44 @@
child: Text(toLaunch),
),
ElevatedButton(
- onPressed: () => setState(() {
- _launched = _launchInBrowser(toLaunch);
- }),
+ 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);
+ }),
+ 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 @@
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<void>(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 e8aee17..d234884 100644
--- a/packages/url_launcher/url_launcher_android/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_android/example/pubspec.yaml
@@ -16,7 +16,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 9aed8f7..9d6ce26 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
@@ -79,7 +79,8 @@
/// Returns true if the URL can definitely be launched.
Future<bool> canLaunchUrl(String arg_url) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec,
+ 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.canLaunchUrl',
+ codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_url]) as List<Object?>?;
@@ -108,7 +109,8 @@
Future<bool> launchUrl(
String arg_url, Map<String?, String?> arg_headers) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec,
+ 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.launchUrl',
+ codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_url, arg_headers]) as List<Object?>?;
@@ -133,15 +135,44 @@
}
}
- /// Opens the URL in an in-app WebView, returning true if it opens
- /// successfully.
- Future<bool> 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<bool> openUrlInApp(String arg_url, bool arg_allowCustomTab,
+ WebViewOptions arg_options) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView', codec,
+ 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.openUrlInApp',
+ codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
- await channel.send(<Object?>[arg_url, arg_options]) as List<Object?>?;
+ await channel.send(<Object?>[arg_url, arg_allowCustomTab, arg_options])
+ as List<Object?>?;
+ 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<bool> supportsCustomTabs() async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.supportsCustomTabs',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
@@ -166,7 +197,8 @@
/// Closes the view opened by [openUrlInSafariViewController].
Future<void> closeWebView() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
- 'dev.flutter.pigeon.UrlLauncherApi.closeWebView', codec,
+ 'dev.flutter.pigeon.url_launcher_android.UrlLauncherApi.closeWebView',
+ codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
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 7b53b85..f121084 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 @@
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<bool> launch(
String url, {
@@ -62,16 +60,57 @@
required Map<String, String> headers,
String? webOnlyWindowName,
}) async {
+ return launchUrl(
+ url,
+ LaunchOptions(
+ mode: useWebView
+ ? PreferredLaunchMode.inAppWebView
+ : PreferredLaunchMode.externalApplication,
+ webViewConfiguration: InAppWebViewConfiguration(
+ enableDomStorage: enableDomStorage,
+ enableJavaScript: enableJavaScript,
+ headers: headers)));
+ }
+
+ @override
+ Future<bool> 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 @@
return succeeded;
}
+ @override
+ Future<bool> 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<bool> 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 84e507d..d718441 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 @@
/// Opens the URL externally, returning true if successful.
bool launchUrl(String url, Map<String, String> 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/pubspec.yaml b/packages/url_launcher/url_launcher_android/pubspec.yaml
index 09e865d..5b5d06e 100644
--- a/packages/url_launcher/url_launcher_android/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_android/pubspec.yaml
@@ -2,7 +2,7 @@
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.1
+version: 6.2.0
environment:
sdk: ">=2.19.0 <4.0.0"
flutter: ">=3.7.0"
@@ -19,7 +19,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 3b3d012..2b331cb 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 @@
});
});
- 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 @@
final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api);
await expectLater(
launcher.launch(
- 'noactivity://',
+ 'https://noactivity',
useSafariVC: false,
useWebView: false,
enableJavaScript: false,
@@ -116,7 +116,7 @@
});
});
- 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 @@
);
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 @@
final UrlLauncherAndroid launcher = UrlLauncherAndroid(api: api);
await expectLater(
launcher.launch(
- 'noactivity://scheme',
+ 'https://noactivity',
useSafariVC: false,
useWebView: true,
enableJavaScript: false,
@@ -197,12 +198,198 @@
});
});
- 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: <String, String>{'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<PlatformException>()));
+ });
+
+ 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<PlatformException>().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<PlatformException>()));
+ });
+
+ 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<PlatformException>().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 @@
///
/// 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 @@
}
@override
- Future<bool> openUrlInWebView(String url, WebViewOptions options) async {
+ Future<bool> openUrlInApp(
+ String url, bool allowCustomTab, WebViewOptions options) async {
passedWebViewOptions = options;
usedWebView = true;
+ allowedCustomTab = allowCustomTab;
return _launch(url);
}
+ @override
+ Future<bool> 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 ad52522..ae63012 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/example/pubspec.yaml b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
index d755c55..3daaa9a 100644
--- a/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_ios/example/pubspec.yaml
@@ -16,7 +16,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 2f0e9f4..6696978 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 @@
required bool universalLinksOnly,
required Map<String, String> 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<bool> 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<bool> 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<bool> 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 8b45ed7..6047568 100644
--- a/packages/url_launcher/url_launcher_ios/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_ios/pubspec.yaml
@@ -2,7 +2,7 @@
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"
@@ -19,7 +19,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 9274173..bacea31 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 @@
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<UrlLauncherIOS>());
- });
+ test('registers instance', () {
+ UrlLauncherIOS.registerWith();
+ expect(UrlLauncherPlatform.instance, isA<UrlLauncherIOS>());
+ });
- 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<PlatformException>()));
});
+ });
- 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 @@
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 @@
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 @@
throwsA(isA<PlatformException>()));
});
- 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 @@
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 @@
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 @@
true);
expect(api.usedSafariViewController, false);
});
+ });
- test('closeWebView default behavior', () async {
+ 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);
- await launcher.closeWebView();
- expect(api.closed, true);
+ 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<PlatformException>()));
+ });
+ });
+
+ 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<PlatformException>()));
+ });
+ });
+
+ 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<PlatformException>()));
+ });
+ });
+
+ 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 {
+ 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);
});
});
}
@@ -179,6 +340,7 @@
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 a7f3087..538fb77 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/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
index 0dd4a4c..92aff2e 100644
--- a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml
@@ -16,7 +16,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- url_launcher_platform_interface: ^2.0.0
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 87ef314..286ac92 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 @@
},
).then((bool? value) => value ?? false);
}
+
+ @override
+ Future<bool> supportsMode(PreferredLaunchMode mode) async {
+ return mode == PreferredLaunchMode.platformDefault ||
+ mode == PreferredLaunchMode.externalApplication;
+ }
+
+ @override
+ Future<bool> 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 2649bee..315ecd2 100644
--- a/packages/url_launcher/url_launcher_linux/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml
@@ -2,7 +2,7 @@
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"
@@ -19,7 +19,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 4e62cc4..c7e6c8e 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 @@
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
- group('$UrlLauncherLinux', () {
+ group('UrlLauncherLinux', () {
const MethodChannel channel =
MethodChannel('plugins.flutter.io/url_launcher_linux');
final List<MethodCall> log = <MethodCall>[];
@@ -142,6 +142,47 @@
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 e2f5c68..ac8a05a 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/example/pubspec.yaml b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml
index 0ad2390..3c98f47 100644
--- a/packages/url_launcher/url_launcher_macos/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_macos/example/pubspec.yaml
@@ -16,7 +16,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
- url_launcher_platform_interface: ^2.0.0
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 55c07b7..1d22973 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 @@
return result.value;
}
+ @override
+ Future<bool> supportsMode(PreferredLaunchMode mode) async {
+ return mode == PreferredLaunchMode.platformDefault ||
+ mode == PreferredLaunchMode.externalApplication;
+ }
+
+ @override
+ Future<bool> 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 23d5f6a..ba7066c 100644
--- a/packages/url_launcher/url_launcher_macos/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_macos/pubspec.yaml
@@ -2,7 +2,7 @@
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"
@@ -20,7 +20,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 a7147af..e9cc3c6 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 @@
throwsA(isA<PlatformException>()));
});
});
+
+ 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 836afdc..76979c9 100644
--- a/packages/url_launcher/url_launcher_web/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.2.0
+
+* Implements `supportsMode` and `supportsCloseForMode`.
+
## 2.1.0
* Adds `launchUrl` implementation.
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 c909186..994e3b2 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: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';
@@ -218,5 +219,30 @@
});
});
});
+
+ 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 9915164..d096423 100644
--- a/packages/url_launcher/url_launcher_web/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/example/pubspec.yaml
@@ -16,6 +16,6 @@
integration_test:
sdk: flutter
mockito: 5.4.1
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
url_launcher_web:
path: ../
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 bf96bf2..0dd1012 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
@@ -111,4 +111,17 @@
final String? windowName = options.webOnlyWindowName;
return openNewWindow(url, webOnlyWindowName: windowName) != null;
}
+
+ @override
+ Future<bool> 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<bool> 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 95dd915..b70a094 100644
--- a/packages/url_launcher/url_launcher_web/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_web/pubspec.yaml
@@ -2,7 +2,7 @@
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.2.0
environment:
sdk: ">=3.1.0 <4.0.0"
@@ -21,7 +21,7 @@
sdk: flutter
flutter_web_plugins:
sdk: flutter
- url_launcher_platform_interface: ^2.1.0
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
diff --git a/packages/url_launcher/url_launcher_windows/CHANGELOG.md b/packages/url_launcher/url_launcher_windows/CHANGELOG.md
index 933cc10..b63c9e1 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/example/pubspec.yaml b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml
index 77106bc..08bf314 100644
--- a/packages/url_launcher/url_launcher_windows/example/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_windows/example/pubspec.yaml
@@ -9,7 +9,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.0
+ url_launcher_platform_interface: ^2.2.0
url_launcher_windows:
# When depending on this package from a real application you should use:
# url_launcher_windows: ^x.y.z
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 41c403e..790a451 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 @@
// Failure is handled via a PlatformException from `launchUrl`.
return true;
}
+
+ @override
+ Future<bool> supportsMode(PreferredLaunchMode mode) async {
+ return mode == PreferredLaunchMode.platformDefault ||
+ mode == PreferredLaunchMode.externalApplication;
+ }
+
+ @override
+ Future<bool> 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 6b17b9d..118d928 100644
--- a/packages/url_launcher/url_launcher_windows/pubspec.yaml
+++ b/packages/url_launcher/url_launcher_windows/pubspec.yaml
@@ -2,7 +2,7 @@
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"
@@ -19,7 +19,7 @@
dependencies:
flutter:
sdk: flutter
- url_launcher_platform_interface: ^2.0.3
+ url_launcher_platform_interface: ^2.2.0
dev_dependencies:
flutter_test:
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 7f48f64..0be939b 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 @@
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 {