From 362f4d120f49dee57f3f1a1ac312d540c38fa21c Mon Sep 17 00:00:00 2001 From: Greg Price Date: Mon, 6 Nov 2023 19:57:08 -0800 Subject: [PATCH 1/2] deps: Ensure url_launcher_android version is at least 6.1.0 This gets us Android Custom Tabs support, via Rajesh's upstream PR: https://github.com/flutter/packages/pull/4739 --- pubspec.lock | 2 +- pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 526beb0810..97c3cfad49 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1090,7 +1090,7 @@ packages: source: hosted version: "6.2.0" url_launcher_android: - dependency: transitive + dependency: "direct main" description: name: url_launcher_android sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" diff --git a/pubspec.yaml b/pubspec.yaml index ed1bac8653..4136e1a44d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: package_info_plus: ^4.0.1 collection: ^1.17.2 url_launcher: ^6.1.11 + url_launcher_android: ">=6.1.0" flutter_localizations: sdk: flutter firebase_messaging: ^14.6.3 From 806a81a560ef1d6c1db310e236f7e33e0c06e110 Mon Sep 17 00:00:00 2001 From: rajveermalviya Date: Fri, 1 Sep 2023 14:13:26 +0530 Subject: [PATCH 2/2] content: Open links in-app on Android `url_launcher` plugin now supports the desired behavior, which is using Android Custom Tabs, so we don't need the workaround of opening the links in external browser anymore, thus removed them. Fixes: #279 --- lib/widgets/content.dart | 10 +--------- test/widgets/content_test.dart | 25 ++++++++++--------------- test/widgets/profile_test.dart | 17 ++++++++--------- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index 294a308bbc..0da97d6aeb 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -680,15 +680,7 @@ void _launchUrl(BuildContext context, String urlString) async { bool launched = false; String? errorMessage; try { - launched = await ZulipBinding.instance.launchUrl(url, - mode: switch (Theme.of(context).platform) { - // TODO(#279): On Android we settle for LaunchMode.externalApplication - // because url_launcher's in-app is a weirdly bare UX. - // Switch once that's fixed upstream (by us or otherwise). - TargetPlatform.android => UrlLaunchMode.externalApplication, - _ => UrlLaunchMode.platformDefault, - }, - ); + launched = await ZulipBinding.instance.launchUrl(url); } on PlatformException catch (e) { errorMessage = e.message; } diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart index 7d25d4db41..dac9bf0616 100644 --- a/test/widgets/content_test.dart +++ b/test/widgets/content_test.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:checks/checks.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -64,8 +63,6 @@ void main() { }); group('LinkNode interactions', () { - const expectedModeAndroid = LaunchMode.externalApplication; - // The Flutter test font uses square glyphs, so width equals height: // https://github.com/flutter/flutter/wiki/Flutter-Test-Fonts const fontSize = 48.0; @@ -89,10 +86,8 @@ void main() { '

hello

'); await tester.tap(find.text('hello')); - final expectedMode = defaultTargetPlatform == TargetPlatform.android ? - LaunchMode.externalApplication : LaunchMode.platformDefault; check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://example/'), mode: expectedMode)); + .single.equals((url: Uri.parse('https://example/'), mode: LaunchMode.platformDefault)); }, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS})); testWidgets('multiple links in paragraph', (tester) async { @@ -106,11 +101,11 @@ void main() { await tester.tapAt(base.translate(1*fontSize, 0)); // "fXo bar baz" check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://a/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://a/'), mode: LaunchMode.platformDefault)); await tester.tapAt(base.translate(9*fontSize, 0)); // "foo bar bXz" check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://b/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://b/'), mode: LaunchMode.platformDefault)); }); testWidgets('link nested in other spans', (tester) async { @@ -118,7 +113,7 @@ void main() { '

word

'); await tester.tap(find.text('word')); check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://a/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://a/'), mode: LaunchMode.platformDefault)); }); testWidgets('link containing other spans', (tester) async { @@ -129,11 +124,11 @@ void main() { await tester.tapAt(base.translate(1*fontSize, 0)); // "tXo words" check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://a/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://a/'), mode: LaunchMode.platformDefault)); await tester.tapAt(base.translate(6*fontSize, 0)); // "two woXds" check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://a/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://a/'), mode: LaunchMode.platformDefault)); }); testWidgets('relative links are resolved', (tester) async { @@ -141,7 +136,7 @@ void main() { '

word

'); await tester.tap(find.text('word')); check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('${eg.realmUrl}a/b?c#d'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('${eg.realmUrl}a/b?c#d'), mode: LaunchMode.platformDefault)); }); testWidgets('link inside HeadingNode', (tester) async { @@ -149,7 +144,7 @@ void main() { '
word
'); await tester.tap(find.text('word')); check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('https://a/'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('https://a/'), mode: LaunchMode.platformDefault)); }); testWidgets('error dialog if invalid link', (tester) async { @@ -159,7 +154,7 @@ void main() { await tester.tap(find.text('word')); await tester.pump(); check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('file:///etc/bad'), mode: expectedModeAndroid)); + .single.equals((url: Uri.parse('file:///etc/bad'), mode: LaunchMode.platformDefault)); checkErrorDialog(tester, expectedTitle: 'Unable to open link'); }); }); @@ -206,7 +201,7 @@ void main() { await tester.tap(find.text('invalid')); final expectedUrl = eg.realmUrl.resolve('/#narrow/stream/1-check/topic'); check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: expectedUrl, mode: LaunchMode.externalApplication)); + .single.equals((url: expectedUrl, mode: LaunchMode.platformDefault)); check(pushedRoutes).isEmpty(); }); }); diff --git a/test/widgets/profile_test.dart b/test/widgets/profile_test.dart index 1d7f72d0fe..26999db5a3 100644 --- a/test/widgets/profile_test.dart +++ b/test/widgets/profile_test.dart @@ -1,5 +1,4 @@ import 'package:checks/checks.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -167,10 +166,10 @@ void main() { ); await tester.tap(find.text(testUrl)); - final expectedMode = defaultTargetPlatform == TargetPlatform.android ? - LaunchMode.externalApplication : LaunchMode.platformDefault; - check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse(testUrl), mode: expectedMode)); + check(testBinding.takeLaunchUrlCalls()).single.equals(( + url: Uri.parse(testUrl), + mode: LaunchMode.platformDefault, + )); }); testWidgets('page builds; external link type navigates away', (WidgetTester tester) async { @@ -194,10 +193,10 @@ void main() { ); await tester.tap(find.text('externalValue')); - final expectedMode = defaultTargetPlatform == TargetPlatform.android ? - LaunchMode.externalApplication : LaunchMode.platformDefault; - check(testBinding.takeLaunchUrlCalls()) - .single.equals((url: Uri.parse('http://example/externalValue'), mode: expectedMode)); + check(testBinding.takeLaunchUrlCalls()).single.equals(( + url: Uri.parse('http://example/externalValue'), + mode: LaunchMode.platformDefault, + )); }); testWidgets('page builds; user links to profile', (WidgetTester tester) async {