Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broadcast players tab #1243

Merged
merged 6 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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]) {
julien4215 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading