11import 'package:dartchess/dartchess.dart' ;
22import 'package:fast_immutable_collections/fast_immutable_collections.dart' ;
33import 'package:flutter/widgets.dart' ;
4+ import 'package:flutter_riverpod/flutter_riverpod.dart' ;
45import 'package:lichess_mobile/src/constants.dart' ;
56import 'package:lichess_mobile/src/model/analysis/analysis_controller.dart' ;
7+ import 'package:lichess_mobile/src/model/challenge/challenge_repository.dart' ;
68import 'package:lichess_mobile/src/model/common/id.dart' ;
9+ import 'package:lichess_mobile/src/model/game/game_repository.dart' ;
710import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart' ;
811import 'package:lichess_mobile/src/model/user/user.dart' ;
912import 'package:lichess_mobile/src/view/analysis/analysis_screen.dart' ;
@@ -12,15 +15,21 @@ import 'package:lichess_mobile/src/view/broadcast/broadcast_round_screen.dart';
1215import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart' ;
1316import 'package:lichess_mobile/src/view/study/study_screen.dart' ;
1417import 'package:lichess_mobile/src/view/tournament/tournament_screen.dart' ;
18+ import 'package:lichess_mobile/src/view/user/challenge_requests_screen.dart' ;
1519import 'package:lichess_mobile/src/view/user/user_or_profile_screen.dart' ;
20+ import 'package:lichess_mobile/src/view/watch/tv_screen.dart' ;
1621import 'package:linkify/linkify.dart' ;
1722import 'package:logging/logging.dart' ;
1823import 'package:url_launcher/url_launcher.dart' ;
1924
2025final _logger = Logger ('AppLinks' );
2126
2227/// Resolves an app link [Uri] to one or more corresponding [Route] s.
23- List <Route <dynamic >>? resolveAppLinkUri (BuildContext context, Uri appLinkUri) {
28+ Future <List <Route <dynamic >>?> resolveAppLinkUri (
29+ BuildContext context,
30+ Uri appLinkUri,
31+ WidgetRef ref,
32+ ) async {
2433 if (appLinkUri.pathSegments.isEmpty) return null ;
2534 _logger.info ('Resolving app link: $appLinkUri ' );
2635 switch (appLinkUri.pathSegments[0 ]) {
@@ -52,31 +61,79 @@ List<Route<dynamic>>? resolveAppLinkUri(BuildContext context, Uri appLinkUri) {
5261 PuzzleScreen .buildRoute (context, angle: PuzzleAngle .fromKey ('mix' ), puzzleId: PuzzleId (id)),
5362 ];
5463 case _:
55- final gameId = GameId (appLinkUri.pathSegments[0 ]);
56- final orientation = appLinkUri.pathSegments.getOrNull (1 );
57- final int ply = int .tryParse (appLinkUri.fragment) ?? 0 ;
58- // The game id can also be a challenge. Challenge by link is not supported yet so let's ignore it.
59- if (gameId.isValid) {
60- return [
61- AnalysisScreen .buildRoute (
62- context,
63- AnalysisOptions .archivedGame (
64- orientation: orientation == 'black' ? Side .black : Side .white,
65- gameId: gameId,
66- initialMoveCursor: ply,
67- ),
64+ final gameRoutes = await _tryResolveGameLink (context, ref, appLinkUri);
65+ if (gameRoutes != null ) return gameRoutes;
66+
67+ if (! context.mounted) return null ;
68+
69+ final challengeRoutes = await _tryResolveChallengeLink (context, ref, appLinkUri);
70+ if (challengeRoutes != null ) return challengeRoutes;
71+ }
72+
73+ return null ;
74+ }
75+
76+ Future <List <Route <dynamic >>?> _tryResolveChallengeLink (
77+ BuildContext context,
78+ WidgetRef ref,
79+ Uri appLinkUri,
80+ ) async {
81+ try {
82+ final challengeId = ChallengeId (appLinkUri.pathSegments[0 ]);
83+ if (! challengeId.isValid) return null ;
84+ final challenge = await ref.read (challengeRepositoryProvider).show (challengeId);
85+ if (! context.mounted) return null ;
86+ return [ChallengeRequestsScreen .buildRoute (context, incomingChallenge: challenge)];
87+ } catch (e) {
88+ _logger.info ('Not a challenge link: $e ' );
89+ }
90+ return null ;
91+ }
92+
93+ Future <List <Route <dynamic >>?> _tryResolveGameLink (
94+ BuildContext context,
95+ WidgetRef ref,
96+ Uri appLinkUri,
97+ ) async {
98+ try {
99+ final gameId = GameId (appLinkUri.pathSegments[0 ]);
100+ if (! gameId.isValid) return null ;
101+
102+ final game = await ref.read (gameRepositoryProvider).getGame (gameId);
103+ final orientation = appLinkUri.pathSegments.getOrNull (1 ) == 'black' ? Side .black : Side .white;
104+ final int ply = int .tryParse (appLinkUri.fragment) ?? 0 ;
105+
106+ if (! context.mounted) return null ;
107+
108+ if (game.finished) {
109+ return [
110+ AnalysisScreen .buildRoute (
111+ context,
112+ AnalysisOptions .archivedGame (
113+ orientation: orientation,
114+ gameId: gameId,
115+ initialMoveCursor: ply,
68116 ),
69- ];
70- }
117+ ),
118+ ];
119+ }
120+
121+ final user = game.playerOf (orientation).user;
122+ if (user != null ) {
123+ return [TvScreen .buildRoute (context, gameId: gameId, user: user, orientation: orientation)];
124+ }
125+ } catch (e) {
126+ _logger.info ('Not a game link: $e ' );
71127 }
72128
73129 return null ;
74130}
75131
76132/// Handles an app link [Uri] by navigating to the corresponding screen(s).
77- void handleAppLink (BuildContext context, Uri uri) {
78- final routes = resolveAppLinkUri (context, uri);
133+ Future < void > handleAppLink (BuildContext context, Uri uri, WidgetRef ref) async {
134+ final routes = await resolveAppLinkUri (context, uri, ref );
79135 if (routes != null ) {
136+ if (! context.mounted) return ;
80137 for (final route in routes) {
81138 Navigator .of (context).push (route);
82139 }
@@ -88,11 +145,11 @@ void handleAppLink(BuildContext context, Uri uri) {
88145const kLichessLinkifiers = [UrlLinkifier (), EmailLinkifier (), UserTagLinkifier ()];
89146
90147/// Handles link clicks in Linkify widgets throughout the app.
91- void onLinkifyOpen (BuildContext context, LinkableElement link) {
148+ Future < void > onLinkifyOpen (BuildContext context, LinkableElement link, WidgetRef ref) async {
92149 if (link is UrlElement && link.url.startsWith (RegExp ('https?:\\ /\\ /$kLichessHost ' ))) {
93150 // Handle Lichess links specifically
94151 final appLinkUri = Uri .parse (link.url);
95- handleAppLink (context, appLinkUri);
152+ await handleAppLink (context, appLinkUri, ref );
96153 } else if (link.originText.startsWith ('@' )) {
97154 final username = link.originText.substring (1 );
98155 Navigator .of (context).push (
0 commit comments