diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 627c60d2fd1a..9ef34c528aad 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 6.3.0 +* Adds `BrowserConfiguration` parameter, to configure in-app browser views, such as Android Custom Tabs or SFSafariViewController. +* Adds `showTitle` to `BrowserConfiguration`, to allow showing webpage titles in in-app browser views. * Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. ## 6.2.6 diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index 40307a3f715d..e3a353f81edf 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -74,6 +74,16 @@ class _MyHomePageState extends State { } } + Future _launchInAppWithBrowserOptions(Uri url) async { + if (!await launchUrl( + url, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + )) { + throw Exception('Could not launch $url'); + } + } + Future _launchAsInAppWebViewWithCustomHeaders(Uri url) async { if (!await launchUrl( url, @@ -220,6 +230,13 @@ class _MyHomePageState extends State { child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInAppWithBrowserOptions(toLaunch); + }), + child: const Text('Launch in app with title displayed'), + ), + const Padding(padding: EdgeInsets.all(16.0)), Link( uri: Uri.parse( 'https://pub.dev/documentation/url_launcher/latest/link/link-library.html'), 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..0e27da35fe7a 100644 --- a/packages/url_launcher/url_launcher/lib/src/type_conversion.dart +++ b/packages/url_launcher/url_launcher/lib/src/type_conversion.dart @@ -8,7 +8,8 @@ import 'types.dart'; /// Converts an (app-facing) [WebViewConfiguration] to a (platform interface) /// [InAppWebViewConfiguration]. -InAppWebViewConfiguration convertConfiguration(WebViewConfiguration config) { +InAppWebViewConfiguration convertWebViewConfiguration( + WebViewConfiguration config) { return InAppWebViewConfiguration( enableJavaScript: config.enableJavaScript, enableDomStorage: config.enableDomStorage, @@ -16,6 +17,15 @@ InAppWebViewConfiguration convertConfiguration(WebViewConfiguration config) { ); } +/// Converts an (app-facing) [BrowserConfiguration] to a (platform interface) +/// [InAppBrowserConfiguration]. +InAppBrowserConfiguration convertBrowserConfiguration( + BrowserConfiguration config) { + return InAppBrowserConfiguration( + showTitle: config.showTitle, + ); +} + /// Converts an (app-facing) [LaunchMode] to a (platform interface) /// [PreferredLaunchMode]. PreferredLaunchMode convertLaunchMode(LaunchMode mode) { diff --git a/packages/url_launcher/url_launcher/lib/src/types.dart b/packages/url_launcher/url_launcher/lib/src/types.dart index 2bf56e1b5d8b..c8df68faadb1 100644 --- a/packages/url_launcher/url_launcher/lib/src/types.dart +++ b/packages/url_launcher/url_launcher/lib/src/types.dart @@ -55,3 +55,15 @@ class WebViewConfiguration { /// Not all browsers support this, so it is not guaranteed to be honored. final Map headers; } + +/// Additional configuration options for [LaunchMode.inAppBrowserView] +@immutable +class BrowserConfiguration { + /// Creates a new InAppBrowserConfiguration with given settings. + const BrowserConfiguration({this.showTitle = false}); + + /// Whether or not to show the webpage title. + /// + /// May not be supported on all platforms. + final bool showTitle; +} 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..bf878c60dded 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 @@ -23,6 +23,7 @@ Future launchUrlString( String urlString, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), + BrowserConfiguration browserConfiguration = const BrowserConfiguration(), String? webOnlyWindowName, }) async { if ((mode == LaunchMode.inAppWebView || @@ -35,7 +36,8 @@ Future launchUrlString( urlString, LaunchOptions( mode: convertLaunchMode(mode), - webViewConfiguration: convertConfiguration(webViewConfiguration), + webViewConfiguration: convertWebViewConfiguration(webViewConfiguration), + browserConfiguration: convertBrowserConfiguration(browserConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); 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 bc55eb2b656d..97f8af482cd4 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 @@ -40,6 +40,7 @@ Future launchUrl( Uri url, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), + BrowserConfiguration browserConfiguration = const BrowserConfiguration(), String? webOnlyWindowName, }) async { if ((mode == LaunchMode.inAppWebView || @@ -52,7 +53,8 @@ Future launchUrl( url.toString(), LaunchOptions( mode: convertLaunchMode(mode), - webViewConfiguration: convertConfiguration(webViewConfiguration), + webViewConfiguration: convertWebViewConfiguration(webViewConfiguration), + browserConfiguration: convertBrowserConfiguration(browserConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index a5ad397ae843..06b2fab2aa2a 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.6 +version: 6.3.0 environment: sdk: ">=3.2.0 <4.0.0" @@ -28,13 +28,13 @@ flutter: dependencies: flutter: sdk: flutter - url_launcher_android: ^6.2.0 + url_launcher_android: ^6.3.0 url_launcher_ios: ^6.2.4 # Allow either the pure-native or Dart/native hybrid versions of the desktop # implementations, as both are compatible. url_launcher_linux: ^3.1.0 url_launcher_macos: ^3.1.0 - url_launcher_platform_interface: ^2.2.0 + url_launcher_platform_interface: ^2.3.0 url_launcher_web: ^2.2.0 url_launcher_windows: ^3.1.0 diff --git a/packages/url_launcher/url_launcher/test/link_test.dart b/packages/url_launcher/url_launcher/test/link_test.dart index 052ca2556e39..4d8fed207979 100644 --- a/packages/url_launcher/url_launcher/test/link_test.dart +++ b/packages/url_launcher/url_launcher/test/link_test.dart @@ -61,6 +61,7 @@ void main() { enableDomStorage: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); await followLink!(); @@ -92,6 +93,7 @@ void main() { enableDomStorage: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); await followLink!(); 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..f964d5371072 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 @@ -17,6 +17,7 @@ class MockUrlLauncher extends Fake bool? enableJavaScript; bool? enableDomStorage; bool? universalLinksOnly; + bool? showTitle; Map? headers; String? webOnlyWindowName; @@ -41,6 +42,7 @@ class MockUrlLauncher extends Fake required bool universalLinksOnly, required Map headers, required String? webOnlyWindowName, + required bool showTitle, }) { this.url = url; this.launchMode = launchMode; @@ -51,6 +53,7 @@ class MockUrlLauncher extends Fake this.universalLinksOnly = universalLinksOnly; this.headers = headers; this.webOnlyWindowName = webOnlyWindowName; + this.showTitle = showTitle; } // ignore: use_setters_to_change_properties @@ -87,6 +90,7 @@ class MockUrlLauncher extends Fake expect(universalLinksOnly, this.universalLinksOnly); expect(headers, this.headers); expect(webOnlyWindowName, this.webOnlyWindowName); + expect(webOnlyWindowName, this.webOnlyWindowName); launchCalled = true; return response!; } @@ -98,6 +102,7 @@ class MockUrlLauncher extends Fake expect(options.webViewConfiguration.enableJavaScript, enableJavaScript); expect(options.webViewConfiguration.enableDomStorage, enableDomStorage); expect(options.webViewConfiguration.headers, headers); + expect(options.browserConfiguration.showTitle, showTitle); expect(options.webOnlyWindowName, webOnlyWindowName); launchCalled = true; return response!; diff --git a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart index 091f9b7c8fd2..8dee5efd9c8d 100644 --- a/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart +++ b/packages/url_launcher/url_launcher/test/src/legacy_api_test.dart @@ -53,6 +53,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/'), isTrue); @@ -69,6 +70,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -90,6 +92,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: true), isTrue); @@ -106,6 +109,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -125,6 +129,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceWebView: true), isTrue); @@ -141,6 +146,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -160,6 +166,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -179,6 +186,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: false), isTrue); @@ -200,6 +208,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launch('mailto:gmail-noreply@google.com?subject=Hello'), @@ -231,6 +240,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); @@ -263,6 +273,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); @@ -296,6 +307,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( diff --git a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart index 64065ff99f9a..0cd0f9b2eb53 100644 --- a/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart +++ b/packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart @@ -49,6 +49,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -65,6 +66,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -81,6 +83,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -97,6 +100,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); @@ -113,6 +117,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString, mode: LaunchMode.inAppWebView), @@ -130,6 +135,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -138,6 +144,50 @@ void main() { isTrue); }); + test('in-app browser', () async { + const String urlString = 'https://flutter.dev'; + mock + ..setLaunchExpectations( + url: urlString, + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: false, + ) + ..setResponse(true); + expect( + await launchUrlString(urlString, mode: LaunchMode.inAppBrowserView), + isTrue, + ); + }); + + test('in-app browser with title', () async { + const String urlString = 'https://flutter.dev'; + mock + ..setLaunchExpectations( + url: urlString, + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: true, + ) + ..setResponse(true); + expect( + await launchUrlString( + urlString, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + ), + isTrue, + ); + }); + test('external non-browser only', () async { const String urlString = 'https://flutter.dev'; mock @@ -149,6 +199,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -168,6 +219,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -189,6 +241,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -210,6 +263,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -239,6 +293,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(emailLaunchUrlString), isTrue); @@ -257,6 +312,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); 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 0c5bfdcf2472..15796ea659a2 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 @@ -54,6 +54,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -70,6 +71,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -86,6 +88,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -102,6 +105,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url), isTrue); @@ -118,6 +122,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(url, mode: LaunchMode.inAppWebView), isTrue); @@ -134,6 +139,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -151,6 +157,7 @@ void main() { universalLinksOnly: true, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -169,6 +176,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -179,6 +187,29 @@ void main() { isTrue); }); + test('in-app browser view with show title', () async { + final Uri url = Uri.parse('https://flutter.dev'); + mock + ..setLaunchExpectations( + url: url.toString(), + launchMode: PreferredLaunchMode.inAppBrowserView, + enableJavaScript: true, + enableDomStorage: true, + universalLinksOnly: false, + headers: {}, + webOnlyWindowName: null, + showTitle: true, + ) + ..setResponse(true); + expect( + await launchUrl( + url, + mode: LaunchMode.inAppBrowserView, + browserConfiguration: const BrowserConfiguration(showTitle: true), + ), + isTrue); + }); + test('in-app webview without DOM storage', () async { final Uri url = Uri.parse('https://flutter.dev'); mock @@ -190,6 +221,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -211,6 +243,7 @@ void main() { universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect( @@ -243,6 +276,7 @@ void main() { universalLinksOnly: false, headers: {}, webOnlyWindowName: null, + showTitle: false, ) ..setResponse(true); expect(await launchUrl(emailLaunchUrl), isTrue);