Skip to content

Commit 5cc5dff

Browse files
launch_url[nfc]: Move _launchUrl logic to a new file
- Move the existing _launchUrl logic from content.dart to a new file named launch_url.dart. - Split function into pieces to handle realm-based and non-realm URLs. - Refactor error handling into a private function.
1 parent 43561bf commit 5cc5dff

File tree

2 files changed

+63
-53
lines changed

2 files changed

+63
-53
lines changed

lib/widgets/content.dart

+2-53
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
import 'package:flutter/foundation.dart';
21
import 'package:flutter/gestures.dart';
32
import 'package:flutter/material.dart';
4-
import 'package:flutter/services.dart';
53
import 'package:html/dom.dart' as dom;
64
import 'package:intl/intl.dart';
75
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
86

97
import '../api/core.dart';
108
import '../api/model/model.dart';
119
import '../model/avatar_url.dart';
12-
import '../model/binding.dart';
1310
import '../model/content.dart';
14-
import '../model/internal_link.dart';
1511
import 'code_block.dart';
16-
import 'dialog.dart';
1712
import 'icons.dart';
13+
import 'launch_url.dart';
1814
import 'lightbox.dart';
19-
import 'message_list.dart';
2015
import 'store.dart';
2116
import 'text.dart';
2217

@@ -507,7 +502,7 @@ class _BlockInlineContainerState extends State<_BlockInlineContainer> {
507502

508503
void _prepareRecognizers() {
509504
_recognizers.addEntries(widget.links.map((node) => MapEntry(node,
510-
TapGestureRecognizer()..onTap = () => _launchUrl(context, node.url))));
505+
TapGestureRecognizer()..onTap = () => launchUrlWithRealm(context, node.url))));
511506
}
512507

513508
void _disposeRecognizers() {
@@ -873,52 +868,6 @@ class GlobalTime extends StatelessWidget {
873868
}
874869
}
875870

876-
void _launchUrl(BuildContext context, String urlString) async {
877-
Future<void> showError(BuildContext context, String? message) {
878-
return showErrorDialog(context: context,
879-
title: 'Unable to open link',
880-
message: [
881-
'Link could not be opened: $urlString',
882-
if (message != null) message,
883-
].join("\n\n"));
884-
}
885-
886-
final store = PerAccountStoreWidget.of(context);
887-
final url = store.tryResolveUrl(urlString);
888-
if (url == null) { // TODO(log)
889-
await showError(context, null);
890-
return;
891-
}
892-
893-
final internalNarrow = parseInternalLink(url, store);
894-
if (internalNarrow != null) {
895-
Navigator.push(context,
896-
MessageListPage.buildRoute(context: context,
897-
narrow: internalNarrow));
898-
return;
899-
}
900-
901-
bool launched = false;
902-
String? errorMessage;
903-
try {
904-
launched = await ZulipBinding.instance.launchUrl(url,
905-
mode: switch (defaultTargetPlatform) {
906-
// On iOS we prefer LaunchMode.externalApplication because (for
907-
// HTTP URLs) LaunchMode.platformDefault uses SFSafariViewController,
908-
// which gives an awkward UX as described here:
909-
// https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1169118
910-
TargetPlatform.iOS => UrlLaunchMode.externalApplication,
911-
_ => UrlLaunchMode.platformDefault,
912-
});
913-
} on PlatformException catch (e) {
914-
errorMessage = e.message;
915-
}
916-
if (!launched) { // TODO(log)
917-
if (!context.mounted) return;
918-
await showError(context, errorMessage);
919-
}
920-
}
921-
922871
/// Like [Image.network], but includes [authHeader] if [src] is on-realm.
923872
///
924873
/// Use this to present image content in the ambient realm: avatars, images in

lib/widgets/launch_url.dart

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter/services.dart';
4+
5+
import '../model/binding.dart';
6+
import '../model/internal_link.dart';
7+
import 'dialog.dart';
8+
import 'message_list.dart';
9+
import 'store.dart';
10+
11+
/// Handles showing an error dialog with a customizable message.
12+
Future<void> _showError(BuildContext context, String? message, String urlString) {
13+
return showErrorDialog(
14+
context: context,
15+
title: 'Unable to open link',
16+
message: [
17+
'Link could not be opened: $urlString',
18+
if (message != null) message,
19+
].join("\n\n"));
20+
}
21+
22+
/// Launches a URL without considering a realm base URL.
23+
void launchUrlWithoutRealm(BuildContext context, Uri url) async {
24+
bool launched = false;
25+
String? errorMessage;
26+
try {
27+
launched = await ZulipBinding.instance.launchUrl(url,
28+
mode: switch (defaultTargetPlatform) {
29+
// On iOS we prefer LaunchMode.externalApplication because (for
30+
// HTTP URLs) LaunchMode.platformDefault uses SFSafariViewController,
31+
// which gives an awkward UX as described here:
32+
// https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1169118
33+
TargetPlatform.iOS => UrlLaunchMode.externalApplication,
34+
_ => UrlLaunchMode.platformDefault,
35+
});
36+
} on PlatformException catch (e) {
37+
errorMessage = e.message;
38+
}
39+
if (!launched) {
40+
if (!context.mounted) return;
41+
await _showError(context, errorMessage, url.toString());
42+
}
43+
}
44+
45+
/// Launches a URL considering a realm base URL.
46+
void launchUrlWithRealm(BuildContext context, String urlString) async {
47+
final store = PerAccountStoreWidget.of(context);
48+
final url = store.tryResolveUrl(urlString);
49+
if (url == null) { // TODO(log)
50+
await _showError(context, null, urlString);
51+
return;
52+
}
53+
54+
final internalNarrow = parseInternalLink(url, store);
55+
if (internalNarrow != null) {
56+
Navigator.push(context, MessageListPage.buildRoute(context: context, narrow: internalNarrow));
57+
return;
58+
}
59+
60+
launchUrlWithoutRealm(context, url);
61+
}

0 commit comments

Comments
 (0)