Skip to content

Commit c691f18

Browse files
notif [nfc]: Introduce NotificationOpenService
And move the notification navigation data parsing utilities to the new class.
1 parent f322c9f commit c691f18

File tree

5 files changed

+331
-297
lines changed

5 files changed

+331
-297
lines changed

lib/notifications/display.dart

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,16 @@ import 'dart:io';
33

44
import 'package:collection/collection.dart';
55
import 'package:flutter/foundation.dart';
6-
import 'package:flutter/widgets.dart' hide Notification;
76
import 'package:http/http.dart' as http;
87

98
import '../api/model/model.dart';
109
import '../api/notifications.dart';
11-
import '../generated/l10n/zulip_localizations.dart';
1210
import '../host/android_notifications.dart';
1311
import '../log.dart';
1412
import '../model/binding.dart';
1513
import '../model/localizations.dart';
1614
import '../model/narrow.dart';
17-
import '../widgets/app.dart';
1815
import '../widgets/color.dart';
19-
import '../widgets/dialog.dart';
20-
import '../widgets/message_list.dart';
21-
import '../widgets/page.dart';
22-
import '../widgets/store.dart';
2316
import '../widgets/theme.dart';
2417
import 'open.dart';
2518

@@ -482,62 +475,6 @@ class NotificationDisplayManager {
482475

483476
static String _personKey(Uri realmUrl, int userId) => "$realmUrl|$userId";
484477

485-
/// Provides the route and the account ID by parsing the notification URL.
486-
///
487-
/// The URL must have been generated using [NotificationOpenPayload.buildUrl]
488-
/// while creating the notification.
489-
///
490-
/// Returns null and shows an error dialog if the associated account is not
491-
/// found in the global store.
492-
static AccountRoute<void>? routeForNotification({
493-
required BuildContext context,
494-
required Uri url,
495-
}) {
496-
assert(defaultTargetPlatform == TargetPlatform.android);
497-
498-
final globalStore = GlobalStoreWidget.of(context);
499-
500-
assert(debugLog('got notif: url: $url'));
501-
assert(url.scheme == 'zulip' && url.host == 'notification');
502-
final payload = NotificationOpenPayload.parseUrl(url);
503-
504-
final account = globalStore.accounts.firstWhereOrNull(
505-
(account) => account.realmUrl.origin == payload.realmUrl.origin
506-
&& account.userId == payload.userId);
507-
if (account == null) { // TODO(log)
508-
final zulipLocalizations = ZulipLocalizations.of(context);
509-
showErrorDialog(context: context,
510-
title: zulipLocalizations.errorNotificationOpenTitle,
511-
message: zulipLocalizations.errorNotificationOpenAccountNotFound);
512-
return null;
513-
}
514-
515-
return MessageListPage.buildRoute(
516-
accountId: account.id,
517-
// TODO(#82): Open at specific message, not just conversation
518-
narrow: payload.narrow);
519-
}
520-
521-
/// Navigates to the [MessageListPage] of the specific conversation
522-
/// given the `zulip://notification/…` Android intent data URL,
523-
/// generated with [NotificationOpenPayload.buildUrl] while creating
524-
/// the notification.
525-
static Future<void> navigateForNotification(Uri url) async {
526-
assert(defaultTargetPlatform == TargetPlatform.android);
527-
assert(debugLog('opened notif: url: $url'));
528-
529-
NavigatorState navigator = await ZulipApp.navigator;
530-
final context = navigator.context;
531-
assert(context.mounted);
532-
if (!context.mounted) return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
533-
534-
final route = routeForNotification(context: context, url: url);
535-
if (route == null) return; // TODO(log)
536-
537-
// TODO(nav): Better interact with existing nav stack on notif open
538-
unawaited(navigator.push(route));
539-
}
540-
541478
static Future<Uint8List?> _fetchBitmap(Uri url) async {
542479
try {
543480
// TODO timeout to prevent waiting indefinitely

lib/notifications/open.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,80 @@
1+
import 'dart:async';
2+
3+
import 'package:collection/collection.dart';
4+
import 'package:flutter/foundation.dart';
5+
import 'package:flutter/widgets.dart';
6+
17
import '../api/model/model.dart';
8+
import '../generated/l10n/zulip_localizations.dart';
9+
import '../log.dart';
210
import '../model/narrow.dart';
11+
import '../widgets/app.dart';
12+
import '../widgets/dialog.dart';
13+
import '../widgets/message_list.dart';
14+
import '../widgets/page.dart';
15+
import '../widgets/store.dart';
16+
17+
/// Responds to the user opening a notification.
18+
class NotificationOpenService {
19+
20+
/// Provides the route and the account ID by parsing the notification URL.
21+
///
22+
/// The URL must have been generated using [NotificationOpenPayload.buildUrl]
23+
/// while creating the notification.
24+
///
25+
/// Returns null and shows an error dialog if the associated account is not
26+
/// found in the global store.
27+
///
28+
/// The context argument should be a descendant of the app's main [Navigator].
29+
static AccountRoute<void>? routeForNotification({
30+
required BuildContext context,
31+
required Uri url,
32+
}) {
33+
assert(defaultTargetPlatform == TargetPlatform.android);
34+
35+
final globalStore = GlobalStoreWidget.of(context);
36+
37+
assert(debugLog('got notif: url: $url'));
38+
assert(url.scheme == 'zulip' && url.host == 'notification');
39+
final payload = NotificationOpenPayload.parseUrl(url);
40+
41+
final account = globalStore.accounts.firstWhereOrNull(
42+
(account) => account.realmUrl.origin == payload.realmUrl.origin
43+
&& account.userId == payload.userId);
44+
if (account == null) { // TODO(log)
45+
final zulipLocalizations = ZulipLocalizations.of(context);
46+
showErrorDialog(context: context,
47+
title: zulipLocalizations.errorNotificationOpenTitle,
48+
message: zulipLocalizations.errorNotificationOpenAccountNotFound);
49+
return null;
50+
}
51+
52+
return MessageListPage.buildRoute(
53+
accountId: account.id,
54+
// TODO(#82): Open at specific message, not just conversation
55+
narrow: payload.narrow);
56+
}
57+
58+
/// Navigates to the [MessageListPage] of the specific conversation
59+
/// given the `zulip://notification/…` Android intent data URL,
60+
/// generated with [NotificationOpenPayload.buildUrl] while creating
61+
/// the notification.
62+
static Future<void> navigateForNotification(Uri url) async {
63+
assert(defaultTargetPlatform == TargetPlatform.android);
64+
assert(debugLog('opened notif: url: $url'));
65+
66+
NavigatorState navigator = await ZulipApp.navigator;
67+
final context = navigator.context;
68+
assert(context.mounted);
69+
if (!context.mounted) return; // TODO(linter): this is impossible as there's no actual async gap, but the use_build_context_synchronously lint doesn't see that
70+
71+
final route = routeForNotification(context: context, url: url);
72+
if (route == null) return; // TODO(log)
73+
74+
// TODO(nav): Better interact with existing nav stack on notif open
75+
unawaited(navigator.push(route));
76+
}
77+
}
378

479
/// The information contained in 'zulip://notification/…' internal
580
/// Android intent data URL, used for notification-open flow.

lib/widgets/app.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import '../log.dart';
99
import '../model/actions.dart';
1010
import '../model/localizations.dart';
1111
import '../model/store.dart';
12-
import '../notifications/display.dart';
12+
import '../notifications/open.dart';
1313
import 'about_zulip.dart';
1414
import 'dialog.dart';
1515
import 'home.dart';
@@ -176,7 +176,7 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
176176

177177
final initialRouteUrl = Uri.tryParse(initialRoute);
178178
if (initialRouteUrl case Uri(scheme: 'zulip', host: 'notification')) {
179-
final route = NotificationDisplayManager.routeForNotification(
179+
final route = NotificationOpenService.routeForNotification(
180180
context: context,
181181
url: initialRouteUrl);
182182

@@ -209,7 +209,7 @@ class _ZulipAppState extends State<ZulipApp> with WidgetsBindingObserver {
209209
await LoginPage.handleWebAuthUrl(url);
210210
return true;
211211
case Uri(scheme: 'zulip', host: 'notification') && var url:
212-
await NotificationDisplayManager.navigateForNotification(url);
212+
await NotificationOpenService.navigateForNotification(url);
213213
return true;
214214
}
215215
return super.didPushRouteInformation(routeInformation);

0 commit comments

Comments
 (0)