Skip to content

Commit

Permalink
Merge branch 'julien4215-broadcast-players-tab'
Browse files Browse the repository at this point in the history
veloce committed Dec 11, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents f537fe2 + ef3d929 commit 2883908
Showing 12 changed files with 639 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
@@ -75,8 +75,6 @@ typedef BroadcastTournamentGroup = ({

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

const factory BroadcastRound({
required BroadcastRoundId id,
required String name,
@@ -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,
11 changes: 11 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';
@@ -54,6 +55,16 @@ Future<BroadcastTournament> broadcastTournament(
);
}

@riverpod
Future<IList<BroadcastPlayerExtended>> broadcastPlayers(
Ref ref,
BroadcastTournamentId tournamentId,
) {
return ref.withClient(
(client) => BroadcastRepository(client).getPlayers(tournamentId),
);
}

@Riverpod(keepAlive: true)
BroadcastImageWorkerFactory broadcastImageWorkerFactory(Ref ref) {
return const BroadcastImageWorkerFactory();
30 changes: 27 additions & 3 deletions lib/src/model/broadcast/broadcast_repository.dart
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ class BroadcastRepository {
path: '/api/broadcast/top',
queryParameters: {'page': page.toString()},
),
headers: {'Accept': 'application/json'},
mapper: _makeBroadcastResponseFromJson,
);
}
@@ -28,7 +27,6 @@ class BroadcastRepository {
) {
return client.readJson(
Uri(path: 'api/broadcast/$broadcastTournamentId'),
headers: {'Accept': 'application/json'},
mapper: _makeTournamentFromJson,
);
}
@@ -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,
);
}
@@ -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(
@@ -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
@@ -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;
@@ -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
@@ -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';
@@ -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),
},
);
}
}

@@ -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),
44 changes: 9 additions & 35 deletions lib/src/view/broadcast/broadcast_game_screen.dart
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ 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/svg.dart';
import 'package:lichess_mobile/src/constants.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
@@ -14,16 +13,15 @@ import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/engine/evaluation_service.dart';
import 'package:lichess_mobile/src/model/settings/board_preferences.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/view/analysis/analysis_layout.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_bottom_bar.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_settings.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_tree_view.dart';
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
import 'package:lichess_mobile/src/view/engine/engine_lines.dart';
import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_view.dart';
@@ -386,40 +384,16 @@ class _PlayerWidget extends ConsumerWidget {
),
const SizedBox(width: 16.0),
],
if (player.federation != null) ...[
SvgPicture.network(
lichessFideFedSrc(player.federation!),
height: 12,
httpClient: ref.read(defaultClientProvider),
Expanded(
child: BroadcastPlayerWidget(
federation: player.federation,
title: player.title,
name: player.name,
rating: player.rating,
textStyle:
const TextStyle().copyWith(fontWeight: FontWeight.bold),
),
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),
],
Text(
player.name,
style: const TextStyle().copyWith(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
if (player.rating != null) ...[
const SizedBox(width: 5),
Text(
player.rating.toString(),
style: const TextStyle(),
overflow: TextOverflow.ellipsis,
),
],
const Spacer(),
if (clock != null)
Container(
height: kAnalysisBoardHeaderOrFooterHeight,
Loading

0 comments on commit 2883908

Please sign in to comment.