Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
<data android:path="/storm" />
<data android:path="/streak" />

<data android:pathPattern="/broadcast/.*/.*/........" />

<!-- Either game or challenge -->
<data android:pathPattern="/........" />
<data android:pathPattern="/......../black" />
Expand Down
5 changes: 5 additions & 0 deletions lib/src/app_links.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/widgets.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_round_screen.dart';
import 'package:lichess_mobile/src/view/game/archived_game_screen.dart';
import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart';
import 'package:lichess_mobile/src/view/puzzle/storm_screen.dart';
Expand All @@ -20,6 +21,10 @@ Route<dynamic>? resolveAppLinkUri(BuildContext context, Uri appLinkUri) {
case 'study':
final id = appLinkUri.pathSegments[1];
return StudyScreen.buildRoute(context, StudyId(id));
case 'broadcast':
final id = appLinkUri.pathSegments[3];
final tab = BroadcastRoundTab.tabOrNullFromString(appLinkUri.fragment);
return BroadcastRoundScreenLoading.buildRoute(context, BroadcastRoundId(id), initialTab: tab);
case 'training':
final id = appLinkUri.pathSegments[1];
return PuzzleScreen.buildRoute(
Expand Down
11 changes: 9 additions & 2 deletions lib/src/model/broadcast/broadcast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ enum BroadcastResult {
canceled => '0',
whiteHalfWins => side == Side.white ? '½' : '0',
blackHalfWins => side == Side.white ? '0' : '½',
_ => throw FormatException('result $this is not a game that is over'),
_ => throw ArgumentError.value(this, 'result', 'Not a completed game'),
};
}

Expand Down Expand Up @@ -128,7 +128,14 @@ class BroadcastRound with _$BroadcastRound {
}) = _BroadcastRound;
}

typedef BroadcastRoundWithGames = ({BroadcastRound round, BroadcastRoundGames games});
typedef BroadcastRoundResponse =
({
String? groupName,
IList<BroadcastTournamentGroup>? group,
BroadcastTournamentData tournament,
BroadcastRound round,
BroadcastRoundGames games,
});

typedef BroadcastRoundGames = IMap<BroadcastGameId, BroadcastGame>;

Expand Down
5 changes: 5 additions & 0 deletions lib/src/model/broadcast/broadcast_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ Future<BroadcastTournament> broadcastTournament(
);
}

@riverpod
Future<BroadcastRoundResponse> broadcastRound(Ref ref, BroadcastRoundId roundId) {
return ref.withClient((client) => BroadcastRepository(client).getRound(roundId));
}

@riverpod
Future<IList<BroadcastPlayerExtended>> broadcastPlayers(
Ref ref,
Expand Down
16 changes: 13 additions & 3 deletions lib/src/model/broadcast/broadcast_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class BroadcastRepository {
);
}

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

BroadcastRoundWithGames _makeRoundWithGamesFromJson(Map<String, dynamic> json) {
BroadcastRoundResponse _makeRoundWithGamesFromJson(Map<String, dynamic> json) {
final groupName = pick(json, 'group', 'name').asStringOrNull();
final group = pick(json, 'group', 'tours').asListOrNull(_tournamentGroupFromPick)?.toIList();
final tournament = pick(json, 'tour').required();
final round = pick(json, 'round').required();
final games = pick(json, 'games').required();
return (round: _roundFromPick(round), games: _gamesFromPick(games));

return (
groupName: groupName,
group: group,
tournament: _tournamentDataFromPick(tournament),
round: _roundFromPick(round),
games: _gamesFromPick(games),
);
}

BroadcastRoundGames _gamesFromPick(RequiredPick pick) =>
Expand Down
59 changes: 58 additions & 1 deletion lib/src/view/broadcast/broadcast_round_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,23 @@ import 'package:lichess_mobile/src/widgets/buttons.dart';
import 'package:lichess_mobile/src/widgets/filter.dart';
import 'package:lichess_mobile/src/widgets/list.dart';
import 'package:lichess_mobile/src/widgets/platform.dart';
import 'package:lichess_mobile/src/widgets/platform_scaffold.dart';
import 'package:lichess_mobile/src/widgets/settings.dart';

enum BroadcastRoundTab { overview, boards, players }
enum BroadcastRoundTab {
overview,
boards,
players;

static BroadcastRoundTab? tabOrNullFromString(String tab) {
return switch (tab) {
'overview' => BroadcastRoundTab.overview,
'boards' => BroadcastRoundTab.boards,
'players' => BroadcastRoundTab.players,
_ => null,
};
}
}

enum _BroadcastGameFilter {
all,
Expand All @@ -41,6 +55,49 @@ enum _BroadcastGameFilter {
}
}

class BroadcastRoundScreenLoading extends ConsumerWidget {
final BroadcastRoundId roundId;
final BroadcastRoundTab? initialTab;

const BroadcastRoundScreenLoading({super.key, required this.roundId, this.initialTab});

static Route<dynamic> buildRoute(
BuildContext context,
BroadcastRoundId roundId, {
BroadcastRoundTab? initialTab,
}) {
return buildScreenRoute(
context,
screen: BroadcastRoundScreenLoading(roundId: roundId, initialTab: initialTab),
);
}

@override
Widget build(BuildContext context, WidgetRef ref) {
final round = ref.watch(broadcastRoundProvider(roundId));

return switch (round) {
AsyncData(:final value) => BroadcastRoundScreen(
broadcast: Broadcast(
tour: value.tournament,
round: value.round,
group: value.groupName,
roundToLinkId: roundId,
),
initialTab: initialTab,
),
AsyncError(:final error) => PlatformScaffold(
appBarTitle: const Text(''),
body: Center(child: Text('Cannot load round data: $error')),
),
_ => const PlatformScaffold(
appBarTitle: Text(''),
body: Center(child: CircularProgressIndicator.adaptive()),
),
};
}
}

class BroadcastRoundScreen extends ConsumerStatefulWidget {
final Broadcast broadcast;
final BroadcastRoundTab? initialTab;
Expand Down
2 changes: 1 addition & 1 deletion test/model/broadcast/broadcast_repository_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void main() {

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

expect(response, isA<BroadcastRoundWithGames>());
expect(response, isA<BroadcastRoundResponse>());
expect(response.games.length, 5);
});
});
Expand Down
31 changes: 31 additions & 0 deletions test/view/broadcast/broadcast_round_screen_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@ void main() {
expect(find.text('Players'), findsOneWidget);
});

testWidgets('Check that the screen can be loaded from round id', variant: kPlatformVariant, (
tester,
) async {
final app = await makeTestProviderScopeApp(
tester,
home: const BroadcastRoundScreenLoading(roundId: BroadcastRoundId('S5VCwuVn')),
overrides: [lichessClientProvider.overrideWith((ref) => LichessClient(client, ref))],
);

await tester.pumpWidget(app);

expect(find.byType(CircularProgressIndicator), findsOneWidget);

// Load the broadcast data
await tester.pump();

expect(find.byType(CircularProgressIndicator), findsOneWidget);

// Load the tournament data
await tester.pump();

expect(find.byType(CircularProgressIndicator), findsOneWidget);

// Load the round data
await tester.pump();

expect(find.text('Overview'), findsOneWidget);
expect(find.text('Boards'), findsOneWidget);
expect(find.text('Players'), findsOneWidget);
});

testWidgets('Test boards tab with a finished tournament', variant: kPlatformVariant, (
tester,
) async {
Expand Down