Skip to content

Commit 7e3c228

Browse files
committed
refactor: extract notification tap routing into its own file
Legacy handled tap → navigation inside notify.dart; the rewrite had it as a private method on the app service host. Move it to notification_routes.dart (which already owns the channel → route table) as routeNotificationTap — the app host just registers it on NotificationTaps — with an injectable navigate for tests.
1 parent d740ca8 commit 7e3c228

3 files changed

Lines changed: 67 additions & 11 deletions

File tree

lib/app/app.dart

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'package:dpip/core/geo/device_location_reporter.dart';
66
import 'package:dpip/core/geo/location_monitor.dart';
77
import 'package:dpip/core/geo/location_service.dart';
88
import 'package:dpip/core/notifications/notification_service.dart';
9-
import 'package:dpip/core/notifications/notification_tap.dart';
109
import 'package:dpip/core/notifications/notification_taps.dart';
1110
import 'package:dpip/core/platform/background_location.dart';
1211
import 'package:dpip/core/realtime/realtime_lifecycle.dart';
@@ -75,8 +74,8 @@ class DpipApp extends StatelessWidget {
7574

7675
/// Owns app-level service wiring that needs a [State]/lifecycle:
7776
/// - the realtime spine's start/pause/resume (via [RealtimeLifecycleObserver]);
78-
/// - routing a tapped notification to the right tab (the channel-key → route
79-
/// mapping lives here because this layer owns the router);
77+
/// - registering the notification tap → route handler ([routeNotificationTap],
78+
/// whose channel→screen table lives in `notification_routes.dart`);
8079
/// - requesting notification permission once, after the first frame;
8180
/// - resolving the current GPS township into the [RegionStore] so 所在地 shows
8281
/// the real location (and the "can't get location" state when GPS is off).
@@ -117,7 +116,7 @@ class _AppServicesHostState extends State<_AppServicesHost>
117116
super.initState();
118117
_observer = RealtimeLifecycleObserver(widget.realtimeService);
119118
WidgetsBinding.instance.addObserver(this);
120-
NotificationTaps.onTap = _routeNotificationTap;
119+
NotificationTaps.onTap = routeNotificationTap;
121120
widget.onboarding.addListener(_onOnboardingChanged);
122121
WidgetsBinding.instance.addPostFrameCallback((_) {
123122
widget.realtimeService.startAll();
@@ -188,13 +187,6 @@ class _AppServicesHostState extends State<_AppServicesHost>
188187
super.dispose();
189188
}
190189

191-
/// Sends a tapped notification to a screen. The destination is resolved by the
192-
/// declarative channel→route table; the tap's [NotificationTap.id] is carried
193-
/// for the per-item detail routes to come.
194-
void _routeNotificationTap(NotificationTap tap) {
195-
appRouter.goNamed(routeForNotificationChannel(tap.channelKey));
196-
}
197-
198190
@override
199191
Widget build(BuildContext context) => widget.child;
200192
}

lib/app/router/notification_routes.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
1+
import 'package:dpip/app/router/app_router.dart';
12
import 'package:dpip/core/logging/log.dart';
23
import 'package:dpip/core/notifications/notification_channels.dart';
4+
import 'package:dpip/core/notifications/notification_tap.dart';
35
import 'package:dpip/shared/navigation/app_routes.dart';
46

7+
/// The slice of the router a notification tap needs — [GoRouter.goNamed].
8+
typedef NotificationRouteNavigator = void Function(
9+
String name, {
10+
Map<String, String> pathParameters,
11+
Map<String, dynamic> queryParameters,
12+
String? fragment,
13+
Object? extra,
14+
});
15+
16+
/// Single owner of notification → destination, mirroring the legacy
17+
/// `notify.dart` tap table in one file: [NotificationTaps] carries the tap
18+
/// intent and calls [routeNotificationTap] once the router is live (replaying a
19+
/// cold-start tap through [NotificationTaps.drainPending]). The channel
20+
/// resolves to a route name via the declarative group table below, then the
21+
/// router navigates — no widget hosts this logic, so adding an alert family is
22+
/// one row in the table and nothing else.
23+
///
24+
/// [navigate] is injectable for tests; it defaults to the app router's
25+
/// [GoRouter.goNamed].
26+
void routeNotificationTap(
27+
NotificationTap tap, {
28+
NotificationRouteNavigator? navigate,
29+
}) {
30+
(navigate ?? appRouter.goNamed)(routeForNotificationChannel(tap.channelKey));
31+
}
32+
533
/// Resolves a tapped notification's channel to a destination route.
634
///
735
/// Declarative and group-driven (via [NotificationChannels.groupOf]) instead of

test/app/router/notification_routes_test.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:dpip/app/router/notification_routes.dart';
22
import 'package:dpip/core/notifications/notification_channels.dart';
3+
import 'package:dpip/core/notifications/notification_tap.dart';
34
import 'package:dpip/shared/navigation/app_routes.dart';
45
import 'package:flutter_test/flutter_test.dart';
56

@@ -70,4 +71,39 @@ void main() {
7071
expect(routeForNotificationChannel('does-not-exist'), AppRoutes.home);
7172
expect(routeForNotificationChannel(null), AppRoutes.home);
7273
});
74+
75+
test('routeNotificationTap navigates to the resolved route', () {
76+
final named = <String>[];
77+
void record(
78+
String name, {
79+
Map<String, String> pathParameters = const {},
80+
Map<String, dynamic> queryParameters = const {},
81+
String? fragment,
82+
Object? extra,
83+
}) {
84+
named.add(name);
85+
}
86+
87+
// EEW channel → the live monitor tab.
88+
routeNotificationTap(
89+
const NotificationTap(channelKey: 'eew_alert-important-v2'),
90+
navigate: record,
91+
);
92+
// Report family → the report list tab.
93+
routeNotificationTap(
94+
const NotificationTap(channelKey: 'report-general-v2'),
95+
navigate: record,
96+
);
97+
// An unmapped channel falls back to home rather than throwing.
98+
routeNotificationTap(
99+
const NotificationTap(channelKey: 'does-not-exist'),
100+
navigate: record,
101+
);
102+
103+
expect(named, [
104+
AppRoutes.eew,
105+
AppRoutes.earthquake,
106+
AppRoutes.home,
107+
]);
108+
});
73109
}

0 commit comments

Comments
 (0)