Skip to content

Commit

Permalink
Add broadcast players tab
Browse files Browse the repository at this point in the history
  • Loading branch information
julien4215 committed Dec 10, 2024
1 parent e00ba00 commit ac9f535
Show file tree
Hide file tree
Showing 12 changed files with 618 additions and 228 deletions.
19 changes: 15 additions & 4 deletions lib/src/model/broadcast/broadcast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ typedef BroadcastTournamentGroup = ({

@freezed
class BroadcastRound with _$BroadcastRound {
const BroadcastRound._();

const factory BroadcastRound({
required BroadcastRoundId id,
required String name,
Expand Down Expand Up @@ -117,17 +115,30 @@ class BroadcastGame with _$BroadcastGame {

@freezed
class BroadcastPlayer with _$BroadcastPlayer {
const BroadcastPlayer._();

const factory BroadcastPlayer({
required String name,
required String? title,
required int? rating,
required Duration? clock,
required String? federation,
required FideId? fideId,
}) = _BroadcastPlayer;
}

@freezed
class BroadcastPlayerExtended with _$BroadcastPlayerExtended {
const factory BroadcastPlayerExtended({
required String name,
required String? title,
required int? rating,
required String? federation,
required FideId? fideId,
required int played,
required double? score,
required int? ratingDiff,
}) = _BroadcastPlayerExtended;
}

enum RoundStatus {
live,
finished,
Expand Down
45 changes: 45 additions & 0 deletions lib/src/model/broadcast/broadcast_providers.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
Expand Down Expand Up @@ -54,6 +55,50 @@ Future<BroadcastTournament> broadcastTournament(
);
}

enum BroadcastPlayersSortingTypes { player, elo, score }

@riverpod
class BroadcastPlayers extends _$BroadcastPlayers {
@override
Future<IList<BroadcastPlayerExtended>> build(
BroadcastTournamentId tournamentId,
) async {
final players = ref.withClient(
(client) => BroadcastRepository(client).getPlayers(tournamentId),
);

return players;
}

void sort(BroadcastPlayersSortingTypes sortingType, [bool reverse = false]) {
if (!state.hasValue) return;

final compare = switch (sortingType) {
BroadcastPlayersSortingTypes.player =>
(BroadcastPlayerExtended a, BroadcastPlayerExtended b) =>
a.name.compareTo(b.name),
BroadcastPlayersSortingTypes.elo =>
(BroadcastPlayerExtended a, BroadcastPlayerExtended b) {
if (a.rating == null) return -1;
if (b.rating == null) return 1;
return b.rating!.compareTo(a.rating!);
},
BroadcastPlayersSortingTypes.score =>
(BroadcastPlayerExtended a, BroadcastPlayerExtended b) {
if (a.score == null) return -1;
if (b.score == null) return 1;
return b.score!.compareTo(a.score!);
}
};

state = AsyncData(
reverse
? state.requireValue.sortReversed(compare)
: state.requireValue.sort(compare),
);
}
}

@Riverpod(keepAlive: true)
BroadcastImageWorkerFactory broadcastImageWorkerFactory(Ref ref) {
return const BroadcastImageWorkerFactory();
Expand Down
30 changes: 27 additions & 3 deletions lib/src/model/broadcast/broadcast_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ class BroadcastRepository {
path: '/api/broadcast/top',
queryParameters: {'page': page.toString()},
),
headers: {'Accept': 'application/json'},
mapper: _makeBroadcastResponseFromJson,
);
}
Expand All @@ -28,7 +27,6 @@ class BroadcastRepository {
) {
return client.readJson(
Uri(path: 'api/broadcast/$broadcastTournamentId'),
headers: {'Accept': 'application/json'},
mapper: _makeTournamentFromJson,
);
}
Expand All @@ -40,7 +38,6 @@ class BroadcastRepository {
Uri(path: 'api/broadcast/-/-/$broadcastRoundId'),
// The path parameters with - are the broadcast tournament and round slugs
// They are only used for SEO, so we can safely use - for these parameters
headers: {'Accept': 'application/x-ndjson'},
mapper: _makeRoundWithGamesFromJson,
);
}
Expand All @@ -51,6 +48,15 @@ class BroadcastRepository {
) {
return client.read(Uri(path: 'api/study/$roundId/$gameId.pgn'));
}

Future<IList<BroadcastPlayerExtended>> getPlayers(
BroadcastTournamentId tournamentId,
) {
return client.readJsonList(
Uri(path: '/broadcast/$tournamentId/players'),
mapper: _makePlayerFromJson,
);
}
}

BroadcastList _makeBroadcastResponseFromJson(
Expand Down Expand Up @@ -195,5 +201,23 @@ BroadcastPlayer _playerFromPick(RequiredPick pick) {
rating: pick('rating').asIntOrNull(),
clock: pick('clock').asDurationFromCentiSecondsOrNull(),
federation: pick('fed').asStringOrNull(),
fideId: pick('fideId').asFideIdOrNull(),
);
}

BroadcastPlayerExtended _makePlayerFromJson(Map<String, dynamic> json) {
return _playerExtendedFromPick(pick(json).required());
}

BroadcastPlayerExtended _playerExtendedFromPick(RequiredPick pick) {
return BroadcastPlayerExtended(
name: pick('name').asStringOrThrow(),
title: pick('title').asStringOrNull(),
rating: pick('rating').asIntOrNull(),
federation: pick('fed').asStringOrNull(),
fideId: pick('fideId').asFideIdOrNull(),
played: pick('played').asIntOrThrow(),
score: pick('score').asDoubleOrNull(),
ratingDiff: pick('ratingDiff').asIntOrNull(),
);
}
21 changes: 21 additions & 0 deletions lib/src/model/common/id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ extension type const StudyChapterId(String value) implements StringId {
StudyChapterId.fromJson(dynamic json) : this(json as String);
}

extension type const FideId(String value) implements StringId {}

extension IDPick on Pick {
UserId asUserIdOrThrow() {
final value = required().value;
Expand Down Expand Up @@ -227,4 +229,23 @@ extension IDPick on Pick {
"value $value at $debugParsingExit can't be casted to StudyId",
);
}

FideId asFideIdOrThrow() {
final value = required().value;
if (value is String) {
return FideId(value);
}
throw PickException(
"value $value at $debugParsingExit can't be casted to FideId",
);
}

FideId? asFideIdOrNull() {
if (value == null) return null;
try {
return asFideIdOrThrow();
} catch (_) {
return null;
}
}
}
95 changes: 36 additions & 59 deletions lib/src/view/broadcast/broadcast_boards_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast_round_controller.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/duration.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/lichess_assets.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/utils/screen.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_screen.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
import 'package:lichess_mobile/src/widgets/board_thumbnail.dart';
import 'package:lichess_mobile/src/widgets/clock.dart';
import 'package:lichess_mobile/src/widgets/shimmer.dart';
Expand All @@ -34,34 +32,42 @@ class BroadcastBoardsTab extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final edgeInsets = MediaQuery.paddingOf(context) -
(Theme.of(context).platform == TargetPlatform.iOS
? EdgeInsets.only(top: MediaQuery.paddingOf(context).top)
: EdgeInsets.zero) +
Styles.bodyPadding;
final round = ref.watch(broadcastRoundControllerProvider(roundId));

return switch (round) {
AsyncData(:final value) => value.games.isEmpty
? SliverPadding(
padding: const EdgeInsets.only(top: 16.0),
sliver: SliverToBoxAdapter(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.info, size: 30),
Text(context.l10n.broadcastNoBoardsYet),
],
return SliverPadding(
padding: edgeInsets,
sliver: switch (round) {
AsyncData(:final value) => value.games.isEmpty
? SliverPadding(
padding: const EdgeInsets.only(top: 16.0),
sliver: SliverToBoxAdapter(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.info, size: 30),
Text(context.l10n.broadcastNoBoardsYet),
],
),
),
)
: BroadcastPreview(
games: value.games.values.toIList(),
roundId: roundId,
title: value.round.name,
),
)
: BroadcastPreview(
games: value.games.values.toIList(),
roundId: roundId,
title: value.round.name,
AsyncError(:final error) => SliverFillRemaining(
child: Center(
child: Text('Could not load broadcast: $error'),
),
AsyncError(:final error) => SliverFillRemaining(
child: Center(
child: Text('Could not load broadcast: $error'),
),
),
_ => BroadcastPreview.loading(roundId: roundId),
};
_ => BroadcastPreview.loading(roundId: roundId),
},
);
}
}

Expand Down Expand Up @@ -210,40 +216,11 @@ class _PlayerWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (player.federation != null) ...[
Consumer(
builder: (context, widgetRef, _) {
return SvgPicture.network(
lichessFideFedSrc(player.federation!),
height: 12,
httpClient: widgetRef.read(defaultClientProvider),
);
},
),
],
const SizedBox(width: 5),
if (player.title != null) ...[
Text(
player.title!,
style: const TextStyle().copyWith(
color: context.lichessColors.brag,
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 5),
],
Flexible(
child: Text(
player.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
Expanded(
child: BroadcastPlayerWidget(
federation: player.federation,
title: player.title,
name: player.name,
),
),
const SizedBox(width: 5),
Expand Down
Loading

0 comments on commit ac9f535

Please sign in to comment.