mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
Merge branch 'HaonRekcef-team-results-team-page'
This commit is contained in:
@@ -107,6 +107,7 @@ sealed class BroadcastTournamentData with _$BroadcastTournamentData {
|
|||||||
// PRIVATE=-1, NORMAL=3, HIGH=4, BEST=5
|
// PRIVATE=-1, NORMAL=3, HIGH=4, BEST=5
|
||||||
int? tier,
|
int? tier,
|
||||||
bool? teamTable,
|
bool? teamTable,
|
||||||
|
bool? showTeamScores,
|
||||||
required BroadcastTournamentInformation information,
|
required BroadcastTournamentInformation information,
|
||||||
}) = _BroadcastTournamentData;
|
}) = _BroadcastTournamentData;
|
||||||
}
|
}
|
||||||
@@ -337,3 +338,26 @@ sealed class BroadcastTeamMatch with _$BroadcastTeamMatch {
|
|||||||
required IList<BroadcastTeamGame> games,
|
required IList<BroadcastTeamGame> games,
|
||||||
}) = _BroadcastTeamMatch;
|
}) = _BroadcastTeamMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class BroadcastTeamStandingMatch with _$BroadcastTeamStandingMatch {
|
||||||
|
const factory BroadcastTeamStandingMatch({
|
||||||
|
required BroadcastRoundId roundId,
|
||||||
|
required String opponent,
|
||||||
|
required String points,
|
||||||
|
required double mp,
|
||||||
|
required double gp,
|
||||||
|
}) = _BroadcastTeamStandingMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class BroadcastTeamStanding with _$BroadcastTeamStanding {
|
||||||
|
const factory BroadcastTeamStanding({
|
||||||
|
required String name,
|
||||||
|
required double mp,
|
||||||
|
required double gp,
|
||||||
|
required IList<BroadcastTeamStandingMatch> matches,
|
||||||
|
required IList<BroadcastPlayerWithOverallResult> players,
|
||||||
|
required int? averageRating,
|
||||||
|
}) = _BroadcastTeamStanding;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
|
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
|
||||||
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
|
import 'package:lichess_mobile/src/model/broadcast/broadcast_repository.dart';
|
||||||
import 'package:lichess_mobile/src/model/common/id.dart';
|
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||||
|
import 'package:lichess_mobile/src/network/http.dart';
|
||||||
|
|
||||||
/// A provider that fetches a paginated list of broadcasts.
|
/// A provider that fetches a paginated list of broadcasts.
|
||||||
final broadcastsPaginatorProvider =
|
final broadcastsPaginatorProvider =
|
||||||
@@ -104,3 +105,14 @@ final broadcastTeamMatchesProvider = FutureProvider.autoDispose
|
|||||||
.family<IList<BroadcastTeamMatch>, BroadcastRoundId>((Ref ref, BroadcastRoundId roundId) {
|
.family<IList<BroadcastTeamMatch>, BroadcastRoundId>((Ref ref, BroadcastRoundId roundId) {
|
||||||
return ref.read(broadcastRepositoryProvider).getTeamMatches(roundId);
|
return ref.read(broadcastRepositoryProvider).getTeamMatches(roundId);
|
||||||
}, name: 'BroadcastTeamMatchesProvider');
|
}, name: 'BroadcastTeamMatchesProvider');
|
||||||
|
|
||||||
|
final broadcastTeamStandingsProvider = FutureProvider.autoDispose
|
||||||
|
.family<IList<BroadcastTeamStanding>, BroadcastTournamentId>((
|
||||||
|
Ref ref,
|
||||||
|
BroadcastTournamentId tournamentId,
|
||||||
|
) {
|
||||||
|
return ref.withClientCacheFor(
|
||||||
|
(client) => ref.read(broadcastRepositoryProvider).getTeamStandings(tournamentId),
|
||||||
|
const Duration(seconds: 30),
|
||||||
|
);
|
||||||
|
}, name: 'BroadcastTeamStandingsProvider');
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ class BroadcastRepository {
|
|||||||
pick(json, 'table').asListOrThrow<BroadcastTeamMatch>(_teamMatchFromPick).toIList(),
|
pick(json, 'table').asListOrThrow<BroadcastTeamMatch>(_teamMatchFromPick).toIList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<IList<BroadcastTeamStanding>> getTeamStandings(BroadcastTournamentId tournamentId) {
|
||||||
|
return client.readJsonList(
|
||||||
|
Uri(path: 'broadcast/$tournamentId/teams/standings'),
|
||||||
|
mapper: (json) => _teamStandingFromPick(pick(json).required()),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BroadcastList broadcastListFromServerJson(Map<String, dynamic> json) {
|
BroadcastList broadcastListFromServerJson(Map<String, dynamic> json) {
|
||||||
@@ -130,6 +137,7 @@ BroadcastTournamentData _tournamentDataFromPick(RequiredPick pick) => BroadcastT
|
|||||||
imageUrl: pick('image').asStringOrNull(),
|
imageUrl: pick('image').asStringOrNull(),
|
||||||
description: pick('description').asStringOrNull(),
|
description: pick('description').asStringOrNull(),
|
||||||
teamTable: pick('teamTable').asBoolOrFalse(),
|
teamTable: pick('teamTable').asBoolOrFalse(),
|
||||||
|
showTeamScores: pick('showTeamScores').asBoolOrFalse(),
|
||||||
information: (
|
information: (
|
||||||
format: pick('info', 'format').asStringOrNull(),
|
format: pick('info', 'format').asStringOrNull(),
|
||||||
timeControl: pick('info', 'tc').asStringOrNull(),
|
timeControl: pick('info', 'tc').asStringOrNull(),
|
||||||
@@ -404,3 +412,24 @@ BroadcastTeamGame _teamGameFromPick(RequiredPick pick) {
|
|||||||
pov: pick('pov').asSideOrThrow(),
|
pov: pick('pov').asSideOrThrow(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BroadcastTeamStanding _teamStandingFromPick(RequiredPick pick) {
|
||||||
|
return BroadcastTeamStanding(
|
||||||
|
name: pick('name').asStringOrThrow(),
|
||||||
|
mp: pick('mp').asDoubleOrThrow(),
|
||||||
|
gp: pick('gp').asDoubleOrThrow(),
|
||||||
|
matches: pick('matches').asListOrEmpty(_teamStandingMatchFromPick).toIList(),
|
||||||
|
players: pick('players').asListOrEmpty(_playerWithOverallResultFromPick).toIList(),
|
||||||
|
averageRating: pick('averageRating').asIntOrNull(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BroadcastTeamStandingMatch _teamStandingMatchFromPick(RequiredPick pick) {
|
||||||
|
return BroadcastTeamStandingMatch(
|
||||||
|
roundId: pick('roundId').asBroadcastRoundIdOrThrow(),
|
||||||
|
opponent: pick('opponent').asStringOrThrow(),
|
||||||
|
points: pick('points').asStringOrThrow(),
|
||||||
|
mp: pick('mp').asDoubleOrThrow(),
|
||||||
|
gp: pick('gp').asDoubleOrThrow(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:lichess_mobile/src/utils/navigation.dart';
|
|||||||
import 'package:lichess_mobile/src/utils/share.dart';
|
import 'package:lichess_mobile/src/utils/share.dart';
|
||||||
import 'package:lichess_mobile/src/view/broadcast/broadcast_game_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/view/broadcast/broadcast_player_widget.dart';
|
||||||
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_team_screen.dart';
|
||||||
import 'package:lichess_mobile/src/widgets/buttons.dart';
|
import 'package:lichess_mobile/src/widgets/buttons.dart';
|
||||||
import 'package:lichess_mobile/src/widgets/network_image.dart';
|
import 'package:lichess_mobile/src/widgets/network_image.dart';
|
||||||
import 'package:lichess_mobile/src/widgets/platform.dart';
|
import 'package:lichess_mobile/src/widgets/platform.dart';
|
||||||
@@ -308,13 +309,22 @@ class _OverallStatPlayer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (team != null) ...[
|
if (team != null) ...[
|
||||||
Row(
|
GestureDetector(
|
||||||
children: [
|
onTap: () {
|
||||||
const SizedBox(width: 100, child: Text('Team')),
|
if (tournament.data.showTeamScores == true) {
|
||||||
Expanded(
|
Navigator.of(context).push(
|
||||||
child: Text(team.trim(), style: Theme.of(context).textTheme.bodyLarge),
|
BroadcastTeamScreen.buildRoute(context, tournament.data.id, team),
|
||||||
),
|
);
|
||||||
],
|
}
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 100, child: Text('Team')),
|
||||||
|
Expanded(
|
||||||
|
child: Text(team, style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ class _BroadcastRoundScreenState extends ConsumerState<BroadcastRoundScreen>
|
|||||||
roundId: _selectedRoundId ?? value.defaultRoundId,
|
roundId: _selectedRoundId ?? value.defaultRoundId,
|
||||||
tournamentId: _selectedTournamentId,
|
tournamentId: _selectedTournamentId,
|
||||||
tournamentSlug: value.data.slug,
|
tournamentSlug: value.data.slug,
|
||||||
|
showTeamScores: value.data.showTeamScores == true,
|
||||||
),
|
),
|
||||||
_ => const SizedBox.shrink(),
|
_ => const SizedBox.shrink(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:lichess_mobile/src/model/broadcast/broadcast.dart';
|
||||||
|
import 'package:lichess_mobile/src/model/broadcast/broadcast_providers.dart';
|
||||||
|
import 'package:lichess_mobile/src/model/common/id.dart';
|
||||||
|
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||||
|
import 'package:lichess_mobile/src/theme.dart';
|
||||||
|
import 'package:lichess_mobile/src/utils/l10n_context.dart';
|
||||||
|
import 'package:lichess_mobile/src/utils/navigation.dart';
|
||||||
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_results_screen.dart';
|
||||||
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
|
||||||
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_round_screen.dart';
|
||||||
|
import 'package:lichess_mobile/src/widgets/network_image.dart';
|
||||||
|
import 'package:lichess_mobile/src/widgets/platform.dart';
|
||||||
|
import 'package:lichess_mobile/src/widgets/stat_card.dart';
|
||||||
|
|
||||||
|
class BroadcastTeamScreen extends ConsumerWidget {
|
||||||
|
const BroadcastTeamScreen({super.key, required this.tournamentId, required this.teamName});
|
||||||
|
|
||||||
|
final BroadcastTournamentId tournamentId;
|
||||||
|
final String teamName;
|
||||||
|
|
||||||
|
static Route<dynamic> buildRoute(
|
||||||
|
BuildContext context,
|
||||||
|
BroadcastTournamentId tournamentId,
|
||||||
|
String teamName,
|
||||||
|
) {
|
||||||
|
return buildScreenRoute(
|
||||||
|
screen: BroadcastTeamScreen(tournamentId: tournamentId, teamName: teamName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final standingsAsync = ref.watch(broadcastTeamStandingsProvider(tournamentId));
|
||||||
|
final tournamentAsync = ref.watch(broadcastTournamentProvider(tournamentId));
|
||||||
|
|
||||||
|
return PlatformScaffold(
|
||||||
|
appBar: PlatformAppBar(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.groups_3),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Flexible(child: Text(teamName, overflow: .ellipsis)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: switch ((standingsAsync, tournamentAsync)) {
|
||||||
|
(AsyncData(value: final standings), AsyncData(value: final tournament)) => () {
|
||||||
|
final team = standings.where((t) => t.name == teamName).firstOrNull;
|
||||||
|
if (team == null) {
|
||||||
|
return const Center(child: Text('Team not found'));
|
||||||
|
}
|
||||||
|
return _Body(tournament: tournament, team: team);
|
||||||
|
}(),
|
||||||
|
(AsyncError(:final error), _) ||
|
||||||
|
(_, AsyncError(:final error)) => Center(child: Text('Cannot load data: $error')),
|
||||||
|
_ => const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Body extends StatelessWidget {
|
||||||
|
const _Body({required this.tournament, required this.team});
|
||||||
|
|
||||||
|
final BroadcastTournament tournament;
|
||||||
|
final BroadcastTeamStanding team;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
_OverallTeamStat(team: team),
|
||||||
|
if (team.players.isNotEmpty) ...[
|
||||||
|
_SectionHeader(title: context.l10n.players),
|
||||||
|
...team.players.asMap().entries.map(
|
||||||
|
(entry) => _PlayerListTile(
|
||||||
|
tournament: tournament,
|
||||||
|
playerResult: entry.value,
|
||||||
|
index: entry.key,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
if (team.matches.isNotEmpty) ...[
|
||||||
|
_SectionHeader(title: context.l10n.broadcastMatchHistory),
|
||||||
|
_MatchHistoryTable(team: team, tournament: tournament),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 32.0),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OverallTeamStat extends StatelessWidget {
|
||||||
|
const _OverallTeamStat({required this.team});
|
||||||
|
|
||||||
|
final BroadcastTeamStanding team;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final statWidth =
|
||||||
|
(MediaQuery.sizeOf(context).width - Styles.bodyPadding.horizontal - 10 * 2) / 3;
|
||||||
|
const cardSpacing = 10.0;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: Styles.bodyPadding.copyWith(top: 16.0, bottom: 16.0),
|
||||||
|
child: Column(
|
||||||
|
spacing: cardSpacing,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: .center,
|
||||||
|
spacing: cardSpacing,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: statWidth,
|
||||||
|
child: _StatCard(
|
||||||
|
context.l10n.broadcastMatches,
|
||||||
|
value: team.matches.length.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: statWidth,
|
||||||
|
child: _StatCard(
|
||||||
|
context.l10n.broadcastMatchPoints,
|
||||||
|
value: NumberFormat('#.#').format(team.mp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: statWidth,
|
||||||
|
child: _StatCard(
|
||||||
|
context.l10n.broadcastGamePoints,
|
||||||
|
value: NumberFormat('#.#').format(team.gp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (team.averageRating != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: .center,
|
||||||
|
spacing: cardSpacing,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: statWidth,
|
||||||
|
child: _StatCard(context.l10n.averageElo, value: team.averageRating.toString()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SectionHeader extends StatelessWidget {
|
||||||
|
const _SectionHeader({required this.title});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ColoredBox(
|
||||||
|
color: ColorScheme.of(context).surfaceDim,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(title, style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayerListTile extends StatelessWidget {
|
||||||
|
const _PlayerListTile({
|
||||||
|
required this.tournament,
|
||||||
|
required this.playerResult,
|
||||||
|
required this.index,
|
||||||
|
});
|
||||||
|
|
||||||
|
final BroadcastTournament tournament;
|
||||||
|
final BroadcastPlayerWithOverallResult playerResult;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scoreStr = playerResult.score != null
|
||||||
|
? NumberFormat('#.#').format(playerResult.score)
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
final pic = playerResult.player.fideId != null
|
||||||
|
? tournament.photos?.get(playerResult.player.fideId!)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
tileColor: index.isEven ? context.lichessTheme.rowEven : context.lichessTheme.rowOdd,
|
||||||
|
leading: ClipRRect(
|
||||||
|
borderRadius: Styles.thumbnailBorderRadius,
|
||||||
|
child: pic != null
|
||||||
|
? HttpNetworkImageWidget(pic.smallUrl, width: 40, height: 40)
|
||||||
|
: playerResult.player.isBot
|
||||||
|
? Image.asset('assets/images/anon-engine.webp', width: 40, height: 40)
|
||||||
|
: Image.asset('assets/images/anon-face.webp', width: 40, height: 40),
|
||||||
|
),
|
||||||
|
title: BroadcastPlayerWidget(player: playerResult.player, showRating: false),
|
||||||
|
subtitle: playerResult.player.rating != null
|
||||||
|
? Text(playerResult.player.rating.toString())
|
||||||
|
: null,
|
||||||
|
trailing: Column(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
crossAxisAlignment: .end,
|
||||||
|
mainAxisAlignment: .center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$scoreStr / ${playerResult.played}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: .bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Score',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (playerResult.player.id == null) return;
|
||||||
|
Navigator.of(context).push(
|
||||||
|
BroadcastPlayerResultsScreen.buildRoute(
|
||||||
|
tournament.data.id,
|
||||||
|
playerResult.player,
|
||||||
|
playerResult.player.id!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatCard extends StatelessWidget {
|
||||||
|
const _StatCard(this.stat, {this.value});
|
||||||
|
|
||||||
|
final String stat;
|
||||||
|
final String? value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StatCard(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
|
||||||
|
stat,
|
||||||
|
value: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MatchHistoryTable extends StatelessWidget {
|
||||||
|
const _MatchHistoryTable({required this.team, required this.tournament});
|
||||||
|
|
||||||
|
final BroadcastTeamStanding team;
|
||||||
|
final BroadcastTournament tournament;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Table(
|
||||||
|
columnWidths: const {
|
||||||
|
0: FixedColumnWidth(100),
|
||||||
|
1: FlexColumnWidth(),
|
||||||
|
2: FixedColumnWidth(70),
|
||||||
|
3: FixedColumnWidth(70),
|
||||||
|
},
|
||||||
|
defaultVerticalAlignment: .middle,
|
||||||
|
children: [
|
||||||
|
const TableRow(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||||
|
child: Text('Round', style: TextStyle(fontWeight: .bold)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
child: Text('Team', style: TextStyle(fontWeight: .bold)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
child: Text(
|
||||||
|
'MP',
|
||||||
|
textAlign: .center,
|
||||||
|
style: TextStyle(fontWeight: .bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||||
|
child: Text(
|
||||||
|
'GP',
|
||||||
|
textAlign: .center,
|
||||||
|
style: TextStyle(fontWeight: .bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
...team.matches.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final match = entry.value;
|
||||||
|
|
||||||
|
Color? pointsColor;
|
||||||
|
if (match.points == '1') {
|
||||||
|
pointsColor = context.lichessColors.good;
|
||||||
|
} else if (match.points == '0') {
|
||||||
|
pointsColor = context.lichessColors.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableRow(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: index.isEven ? context.lichessTheme.rowEven : context.lichessTheme.rowOdd,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
_TableTapCell(
|
||||||
|
match: match,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||||
|
child: Text((index + 1).toString()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableRowInkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
BroadcastTeamScreen.buildRoute(context, tournament.data.id, match.opponent),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
child: Text(match.opponent, maxLines: 2, overflow: .ellipsis),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_TableTapCell(
|
||||||
|
match: match,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
child: Text(
|
||||||
|
NumberFormat('#.#').format(match.mp),
|
||||||
|
textAlign: .center,
|
||||||
|
style: TextStyle(fontWeight: .bold, color: pointsColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_TableTapCell(
|
||||||
|
match: match,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||||
|
child: Text(NumberFormat('#.#').format(match.gp), textAlign: .center),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TableTapCell extends StatelessWidget {
|
||||||
|
const _TableTapCell({required this.match, required this.child});
|
||||||
|
|
||||||
|
final BroadcastTeamStandingMatch match;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TableRowInkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).push(BroadcastRoundScreenLoading.buildRoute(match.roundId, initialTab: .teams));
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import 'package:lichess_mobile/src/theme.dart';
|
|||||||
import 'package:lichess_mobile/src/utils/screen.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_game_screen.dart';
|
||||||
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_player_widget.dart';
|
||||||
|
import 'package:lichess_mobile/src/view/broadcast/broadcast_team_screen.dart';
|
||||||
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
@@ -33,11 +34,13 @@ class BroadcastTeamsTab extends ConsumerWidget {
|
|||||||
required this.roundId,
|
required this.roundId,
|
||||||
required this.tournamentId,
|
required this.tournamentId,
|
||||||
required this.tournamentSlug,
|
required this.tournamentSlug,
|
||||||
|
this.showTeamScores = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BroadcastRoundId roundId;
|
final BroadcastRoundId roundId;
|
||||||
final BroadcastTournamentId tournamentId;
|
final BroadcastTournamentId tournamentId;
|
||||||
final String tournamentSlug;
|
final String tournamentSlug;
|
||||||
|
final bool showTeamScores;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -49,6 +52,7 @@ class BroadcastTeamsTab extends ConsumerWidget {
|
|||||||
roundId,
|
roundId,
|
||||||
tournamentId,
|
tournamentId,
|
||||||
tournamentSlug,
|
tournamentSlug,
|
||||||
|
showTeamScores,
|
||||||
),
|
),
|
||||||
AsyncError(:final error) => Center(child: Text('Cannot load teams data: $error')),
|
AsyncError(:final error) => Center(child: Text('Cannot load teams data: $error')),
|
||||||
_ => const Center(child: CircularProgressIndicator.adaptive()),
|
_ => const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
@@ -57,12 +61,19 @@ class BroadcastTeamsTab extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BroadcastTeamsList extends ConsumerWidget {
|
class BroadcastTeamsList extends ConsumerWidget {
|
||||||
const BroadcastTeamsList(this.teamMatches, this.roundId, this.tournamentId, this.tournamentSlug);
|
const BroadcastTeamsList(
|
||||||
|
this.teamMatches,
|
||||||
|
this.roundId,
|
||||||
|
this.tournamentId,
|
||||||
|
this.tournamentSlug,
|
||||||
|
this.showTeamScores,
|
||||||
|
);
|
||||||
|
|
||||||
final IList<BroadcastTeamMatch> teamMatches;
|
final IList<BroadcastTeamMatch> teamMatches;
|
||||||
final BroadcastRoundId roundId;
|
final BroadcastRoundId roundId;
|
||||||
final BroadcastTournamentId tournamentId;
|
final BroadcastTournamentId tournamentId;
|
||||||
final String tournamentSlug;
|
final String tournamentSlug;
|
||||||
|
final bool showTeamScores;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -86,6 +97,7 @@ class BroadcastTeamsList extends ConsumerWidget {
|
|||||||
title: value.round.name,
|
title: value.round.name,
|
||||||
showEvaluationGauge: showEvaluationGauges,
|
showEvaluationGauge: showEvaluationGauges,
|
||||||
customScoring: value.round.customScoring,
|
customScoring: value.round.customScoring,
|
||||||
|
showTeamScores: showTeamScores,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -106,6 +118,7 @@ class _TeamMatchCard extends StatelessWidget {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.showEvaluationGauge,
|
required this.showEvaluationGauge,
|
||||||
required this.customScoring,
|
required this.customScoring,
|
||||||
|
required this.showTeamScores,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BroadcastTeamMatch match;
|
final BroadcastTeamMatch match;
|
||||||
@@ -117,6 +130,7 @@ class _TeamMatchCard extends StatelessWidget {
|
|||||||
final String title;
|
final String title;
|
||||||
final bool showEvaluationGauge;
|
final bool showEvaluationGauge;
|
||||||
final BroadcastCustomScoring? customScoring;
|
final BroadcastCustomScoring? customScoring;
|
||||||
|
final bool showTeamScores;
|
||||||
|
|
||||||
bool get matchFinished => games.everyEntry((e) => e.value.isOver);
|
bool get matchFinished => games.everyEntry((e) => e.value.isOver);
|
||||||
BroadcastResult? get matchStatus => matchFinished
|
BroadcastResult? get matchStatus => matchFinished
|
||||||
@@ -142,11 +156,20 @@ class _TeamMatchCard extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: GestureDetector(
|
||||||
match.team1.name,
|
onTap: () {
|
||||||
maxLines: _kTeamNameMaxLines,
|
if (showTeamScores) {
|
||||||
textAlign: TextAlign.center,
|
Navigator.of(context).push(
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
BroadcastTeamScreen.buildRoute(context, tournamentId, match.team1.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
match.team1.name,
|
||||||
|
maxLines: _kTeamNameMaxLines,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
@@ -174,11 +197,20 @@ class _TeamMatchCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: GestureDetector(
|
||||||
match.team2.name,
|
onTap: () {
|
||||||
maxLines: _kTeamNameMaxLines,
|
if (showTeamScores) {
|
||||||
textAlign: TextAlign.center,
|
Navigator.of(context).push(
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
BroadcastTeamScreen.buildRoute(context, tournamentId, match.team2.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
match.team2.name,
|
||||||
|
maxLines: _kTeamNameMaxLines,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user