Skip to content

Commit

Permalink
launch_url [nfc]: Move launchUrl logic to a new file
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
VatsalBhesaniya committed May 23, 2024
1 parent baea6d7 commit ed81d05
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 54 deletions.
57 changes: 3 additions & 54 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:html/dom.dart' as dom;
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';

import '../api/core.dart';
import '../api/model/model.dart';
import '../model/avatar_url.dart';
import '../model/binding.dart';
import '../model/content.dart';
import '../model/internal_link.dart';
import 'code_block.dart';
import 'dialog.dart';
import 'icons.dart';
import 'launch_url.dart';
import 'lightbox.dart';
import 'message_list.dart';
import 'store.dart';
import 'text.dart';

Expand Down Expand Up @@ -437,7 +432,7 @@ class MessageEmbedVideo extends StatelessWidget {
final previewImageSrcUrl = store.tryResolveUrl(node.previewImageSrcUrl);

return MessageMediaContainer(
onTap: () => _launchUrl(context, node.hrefUrl),
onTap: () => launchUrlWithRealm(context, node.hrefUrl),
child: Stack(
alignment: Alignment.center,
children: [
Expand Down Expand Up @@ -609,7 +604,7 @@ class _BlockInlineContainerState extends State<_BlockInlineContainer> {

void _prepareRecognizers() {
_recognizers.addEntries(widget.links.map((node) => MapEntry(node,
TapGestureRecognizer()..onTap = () => _launchUrl(context, node.url))));
TapGestureRecognizer()..onTap = () => launchUrlWithRealm(context, node.url))));
}

void _disposeRecognizers() {
Expand Down Expand Up @@ -1001,52 +996,6 @@ class GlobalTime extends StatelessWidget {
}
}

void _launchUrl(BuildContext context, String urlString) async {
Future<void> showError(BuildContext context, String? message) {
return showErrorDialog(context: context,
title: 'Unable to open link',
message: [
'Link could not be opened: $urlString',
if (message != null) message,
].join("\n\n"));
}

final store = PerAccountStoreWidget.of(context);
final url = store.tryResolveUrl(urlString);
if (url == null) { // TODO(log)
await showError(context, null);
return;
}

final internalNarrow = parseInternalLink(url, store);
if (internalNarrow != null) {
Navigator.push(context,
MessageListPage.buildRoute(context: context,
narrow: internalNarrow));
return;
}

bool launched = false;
String? errorMessage;
try {
launched = await ZulipBinding.instance.launchUrl(url,
mode: switch (defaultTargetPlatform) {
// On iOS we prefer LaunchMode.externalApplication because (for
// HTTP URLs) LaunchMode.platformDefault uses SFSafariViewController,
// which gives an awkward UX as described here:
// https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1169118
TargetPlatform.iOS => UrlLaunchMode.externalApplication,
_ => UrlLaunchMode.platformDefault,
});
} on PlatformException catch (e) {
errorMessage = e.message;
}
if (!launched) { // TODO(log)
if (!context.mounted) return;
await showError(context, errorMessage);
}
}

/// Like [Image.network], but includes [authHeader] if [src] is on-realm.
///
/// Use this to present image content in the ambient realm: avatars, images in
Expand Down
62 changes: 62 additions & 0 deletions lib/widgets/launch_url.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../model/binding.dart';
import '../model/internal_link.dart';
import 'dialog.dart';
import 'message_list.dart';
import 'store.dart';

/// Handles showing an error dialog with a customizable message.
Future<void> _showError(BuildContext context, String? message, String urlString) {
return showErrorDialog(
context: context,
title: 'Unable to open link',
message: [
'Link could not be opened: $urlString',
if (message != null) message,
].join("\n\n"));
}

/// Launches a URL without considering a realm base URL.
void launchUrlWithoutRealm(BuildContext context, Uri url) async {
bool launched = false;
String? errorMessage;
try {
launched = await ZulipBinding.instance.launchUrl(url,
mode: switch (defaultTargetPlatform) {
// On iOS we prefer LaunchMode.externalApplication because (for
// HTTP URLs) LaunchMode.platformDefault uses SFSafariViewController,
// which gives an awkward UX as described here:
// https://chat.zulip.org/#narrow/stream/48-mobile/topic/in-app.20browser/near/1169118
TargetPlatform.iOS => UrlLaunchMode.externalApplication,
_ => UrlLaunchMode.platformDefault,
});
} on PlatformException catch (e) {
errorMessage = e.message;
}
if (!launched) {
if (!context.mounted) return;
await _showError(context, errorMessage, url.toString());
}
}

/// Launches a URL considering a realm base URL.
void launchUrlWithRealm(BuildContext context, String urlString) async {
final store = PerAccountStoreWidget.of(context);
final url = store.tryResolveUrl(urlString);
if (url == null) { // TODO(log)
await _showError(context, null, urlString);
return;
}

final internalNarrow = parseInternalLink(url, store);
if (internalNarrow != null) {
Navigator.push(context,
MessageListPage.buildRoute(context: context, narrow: internalNarrow));
return;
}

launchUrlWithoutRealm(context, url);
}

0 comments on commit ed81d05

Please sign in to comment.