Skip to content

Commit 779f765

Browse files
authored
Merge pull request #1458 from julien4215/broadcast-deeplink
Add deeplink support for broadcast round
2 parents e314a73 + 040db9f commit 779f765

File tree

8 files changed

+124
-7
lines changed

8 files changed

+124
-7
lines changed

android/app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
<data android:path="/storm" />
6565
<data android:path="/streak" />
6666

67+
<data android:pathPattern="/broadcast/.*/.*/........" />
68+
6769
<!-- Either game or challenge -->
6870
<data android:pathPattern="/........" />
6971
<data android:pathPattern="/......../black" />

lib/src/app_links.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
33
import 'package:flutter/widgets.dart';
44
import 'package:lichess_mobile/src/model/common/id.dart';
55
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
6+
import 'package:lichess_mobile/src/view/broadcast/broadcast_round_screen.dart';
67
import 'package:lichess_mobile/src/view/game/archived_game_screen.dart';
78
import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart';
89
import 'package:lichess_mobile/src/view/puzzle/storm_screen.dart';
@@ -20,6 +21,10 @@ Route<dynamic>? resolveAppLinkUri(BuildContext context, Uri appLinkUri) {
2021
case 'study':
2122
final id = appLinkUri.pathSegments[1];
2223
return StudyScreen.buildRoute(context, StudyId(id));
24+
case 'broadcast':
25+
final id = appLinkUri.pathSegments[3];
26+
final tab = BroadcastRoundTab.tabOrNullFromString(appLinkUri.fragment);
27+
return BroadcastRoundScreenLoading.buildRoute(context, BroadcastRoundId(id), initialTab: tab);
2328
case 'training':
2429
final id = appLinkUri.pathSegments[1];
2530
return PuzzleScreen.buildRoute(

lib/src/model/broadcast/broadcast.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ enum BroadcastResult {
4040
canceled => '0',
4141
whiteHalfWins => side == Side.white ? '½' : '0',
4242
blackHalfWins => side == Side.white ? '0' : '½',
43-
_ => throw FormatException('result $this is not a game that is over'),
43+
_ => throw ArgumentError.value(this, 'result', 'Not a completed game'),
4444
};
4545
}
4646

@@ -128,7 +128,14 @@ class BroadcastRound with _$BroadcastRound {
128128
}) = _BroadcastRound;
129129
}
130130

131-
typedef BroadcastRoundWithGames = ({BroadcastRound round, BroadcastRoundGames games});
131+
typedef BroadcastRoundResponse =
132+
({
133+
String? groupName,
134+
IList<BroadcastTournamentGroup>? group,
135+
BroadcastTournamentData tournament,
136+
BroadcastRound round,
137+
BroadcastRoundGames games,
138+
});
132139

133140
typedef BroadcastRoundGames = IMap<BroadcastGameId, BroadcastGame>;
134141

lib/src/model/broadcast/broadcast_providers.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ Future<BroadcastTournament> broadcastTournament(
5252
);
5353
}
5454

55+
@riverpod
56+
Future<BroadcastRoundResponse> broadcastRound(Ref ref, BroadcastRoundId roundId) {
57+
return ref.withClient((client) => BroadcastRepository(client).getRound(roundId));
58+
}
59+
5560
@riverpod
5661
Future<IList<BroadcastPlayerExtended>> broadcastPlayers(
5762
Ref ref,

lib/src/model/broadcast/broadcast_repository.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class BroadcastRepository {
2727
);
2828
}
2929

