@@ -15,6 +15,8 @@ class DeepLinkRouter {
1515 required String source,
1616 String ? baseLocation,
1717 void Function (int tabIndex)? tabSetter,
18+ void Function (String planId, int ? dayNumber, String ? planLanguage)?
19+ planNavigator,
1820 }) {
1921 try {
2022 final destination = _resolveRoute (uri);
@@ -23,6 +25,20 @@ class DeepLinkRouter {
2325 return false ;
2426 }
2527
28+ // Plan links can't be expressed as a plain location: opening a specific
29+ // plan requires resolving the plan model from its id, which the injected
30+ // [planNavigator] handles (mirroring the PLAN push-notification path).
31+ // When no navigator is wired (e.g. the Airbridge path), fall through to
32+ // the destination's fallback location instead.
33+ final planId = destination.planId;
34+ if (planId != null && planNavigator != null ) {
35+ _logger.info (
36+ 'Deep link from $source -> plan $planId day ${destination .dayNumber } lang ${destination .planLanguage } ($uri )' ,
37+ );
38+ planNavigator (planId, destination.dayNumber, destination.planLanguage);
39+ return true ;
40+ }
41+
2642 _logger.info ('Deep link from $source -> ${destination .location } ($uri )' );
2743 if (destination.opensOnTop && baseLocation != null ) {
2844 router.go (baseLocation);
@@ -140,6 +156,29 @@ class DeepLinkRouter {
140156 return const _DeepLinkDestination (AppRoutes .home, tabIndex: _meTabIndex);
141157 }
142158
159+ if (segments.length >= 3 &&
160+ segments[0 ] == 'open' &&
161+ segments[1 ] == 'plan' ) {
162+ final planId = segments[2 ];
163+ // /open/plan/{planId}/day/{dayNumber}?lang={language} — specific day deep link
164+ int ? dayNumber;
165+ if (segments.length >= 5 && segments[3 ] == 'day' ) {
166+ dayNumber = int .tryParse (segments[4 ]);
167+ }
168+ // lang carries the content language the plan was enrolled in, so the
169+ // recipient's app can find the enrollment even across locale differences.
170+ final planLanguage = uri.queryParameters['lang' ];
171+ // Fallback location (My Practices) is used only when no planNavigator is
172+ // wired; otherwise the navigator resolves and opens the specific plan.
173+ return _DeepLinkDestination (
174+ AppRoutes .practiceMyPractices,
175+ planId: planId,
176+ dayNumber: dayNumber,
177+ planLanguage: planLanguage,
178+ opensOnTop: true ,
179+ );
180+ }
181+
143182 if (segments.length >= 3 &&
144183 segments[0 ] == 'open' &&
145184 segments[1 ] == 'group' ) {
@@ -208,10 +247,26 @@ class _DeepLinkDestination {
208247 /// the `/home` route (e.g. the Me tab).
209248 final int ? tabIndex;
210249
250+ /// When non-null, this destination targets a specific plan that must be
251+ /// resolved from its id by the injected `planNavigator` . [location] then
252+ /// acts only as a fallback for callers without a navigator.
253+ final String ? planId;
254+
255+ /// When non-null, the plan navigator should open this specific day instead
256+ /// of computing today's day from the plan start date.
257+ final int ? dayNumber;
258+
259+ /// Content language of the shared plan (e.g. 'en', 'bo'). Passed to the
260+ /// plan navigator so it can find the enrollment across locale differences.
261+ final String ? planLanguage;
262+
211263 const _DeepLinkDestination (
212264 this .location, {
213265 this .extra,
214266 this .opensOnTop = false ,
215267 this .tabIndex,
268+ this .planId,
269+ this .dayNumber,
270+ this .planLanguage,
216271 });
217272}
0 commit comments