Skip to content

Commit

Permalink
launch_url: Deduplicate launchUrl functions
Browse files Browse the repository at this point in the history
- Deduplicate  logic for realm-based and non-realm URLs.
- Utilize i18n for consistent error messaging.
- Refactor error handling into a private  function.
- Move shared functionality to a new file: ```dart lib/widgets/launch_url.dart ```.
  • Loading branch information
VatsalBhesaniya committed Mar 29, 2024
1 parent f0c82eb commit 3585ecd
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 80 deletions.
55 changes: 2 additions & 53 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 @@ -507,7 +502,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 @@ -868,52 +863,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 'package:flutter_gen/gen_l10n/zulip_localizations.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: ZulipLocalizations.of(context).errorUnableToOpenLinkTitle,
message: [
ZulipLocalizations.of(context).errorLinkCouldNotBeOpened(urlString),
if (message != null) message,
].join("\n\n"));
}

/// Launches a URL without considering a realm base URL.
void launchUrlWithoutRealm(BuildContext context, String urlString) async {
bool launched = false;
String? errorMessage;
try {
launched = await ZulipBinding.instance.launchUrl(Uri.parse(urlString),
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, urlString);
}
}

/// Launches a URL considering a realm base URL (if available).
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.toString());
}
29 changes: 2 additions & 27 deletions lib/widgets/login.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';

import '../api/exception.dart';
import '../api/route/account.dart';
import '../api/route/realm.dart';
import '../api/route/users.dart';
import '../model/binding.dart';
import '../model/store.dart';
import 'app.dart';
import 'dialog.dart';
import 'input.dart';
import 'launch_url.dart';
import 'page.dart';
import 'store.dart';

Expand Down Expand Up @@ -228,7 +227,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
errorText: errorText,
helper: GestureDetector(
onTap: () {
_launchUrl(context);
launchUrlWithoutRealm(context, AddAccountPage.serverUrlHelpUrl);
},
child: Text(
zulipLocalizations.serverUrlDocLinkLabel,
Expand All @@ -245,30 +244,6 @@ class _AddAccountPageState extends State<AddAccountPage> {
}
}

void _launchUrl(BuildContext context) async {
Future<void> showError(BuildContext context, String? message) {
return showErrorDialog(
context: context,
title: ZulipLocalizations.of(context).errorUnableToOpenLinkTitle,
message: [
ZulipLocalizations.of(context).errorLinkCouldNotBeOpened(AddAccountPage.serverUrlHelpUrl),
if (message != null) message,
].join("\n\n"));
}

bool launched = false;
String? errorMessage;
try {
launched = await ZulipBinding.instance.launchUrl(Uri.parse(AddAccountPage.serverUrlHelpUrl));
} on PlatformException catch (e) {
errorMessage = e.message;
}
if (!launched) {
if (!context.mounted) return;
await showError(context, errorMessage);
}
}

class PasswordLoginPage extends StatefulWidget {
const PasswordLoginPage({super.key, required this.serverSettings});

Expand Down

0 comments on commit 3585ecd

Please sign in to comment.