30-
Future<BroadcastRoundWithGames> getRound(BroadcastRoundId broadcastRoundId) {
30+
Future<BroadcastRoundResponse> getRound(BroadcastRoundId broadcastRoundId) {
3131
return client.readJson(
3232
Uri(path: 'api/broadcast/-/-/$broadcastRoundId'),
3333
// The path parameters with - are the broadcast tournament and round slugs
@@ -137,10 +137,20 @@ BroadcastRound _roundFromPick(RequiredPick pick) {
137137
);
138138
}
139139

140-
BroadcastRoundWithGames _makeRoundWithGamesFromJson(Map<String, dynamic> json) {
140+
BroadcastRoundResponse _makeRoundWithGamesFromJson(Map<String, dynamic> json) {
141+
final groupName = pick(json, 'group', 'name').asStringOrNull();
142+
final group = pick(json, 'group', 'tours').asListOrNull(_tournamentGroupFromPick)?.toIList();
143+
final tournament = pick(json, 'tour').required();
141144
final round = pick(json, 'round').required();
142145
final games = pick(json, 'games').required();
143-
return (round: _roundFromPick(round), games: _gamesFromPick(games));
146+
147+
return (
148+
groupName: groupName,
149+
group: group,
150+
tournament: _tournamentDataFromPick(tournament),
151+
round: _roundFromPick(round),
152+
games: _gamesFromPick(games),
153+
);
144154
}
145155

146156
BroadcastRoundGames _gamesFromPick(RequiredPick pick) =>

lib/src/view/broadcast/broadcast_round_screen.dart

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,23 @@ import 'package:lichess_mobile/src/widgets/buttons.dart';
2222
import 'package:lichess_mobile/src/widgets/filter.dart';
2323
import 'package:lichess_mobile/src/widgets/list.dart';
2424
import 'package:lichess_mobile/src/widgets/platform.dart';
25+
import 'package:lichess_mobile/src/widgets/platform_scaffold.dart';
2526
import 'package:lichess_mobile/src/widgets/settings.dart';
2627

27-
enum BroadcastRoundTab { overview, boards, players }
28+
enum BroadcastRoundTab {
29+
overview,
30+
boards,
31+
players;
32+
33+
static BroadcastRoundTab? tabOrNullFromString(String tab) {
34+
return switch (tab) {
35+
'overview' => BroadcastRoundTab.overview,
36+
'boards' => BroadcastRoundTab.boards,
37+
'players' => BroadcastRoundTab.players,
38+
_ => null,
39+
};
40+
}
41+
}
2842

2943
enum _BroadcastGameFilter {
3044
all,
@@ -41,6 +55,49 @@ enum _BroadcastGameFilter {
4155
}
4256
}
4357

58+
class BroadcastRoundScreenLoading extends ConsumerWidget {
59+
final BroadcastRoundId roundId;
60+
final BroadcastRoundTab? initialTab;
61+
62+
const BroadcastRoundScreenLoading({super.key, required this.roundId, this.initialTab});
63+
64+
static Route<dynamic> buildRoute(
65+
BuildContext context,
66+
BroadcastRoundId roundId, {
67+
BroadcastRoundTab? initialTab,
68+
}) {
69+
return buildScreenRoute(
70+
context,
71+
screen: BroadcastRoundScreenLoading(roundId: roundId, initialTab: initialTab),
72+
);
73+
}
74+
75+
@override
76+
Widget build(BuildContext context, WidgetRef ref) {
77+
final round = ref.watch(broadcastRoundProvider(roundId));
78+
79+
return switch (round) {
80+
AsyncData(:final value) => BroadcastRoundScreen(
81+
broadcast: Broadcast(
82+
tour: value.tournament,
83+
round: value.round,
84+
group: value.groupName,
85+
roundToLinkId: roundId,
86+
),
87+
initialTab: initialTab,
88+
),
89+
AsyncError(:final error) => PlatformScaffold(
90+
appBarTitle: const Text(''),
91+
body: Center(child: Text('Cannot load round data: $error')),
92+
),
93+
_ => const PlatformScaffold(
94+
appBarTitle: Text(''),
95+
body: Center(child: CircularProgressIndicator.adaptive()),
96+
),
97+
};
98+
}
99+
}
100+
44101
class BroadcastRoundScreen extends ConsumerStatefulWidget {
45102
final Broadcast broadcast;
46103
final BroadcastRoundTab? initialTab;

test/model/broadcast/broadcast_repository_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void main() {
5959

6060
final response = await repo.getRound(const BroadcastRoundId(roundId));
6161

62-
expect(response, isA<BroadcastRoundWithGames>());
62+
expect(response, isA<BroadcastRoundResponse>());
6363
expect(response.games.length, 5);
6464
});
6565
});

test/view/broadcast/broadcast_round_screen_test.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,37 @@ void main() {
5656
expect(find.text('Players'), findsOneWidget);
5757
});
5858

59+
testWidgets('Check that the screen can be loaded from round id', variant: kPlatformVariant, (
60+
tester,
61+
) async {
62+
final app = await makeTestProviderScopeApp(
63+
tester,
64+
home: const BroadcastRoundScreenLoading(roundId: BroadcastRoundId('S5VCwuVn')),
65+
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
66+
);
67+
68+
await tester.pumpWidget(app);
69+
70+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
71+
72+
// Load the broadcast data
73+
await tester.pump();
74+
75+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
76+
77+
// Load the tournament data
78+
await tester.pump();
79+
80+
expect(find.byType(CircularProgressIndicator), findsOneWidget);
81+
82+
// Load the round data
83+
await tester.pump();
84+
85+
expect(find.text('Overview'), findsOneWidget);
86+
expect(find.text('Boards'), findsOneWidget);
87+
expect(find.text('Players'), findsOneWidget);
88+
});
89+
5990
testWidgets('Test boards tab with a finished tournament', variant: kPlatformVariant, (
6091
tester,
6192
) async {

0 commit comments

Comments
 (0)