From 92a617e69c66f249fadaa5650d4f097502078848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Ali=20Ustao=C4=9Flu?= Date: Sat, 24 Oct 2026 20:58:50 +0200 Subject: [PATCH] url_launcher_watchos 0.1.2: upstream's example and official test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The example was a hand-written demo. AUTHORING.md §3 asks for upstream's own example and official test, unchanged, so both are now url_launcher's, ported with `flutter-watchos plugin port --include-example` (#24). The example's pubspec adds `path`, which upstream's lib/files.dart imports. The report said upstream's test was not reused because it asserts an in-app SFSafariViewController. That is url_launcher_ios's test; the app-facing url_launcher's test, which the porter takes, checks canLaunchUrl and the legacy canLaunch, and passes verbatim. Our former test stays beside it as watchos_url_launcher_test.dart: the on-watch browser, Handoff and the refused schemes. On the watchOS 27.0 Simulator both tests pass (1 and 6), and the example starts with no exception in its first 15 seconds and no overflow. --- packages/url_launcher_watchos/CHANGELOG.md | 6 + .../url_launcher_watchos/PORTING_REPORT.md | 12 +- packages/url_launcher_watchos/README.md | 9 + .../integration_test/url_launcher_test.dart | 81 ++--- .../watchos_url_launcher_test.dart | 70 +++++ .../example/lib/basic.dart | 33 +++ .../example/lib/encoding.dart | 53 ++++ .../example/lib/files.dart | 45 +++ .../example/lib/main.dart | 277 ++++++++++++++---- .../url_launcher_watchos/example/pubspec.yaml | 2 + packages/url_launcher_watchos/pubspec.yaml | 2 +- 11 files changed, 473 insertions(+), 117 deletions(-) create mode 100644 packages/url_launcher_watchos/example/integration_test/watchos_url_launcher_test.dart create mode 100644 packages/url_launcher_watchos/example/lib/basic.dart create mode 100644 packages/url_launcher_watchos/example/lib/encoding.dart create mode 100644 packages/url_launcher_watchos/example/lib/files.dart diff --git a/packages/url_launcher_watchos/CHANGELOG.md b/packages/url_launcher_watchos/CHANGELOG.md index aec7cb3..26a48a1 100644 --- a/packages/url_launcher_watchos/CHANGELOG.md +++ b/packages/url_launcher_watchos/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.2 + +* Example: `url_launcher`'s own example app and its official integration + test, unchanged, replace a hand-written demo (`AUTHORING.md` §3). The + former test stays beside it as `watchos_url_launcher_test.dart`. + ## 0.1.1 * README: the install snippet names `url_launcher: ^6.3.2` instead of `any`, diff --git a/packages/url_launcher_watchos/PORTING_REPORT.md b/packages/url_launcher_watchos/PORTING_REPORT.md index 19672fb..49fdf7a 100644 --- a/packages/url_launcher_watchos/PORTING_REPORT.md +++ b/packages/url_launcher_watchos/PORTING_REPORT.md @@ -9,8 +9,8 @@ | Host unit tests (`flutter-watchos test`) | ✅ pass (10) | | Native compile (`arm64-apple-watchos7.0-simulator`, `-Wall -Wextra`) | ✅ clean, all 3 symbols exported | | CLI integration | ✅ `url_launcher` drops off the "no watchOS implementation" warning in a real app | -| Upstream integration test | ✗ not reused — upstream asserts an in-app `SFSafariViewController`/webview surface that cannot exist on watchOS | -| Own integration test (Simulator, real FFI) | ✅ pass (5) — symbols resolve via `DynamicLibrary.process()` and `url_launcher` federates to this implementation | +| Upstream integration test | ✅ passes verbatim on the watchOS 27.0 Simulator (`url_launcher`'s `url_launcher_test.dart`, 2026-10-24) | +| watchOS integration test (`watchos_url_launcher_test.dart`, Simulator, real FFI) | ✅ pass (6) — symbols resolve via `DynamicLibrary.process()` and `url_launcher` federates to this implementation | | On-device (Apple Watch Ultra 3, watchOS 26.5, release AOT) | ✅ `tel:` and `sms:` confirmed by hand — both raise the system call / compose UI on the watch. Web path confirmed too: the system sheet appears and reports the link can be viewed on the iPhone | | On-device Handoff pickup on the phone | ✅ confirmed — the Handoff icon appears in the paired iPhone's app switcher and opens the page | @@ -29,6 +29,14 @@ scaffold's shape (package name, `dartPluginClass: UrlLauncherWatchos`, `ffiPlugin: true`, `implements: url_launcher`) matches this package; the exported symbols and every method body are the human half it leaves behind. +## Example + +`url_launcher`'s own example app and its official integration test, ported +unchanged with `flutter-watchos plugin port --include-example`. There are no +watchOS deviations. The official test checks `canLaunchUrl` and the legacy +`canLaunch`. `watchos_url_launcher_test.dart` beside it checks the on-watch +browser, Handoff and the refused schemes. + ## Status ✅ WORKING FFI implementation: diff --git a/packages/url_launcher_watchos/README.md b/packages/url_launcher_watchos/README.md index 15d525b..c10540c 100644 --- a/packages/url_launcher_watchos/README.md +++ b/packages/url_launcher_watchos/README.md @@ -83,6 +83,15 @@ These members of `url_launcher_platform_interface` 2.3.2 throw or fail on watchO | `launchUrl` / `launch` with a scheme other than `http:`, `https:`, `tel:` and `sms:` (`mailto:` included) | return false | `openSystemURL:` owns only `tel:` and `sms:` and fails silently on other schemes | | `supportsMode(externalNonBrowserApplication)` | returns false | the watch cannot tell whether the iPhone opens a web URL in an app; `launchUrl` still launches it as `externalApplication` | +## Example + +The example is `url_launcher`'s own example app, and +`example/integration_test/url_launcher_test.dart` is its official test, both +unchanged, ported with `flutter-watchos plugin port --include-example`. There +are no watchOS deviations. `example/integration_test/watchos_url_launcher_test.dart` +covers what the official test does not: the on-watch browser, Handoff and +the schemes the watch refuses. + ## Testing `UrlLauncherWatchos.bindingsOverride` accepts a fake extending diff --git a/packages/url_launcher_watchos/example/integration_test/url_launcher_test.dart b/packages/url_launcher_watchos/example/integration_test/url_launcher_test.dart index 65fe73c..345a892 100644 --- a/packages/url_launcher_watchos/example/integration_test/url_launcher_test.dart +++ b/packages/url_launcher_watchos/example/integration_test/url_launcher_test.dart @@ -1,16 +1,10 @@ -// Copyright 2026 The FlutterWatch Authors. All rights reserved. +// Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// -// Runs on a real watch (or the Simulator) against the linked FFI symbols, so -// it proves the native side is present and answering — something the host -// unit tests, which fake the bindings, cannot. -// -// What it deliberately does NOT assert: that a URL actually opened. Neither -// the on-watch browser, `openSystemURL:` nor Handoff reports completion to the -// app, and the watch has no API to observe the paired phone. Those need a -// human with a wrist. +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -18,53 +12,24 @@ import 'package:url_launcher/url_launcher.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('canLaunchUrl (real FFI)', () { - test('accepts the schemes watchOS can act on', () async { - expect(await canLaunchUrl(Uri.parse('tel:+15551234567')), isTrue); - expect(await canLaunchUrl(Uri.parse('sms:+15551234567')), isTrue); - expect(await canLaunchUrl(Uri.parse('https://flutterwatch.dev')), isTrue); - expect(await canLaunchUrl(Uri.parse('http://example.com')), isTrue); - }); - - test('refuses schemes with no watchOS mechanism', () async { - expect( - await canLaunchUrl(Uri.parse('mailto:hello@example.com')), isFalse); - expect(await canLaunchUrl(Uri.parse('ftp://example.com')), isFalse); - }); - }); - - group('launchUrl (real FFI)', () { - test('a web URL is accepted for the on-watch browser', () async { - // True means "handed to ASWebAuthenticationSession", not "rendered". - expect(await launchUrl(Uri.parse('https://flutterwatch.dev')), isTrue); - await closeInAppWebView(); - }); - - test('a web URL is accepted for Handoff with externalApplication', - () async { - // True means "published as an NSUserActivity", not "opened". - expect( - await launchUrl( - Uri.parse('https://flutterwatch.dev'), - mode: LaunchMode.externalApplication, - ), - isTrue, - ); - }); - - test('an unsupported scheme is refused, not silently swallowed', () async { - // Reports false rather than throwing: the caller gets a definite "this - // did not happen" instead of an exception to catch. - expect(await launchUrl(Uri.parse('ftp://example.com')), isFalse); - }); - - test('closeWebView withdraws the Handoff activity without throwing', - () async { - await launchUrl( - Uri.parse('https://flutterwatch.dev'), - mode: LaunchMode.externalApplication, - ); - await closeInAppWebView(); - }); + testWidgets('canLaunch', (WidgetTester _) async { + expect( + await canLaunchUrl(Uri(scheme: 'randomscheme', path: 'a_path')), false); + + // Generally all devices should have some default browser. + expect(await canLaunchUrl(Uri(scheme: 'http', host: 'flutter.dev')), true); + expect(await canLaunchUrl(Uri(scheme: 'https', host: 'flutter.dev')), true); + + // SMS handling is available by default on most platforms. + if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { + expect(await canLaunchUrl(Uri(scheme: 'sms', path: '5555555555')), true); + } + + // Sanity-check legacy API. + // ignore: deprecated_member_use + expect(await canLaunch('randomstring'), false); + // Generally all devices should have some default browser. + // ignore: deprecated_member_use + expect(await canLaunch('https://flutter.dev'), true); }); } diff --git a/packages/url_launcher_watchos/example/integration_test/watchos_url_launcher_test.dart b/packages/url_launcher_watchos/example/integration_test/watchos_url_launcher_test.dart new file mode 100644 index 0000000..65fe73c --- /dev/null +++ b/packages/url_launcher_watchos/example/integration_test/watchos_url_launcher_test.dart @@ -0,0 +1,70 @@ +// Copyright 2026 The FlutterWatch Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Runs on a real watch (or the Simulator) against the linked FFI symbols, so +// it proves the native side is present and answering — something the host +// unit tests, which fake the bindings, cannot. +// +// What it deliberately does NOT assert: that a URL actually opened. Neither +// the on-watch browser, `openSystemURL:` nor Handoff reports completion to the +// app, and the watch has no API to observe the paired phone. Those need a +// human with a wrist. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('canLaunchUrl (real FFI)', () { + test('accepts the schemes watchOS can act on', () async { + expect(await canLaunchUrl(Uri.parse('tel:+15551234567')), isTrue); + expect(await canLaunchUrl(Uri.parse('sms:+15551234567')), isTrue); + expect(await canLaunchUrl(Uri.parse('https://flutterwatch.dev')), isTrue); + expect(await canLaunchUrl(Uri.parse('http://example.com')), isTrue); + }); + + test('refuses schemes with no watchOS mechanism', () async { + expect( + await canLaunchUrl(Uri.parse('mailto:hello@example.com')), isFalse); + expect(await canLaunchUrl(Uri.parse('ftp://example.com')), isFalse); + }); + }); + + group('launchUrl (real FFI)', () { + test('a web URL is accepted for the on-watch browser', () async { + // True means "handed to ASWebAuthenticationSession", not "rendered". + expect(await launchUrl(Uri.parse('https://flutterwatch.dev')), isTrue); + await closeInAppWebView(); + }); + + test('a web URL is accepted for Handoff with externalApplication', + () async { + // True means "published as an NSUserActivity", not "opened". + expect( + await launchUrl( + Uri.parse('https://flutterwatch.dev'), + mode: LaunchMode.externalApplication, + ), + isTrue, + ); + }); + + test('an unsupported scheme is refused, not silently swallowed', () async { + // Reports false rather than throwing: the caller gets a definite "this + // did not happen" instead of an exception to catch. + expect(await launchUrl(Uri.parse('ftp://example.com')), isFalse); + }); + + test('closeWebView withdraws the Handoff activity without throwing', + () async { + await launchUrl( + Uri.parse('https://flutterwatch.dev'), + mode: LaunchMode.externalApplication, + ); + await closeInAppWebView(); + }); + }); +} diff --git a/packages/url_launcher_watchos/example/lib/basic.dart b/packages/url_launcher_watchos/example/lib/basic.dart new file mode 100644 index 0000000..4e3aac0 --- /dev/null +++ b/packages/url_launcher_watchos/example/lib/basic.dart @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Run this example with: flutter run -t lib/basic.dart -d emulator + +// This file is used to extract code samples for the README.md file. +// Run update-excerpts if you modify this file. + +// #docregion basic-example +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +final Uri _url = Uri.parse('https://flutter.dev'); + +void main() => runApp( + const MaterialApp( + home: Material( + child: Center( + child: ElevatedButton( + onPressed: _launchUrl, child: Text('Show Flutter homepage')), + ), + ), + ), + ); + +Future _launchUrl() async { + if (!await launchUrl(_url)) { + throw Exception('Could not launch $_url'); + } +} + +// #enddocregion basic-example diff --git a/packages/url_launcher_watchos/example/lib/encoding.dart b/packages/url_launcher_watchos/example/lib/encoding.dart new file mode 100644 index 0000000..8f8e9e6 --- /dev/null +++ b/packages/url_launcher_watchos/example/lib/encoding.dart @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Run this example with: flutter run -t lib/encoding.dart -d emulator + +// This file is used to extract code samples for the README.md file. +// Run update-excerpts if you modify this file. + +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// Encode [params] so it produces a correct query string. +/// Workaround for: https://github.com/dart-lang/sdk/issues/43838 +// #docregion encode-query-parameters +String? encodeQueryParameters(Map params) { + return params.entries + .map( + (MapEntry e) => + '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}', + ) + .join('&'); +} + +// #enddocregion encode-query-parameters + +void main() => runApp( + const MaterialApp( + home: Material( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: _composeMail, child: Text('Compose an email')), + ], + ), + ), + ), + ); + +void _composeMail() { + // #docregion encode-query-parameters + final emailLaunchUri = Uri( + scheme: 'mailto', + path: 'smith@example.com', + query: encodeQueryParameters({ + 'subject': 'Example Subject & Symbols are allowed!', + }), + ); + + launchUrl(emailLaunchUri); + // #enddocregion encode-query-parameters +} diff --git a/packages/url_launcher_watchos/example/lib/files.dart b/packages/url_launcher_watchos/example/lib/files.dart new file mode 100644 index 0000000..299c1b2 --- /dev/null +++ b/packages/url_launcher_watchos/example/lib/files.dart @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Run this example with: flutter run -t lib/files.dart -d linux + +// This file is used to extract code samples for the README.md file. +// Run update-excerpts if you modify this file. +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:path/path.dart' as p; +import 'package:url_launcher/url_launcher.dart'; + +void main() => runApp( + const MaterialApp( + home: Material( + child: Center( + child: + ElevatedButton(onPressed: _openFile, child: Text('Open File')), + ), + ), + ), + ); + +Future _openFile() async { + // Prepare a file within tmp + final String tempFilePath = p.joinAll([ + ...p.split(Directory.systemTemp.path), + 'flutter_url_launcher_example.txt', + ]); + final testFile = File(tempFilePath); + await testFile.writeAsString('Hello, world!'); + // #docregion file + final String filePath = testFile.absolute.path; + final uri = Uri.file(filePath); + + if (!File(uri.toFilePath()).existsSync()) { + throw Exception('$uri does not exist!'); + } + if (!await launchUrl(uri)) { + throw Exception('Could not launch $uri'); + } + // #enddocregion file +} diff --git a/packages/url_launcher_watchos/example/lib/main.dart b/packages/url_launcher_watchos/example/lib/main.dart index 779e3fe..a4bcf52 100644 --- a/packages/url_launcher_watchos/example/lib/main.dart +++ b/packages/url_launcher_watchos/example/lib/main.dart @@ -1,85 +1,250 @@ -// Copyright 2026 The FlutterWatch Authors. All rights reserved. +// Copyright 2013 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:url_launcher/link.dart'; import 'package:url_launcher/url_launcher.dart'; -void main() => runApp(const ExampleApp()); +void main() { + runApp(const MyApp()); +} -/// Exercises each class of URL watchOS can act on, and one it cannot. -class ExampleApp extends StatefulWidget { - const ExampleApp({super.key}); +class MyApp extends StatelessWidget { + const MyApp({super.key}); @override - State createState() => _ExampleAppState(); + Widget build(BuildContext context) { + return MaterialApp( + title: 'URL Launcher', + theme: ThemeData(primarySwatch: Colors.blue), + home: const MyHomePage(title: 'URL Launcher'), + ); + } } -class _ExampleAppState extends State { - String _status = 'Pick a URL'; +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + bool _hasCallSupport = false; + Future? _launched; + String _phone = ''; + + @override + void initState() { + super.initState(); + // Check for phone call support. + canLaunchUrl(Uri(scheme: 'tel', path: '123')).then((bool result) { + setState(() { + _hasCallSupport = result; + }); + }); + } + + Future _launchInBrowser(Uri url) async { + if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInBrowserView(Uri url) async { + if (!await launchUrl(url, mode: LaunchMode.inAppBrowserView)) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebView(Uri url) async { + if (!await launchUrl(url, mode: LaunchMode.inAppWebView)) { + throw Exception('Could not launch $url'); + } + } + + 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, + mode: LaunchMode.inAppWebView, + webViewConfiguration: const WebViewConfiguration( + headers: {'my_header_key': 'my_header_value'}, + ), + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebViewWithoutJavaScript(Uri url) async { + if (!await launchUrl( + url, + mode: LaunchMode.inAppWebView, + webViewConfiguration: const WebViewConfiguration(enableJavaScript: false), + )) { + throw Exception('Could not launch $url'); + } + } - Future _open(String url, - {LaunchMode mode = LaunchMode.platformDefault}) async { - final Uri uri = Uri.parse(url); - if (!await canLaunchUrl(uri)) { - setState(() => _status = 'unsupported: ${uri.scheme}:'); - return; + Future _launchInWebViewWithoutDomStorage(Uri url) async { + if (!await launchUrl( + url, + mode: LaunchMode.inAppWebView, + webViewConfiguration: const WebViewConfiguration(enableDomStorage: false), + )) { + throw Exception('Could not launch $url'); } - final bool ok = await launchUrl(uri, mode: mode); - if (!mounted) { - return; + } + + Future _launchUniversalLinkIOS(Uri url) async { + final bool nativeAppLaunchSucceeded = await launchUrl( + url, + mode: LaunchMode.externalNonBrowserApplication, + ); + if (!nativeAppLaunchSucceeded) { + await launchUrl(url, mode: LaunchMode.inAppBrowserView); } - // "Handed to the system", not "the user followed it": watchOS reports no - // completion for any of these. - setState(() => _status = ok ? 'handed to system' : 'refused'); + } + + Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return const Text(''); + } + } + + Future _makePhoneCall(String phoneNumber) async { + final launchUri = Uri(scheme: 'tel', path: phoneNumber); + await launchUrl(launchUri); } @override Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - body: SafeArea( - child: ListView( - padding: const EdgeInsets.all(8), + // onPressed calls using this URL are not gated on a 'canLaunch' check + // because the assumption is that every device can launch a web URL. + final toLaunch = + Uri(scheme: 'https', host: 'www.cylog.org', path: 'headers/'); + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: ListView( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(_status, textAlign: TextAlign.center), - const SizedBox(height: 8), - _Button( - label: 'https (on watch)', - onTap: () => _open('https://example.com'), + Padding( + padding: const EdgeInsets.all(16.0), + child: TextField( + onChanged: (String text) => _phone = text, + decoration: const InputDecoration( + hintText: 'Input the phone number to launch'), + ), + ), + ElevatedButton( + onPressed: _hasCallSupport + ? () => setState(() { + _launched = _makePhoneCall(_phone); + }) + : null, + child: _hasCallSupport + ? const Text('Make phone call') + : const Text('Calling not supported'), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text(toLaunch.toString())), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInBrowser(toLaunch); + }), + child: const Text('Launch in browser'), ), - _Button( - label: 'https (→ iPhone)', - onTap: () => _open( - 'https://example.com', - mode: LaunchMode.externalApplication, + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInBrowserView(toLaunch); + }), + child: const Text('Launch in app'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchAsInAppWebViewWithCustomHeaders(toLaunch); + }), + child: const Text('Launch in app (Custom Headers)'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithoutJavaScript(toLaunch); + }), + child: const Text('Launch in app (JavaScript OFF)'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithoutDomStorage(toLaunch); + }), + child: const Text('Launch in app (DOM storage OFF)'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchUniversalLinkIOS(toLaunch); + }), + child: const Text( + 'Launch a universal link in a native app, fallback to Safari.(Youtube)', ), ), - _Button(label: 'tel', onTap: () => _open('tel:+15551234567')), - _Button(label: 'sms', onTap: () => _open('sms:+15551234567')), - _Button( - label: 'mailto (unsupported)', - onTap: () => _open('mailto:hello@example.com'), + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebView(toLaunch); + Timer(const Duration(seconds: 5), () { + closeInAppWebView(); + }); + }), + 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', + ), + target: LinkTarget.blank, + builder: (BuildContext ctx, FollowLink? openLink) { + return TextButton.icon( + onPressed: openLink, + label: const Text('Link Widget documentation'), + icon: const Icon(Icons.read_more), + ); + }, + ), + const Padding(padding: EdgeInsets.all(16.0)), + FutureBuilder(future: _launched, builder: _launchStatus), ], ), - ), + ], ), ); } } - -class _Button extends StatelessWidget { - const _Button({required this.label, required this.onTap}); - - final String label; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 3), - child: ElevatedButton(onPressed: onTap, child: Text(label)), - ); - } -} diff --git a/packages/url_launcher_watchos/example/pubspec.yaml b/packages/url_launcher_watchos/example/pubspec.yaml index 0c58859..f80585c 100644 --- a/packages/url_launcher_watchos/example/pubspec.yaml +++ b/packages/url_launcher_watchos/example/pubspec.yaml @@ -12,6 +12,8 @@ dependencies: url_launcher: ^6.3.2 url_launcher_watchos: path: ../ + # Upstream's example imports it (lib/files.dart). + path: ^1.8.0 dev_dependencies: flutter_test: diff --git a/packages/url_launcher_watchos/pubspec.yaml b/packages/url_launcher_watchos/pubspec.yaml index f09a564..e2bd33a 100644 --- a/packages/url_launcher_watchos/pubspec.yaml +++ b/packages/url_launcher_watchos/pubspec.yaml @@ -1,6 +1,6 @@ name: url_launcher_watchos description: "watchOS implementation of url_launcher over dart:ffi: web pages in the watch's system browser, tel: and sms: through the system handler, and web links handed to the iPhone." -version: 0.1.1 +version: 0.1.2 homepage: https://flutterwatch.dev repository: https://github.com/flutterwatch/plugins/tree/main/packages/url_launcher_watchos issue_tracker: https://github.com/flutterwatch/plugins/issues