Updated score and winner handling
This commit is contained in:
@@ -2,11 +2,12 @@ import 'package:drift/drift.dart';
|
|||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/game_table.dart';
|
import 'package:tallee/data/db/tables/game_table.dart';
|
||||||
|
import 'package:tallee/data/db/tables/match_table.dart';
|
||||||
import 'package:tallee/data/models/game.dart';
|
import 'package:tallee/data/models/game.dart';
|
||||||
|
|
||||||
part 'game_dao.g.dart';
|
part 'game_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [GameTable])
|
@DriftAccessor(tables: [MatchTable, GameTable])
|
||||||
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||||
GameDao(super.db);
|
GameDao(super.db);
|
||||||
|
|
||||||
@@ -44,6 +45,25 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Game> getGameByMatchId({required String matchId}) async {
|
||||||
|
final query = select(gameTable).join([
|
||||||
|
innerJoin(matchTable, matchTable.gameId.equalsExp(gameTable.id)),
|
||||||
|
])..where(matchTable.id.equals(matchId));
|
||||||
|
|
||||||
|
final result = await query.getSingle();
|
||||||
|
final gameRow = result.readTable(gameTable);
|
||||||
|
|
||||||
|
return Game(
|
||||||
|
id: gameRow.id,
|
||||||
|
name: gameRow.name,
|
||||||
|
ruleset: Ruleset.values.firstWhere((e) => e.name == gameRow.ruleset),
|
||||||
|
description: gameRow.description,
|
||||||
|
color: GameColor.values.firstWhere((e) => e.name == gameRow.color),
|
||||||
|
icon: gameRow.icon,
|
||||||
|
createdAt: gameRow.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a new [game] to the database.
|
/// Adds a new [game] to the database.
|
||||||
/// If a game with the same ID already exists, no action is taken.
|
/// If a game with the same ID already exists, no action is taken.
|
||||||
/// Returns `true` if the game was added, `false` otherwise.
|
/// Returns `true` if the game was added, `false` otherwise.
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ part of 'game_dao.dart';
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
GameDaoManager get managers => GameDaoManager(this);
|
GameDaoManager get managers => GameDaoManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,4 +15,8 @@ class GameDaoManager {
|
|||||||
GameDaoManager(this._db);
|
GameDaoManager(this._db);
|
||||||
$$GameTableTableTableManager get gameTable =>
|
$$GameTableTableTableManager get gameTable =>
|
||||||
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
$$GameTableTableTableManager(_db.attachedDatabase, _db.gameTable);
|
||||||
|
$$GroupTableTableTableManager get groupTable =>
|
||||||
|
$$GroupTableTableTableManager(_db.attachedDatabase, _db.groupTable);
|
||||||
|
$$MatchTableTableTableManager get matchTable =>
|
||||||
|
$$MatchTableTableTableManager(_db.attachedDatabase, _db.matchTable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
matchId: row.id,
|
matchId: row.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
|
|
||||||
return Match(
|
return Match(
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -45,7 +44,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
endedAt: row.endedAt,
|
endedAt: row.endedAt,
|
||||||
scores: scores,
|
scores: scores,
|
||||||
winner: winner,
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -68,8 +66,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
|
|
||||||
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
|
final scores = await db.scoreEntryDao.getAllMatchScores(matchId: matchId);
|
||||||
|
|
||||||
final winner = await db.scoreEntryDao.getWinner(matchId: matchId);
|
|
||||||
|
|
||||||
return Match(
|
return Match(
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: result.name,
|
name: result.name,
|
||||||
@@ -80,7 +76,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
endedAt: result.endedAt,
|
endedAt: result.endedAt,
|
||||||
scores: scores,
|
scores: scores,
|
||||||
winner: winner,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,19 +105,14 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final pid in match.scores.keys) {
|
for (final pid in match.scores.keys) {
|
||||||
final playerScores = match.scores[pid]!;
|
final playerScores = match.scores[pid];
|
||||||
await db.scoreEntryDao.addScoresAsList(
|
if (playerScores != null) {
|
||||||
entrys: playerScores,
|
await db.scoreEntryDao.addScore(
|
||||||
playerId: pid,
|
entry: playerScores,
|
||||||
matchId: match.id,
|
playerId: pid,
|
||||||
);
|
matchId: match.id,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
if (match.winner != null) {
|
|
||||||
await db.scoreEntryDao.setWinner(
|
|
||||||
matchId: match.id,
|
|
||||||
playerId: match.winner!.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -300,7 +290,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
final group = await db.groupDao.getGroupById(groupId: groupId);
|
final group = await db.groupDao.getGroupById(groupId: groupId);
|
||||||
final players =
|
final players =
|
||||||
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
|
await db.playerMatchDao.getPlayersOfMatch(matchId: row.id) ?? [];
|
||||||
final winner = await db.scoreEntryDao.getWinner(matchId: row.id);
|
|
||||||
return Match(
|
return Match(
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
@@ -310,7 +299,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
notes: row.notes ?? '',
|
notes: row.notes ?? '',
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
endedAt: row.endedAt,
|
endedAt: row.endedAt,
|
||||||
winner: winner,
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/score_entry_table.dart';
|
import 'package:tallee/data/db/tables/score_entry_table.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
@@ -83,21 +84,21 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves all scores for a specific match.
|
/// Retrieves all scores for a specific match.
|
||||||
Future<Map<String, List<ScoreEntry>>> getAllMatchScores({
|
Future<Map<String, ScoreEntry?>> getAllMatchScores({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
}) async {
|
}) async {
|
||||||
final query = select(scoreEntryTable)
|
final query = select(scoreEntryTable)
|
||||||
..where((s) => s.matchId.equals(matchId));
|
..where((s) => s.matchId.equals(matchId));
|
||||||
final result = await query.get();
|
final result = await query.get();
|
||||||
|
|
||||||
final Map<String, List<ScoreEntry>> scoresByPlayer = {};
|
final Map<String, ScoreEntry?> scoresByPlayer = {};
|
||||||
for (final row in result) {
|
for (final row in result) {
|
||||||
final score = ScoreEntry(
|
final score = ScoreEntry(
|
||||||
roundNumber: row.roundNumber,
|
roundNumber: row.roundNumber,
|
||||||
score: row.score,
|
score: row.score,
|
||||||
change: row.change,
|
change: row.change,
|
||||||
);
|
);
|
||||||
scoresByPlayer.putIfAbsent(row.playerId, () => []).add(score);
|
scoresByPlayer[row.playerId] = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
return scoresByPlayer;
|
return scoresByPlayer;
|
||||||
@@ -237,10 +238,25 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
|
|
||||||
// Retrieves the winner of a match based on the highest score.
|
// Retrieves the winner of a match based on the highest score.
|
||||||
Future<Player?> getWinner({required String matchId}) async {
|
Future<Player?> getWinner({required String matchId}) async {
|
||||||
|
// Check the ruleset of the match
|
||||||
|
final ruleset = await db.gameDao
|
||||||
|
.getGameByMatchId(matchId: matchId)
|
||||||
|
.then((game) => game.ruleset);
|
||||||
|
|
||||||
final query = select(scoreEntryTable)
|
final query = select(scoreEntryTable)
|
||||||
..where((s) => s.matchId.equals(matchId))
|
..where((s) => s.matchId.equals(matchId));
|
||||||
..orderBy([(s) => OrderingTerm.desc(s.score)])
|
|
||||||
..limit(1);
|
// If the ruleset is lowestScore, the winner is the player with the lowest
|
||||||
|
// score so we order by ascending score.
|
||||||
|
if (ruleset == Ruleset.lowestScore) {
|
||||||
|
query
|
||||||
|
..orderBy([(s) => OrderingTerm.asc(s.score)])
|
||||||
|
..limit(1);
|
||||||
|
} else {
|
||||||
|
query
|
||||||
|
..orderBy([(s) => OrderingTerm.desc(s.score)])
|
||||||
|
..limit(1);
|
||||||
|
}
|
||||||
final result = await query.getSingleOrNull();
|
final result = await query.getSingleOrNull();
|
||||||
|
|
||||||
if (result == null) return null;
|
if (result == null) return null;
|
||||||
|
|||||||
@@ -15,27 +15,25 @@ class Match {
|
|||||||
final Group? group;
|
final Group? group;
|
||||||
final List<Player> players;
|
final List<Player> players;
|
||||||
final String notes;
|
final String notes;
|
||||||
Map<String, List<ScoreEntry>> scores;
|
Map<String, ScoreEntry?> scores;
|
||||||
Player? winner;
|
|
||||||
|
|
||||||
Match({
|
Match({
|
||||||
String? id,
|
|
||||||
DateTime? createdAt,
|
|
||||||
this.endedAt,
|
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.game,
|
required this.game,
|
||||||
|
required this.players,
|
||||||
|
this.endedAt,
|
||||||
this.group,
|
this.group,
|
||||||
this.players = const [],
|
|
||||||
this.notes = '',
|
this.notes = '',
|
||||||
Map<String, List<ScoreEntry>>? scores,
|
String? id,
|
||||||
this.winner,
|
DateTime? createdAt,
|
||||||
|
Map<String, ScoreEntry?>? scores,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
createdAt = createdAt ?? clock.now(),
|
createdAt = createdAt ?? clock.now(),
|
||||||
scores = scores ?? {for (var player in players) player.id: []};
|
scores = scores ?? {for (Player p in players) p.id: null};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, winner: $winner}';
|
return 'Match{id: $id, createdAt: $createdAt, endedAt: $endedAt, name: $name, game: $game, group: $group, players: $players, notes: $notes, scores: $scores, mvp: $mvp}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Match instance from a JSON object where related objects are
|
/// Creates a Match instance from a JSON object where related objects are
|
||||||
@@ -71,10 +69,60 @@ class Match {
|
|||||||
'gameId': game.id,
|
'gameId': game.id,
|
||||||
'groupId': group?.id,
|
'groupId': group?.id,
|
||||||
'playerIds': players.map((player) => player.id).toList(),
|
'playerIds': players.map((player) => player.id).toList(),
|
||||||
'scores': scores.map(
|
'scores': scores,
|
||||||
(playerId, scoreList) =>
|
|
||||||
MapEntry(playerId, scoreList.map((score) => score.toJson()).toList()),
|
|
||||||
),
|
|
||||||
'notes': notes,
|
'notes': notes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
List<Player> get mvp {
|
||||||
|
if (players.isEmpty || scores.isEmpty) return [];
|
||||||
|
|
||||||
|
switch (game.ruleset) {
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
return _getPlayersWithHighestScore();
|
||||||
|
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
return _getPlayersWithLowestScore();
|
||||||
|
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return [_getPlayersWithHighestScore().first];
|
||||||
|
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return [_getPlayersWithLowestScore().first];
|
||||||
|
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> _getPlayersWithHighestScore() {
|
||||||
|
if (players.isEmpty || scores.isEmpty) return [];
|
||||||
|
|
||||||
|
final int highestScore = players
|
||||||
|
.map((player) => scores[player.id]?.score)
|
||||||
|
.whereType<int>()
|
||||||
|
.reduce((max, score) => score > max ? score : max);
|
||||||
|
|
||||||
|
return players.where((player) {
|
||||||
|
final playerScores = scores[player.id];
|
||||||
|
if (playerScores == null) return false;
|
||||||
|
return playerScores.score == highestScore;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> _getPlayersWithLowestScore() {
|
||||||
|
if (players.isEmpty || scores.values.every((score) => score == null)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final int lowestScore = players
|
||||||
|
.map((player) => scores[player.id]?.score)
|
||||||
|
.whereType<int>()
|
||||||
|
.reduce((min, score) => score < min ? score : min);
|
||||||
|
|
||||||
|
return players.where((player) {
|
||||||
|
final playerScore = scores[player.id];
|
||||||
|
if (playerScore == null) return false;
|
||||||
|
return playerScore.score == lowestScore;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class GroupDetailView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GroupDetailViewState extends State<GroupDetailView> {
|
class _GroupDetailViewState extends State<GroupDetailView> {
|
||||||
|
late final AppLocalizations loc;
|
||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
late Group _group;
|
late Group _group;
|
||||||
@@ -51,13 +52,12 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_group = widget.group;
|
_group = widget.group;
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
loc = AppLocalizations.of(context);
|
||||||
_loadStatistics();
|
_loadStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: CustomTheme.backgroundColor,
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -259,28 +259,36 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
|
|
||||||
/// Determines the best player in the group based on match wins
|
/// Determines the best player in the group based on match wins
|
||||||
String _getBestPlayer(List<Match> matches) {
|
String _getBestPlayer(List<Match> matches) {
|
||||||
final bestPlayerCounts = <Player, int>{};
|
final mvpCounts = <Player, int>{};
|
||||||
|
|
||||||
// Count wins for each player
|
|
||||||
for (var match in matches) {
|
for (var match in matches) {
|
||||||
if (match.winner != null &&
|
final mvps = match.mvp;
|
||||||
_group.members.any((m) => m.id == match.winner?.id)) {
|
for (final mvpPlayer in mvps) {
|
||||||
print(match.winner);
|
if (_group.members.any((m) => m.id == mvpPlayer.id)) {
|
||||||
bestPlayerCounts.update(
|
mvpCounts.update(mvpPlayer, (value) => value + 1, ifAbsent: () => 1);
|
||||||
match.winner!,
|
}
|
||||||
(value) => value + 1,
|
|
||||||
ifAbsent: () => 1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort players by win count
|
final sortedMvps = mvpCounts.entries.toList()
|
||||||
final sortedPlayers = bestPlayerCounts.entries.toList()
|
|
||||||
..sort((a, b) => b.value.compareTo(a.value));
|
..sort((a, b) => b.value.compareTo(a.value));
|
||||||
|
|
||||||
// Get the best player
|
if (sortedMvps.isEmpty) {
|
||||||
bestPlayer = sortedPlayers.isNotEmpty ? sortedPlayers.first.key.name : '-';
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
return bestPlayer;
|
// Check if there are multiple players with the same value
|
||||||
|
final highestMvpCount = sortedMvps.first.value;
|
||||||
|
final topPlayers = sortedMvps
|
||||||
|
.where((entry) => entry.value == highestMvpCount)
|
||||||
|
.toList();
|
||||||
|
switch (topPlayers.length) {
|
||||||
|
case 0:
|
||||||
|
return '-';
|
||||||
|
case 1:
|
||||||
|
return topPlayers.first.key.name;
|
||||||
|
default:
|
||||||
|
return loc.tie;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,21 +43,25 @@ class _HomeViewState extends State<HomeView> {
|
|||||||
Match(
|
Match(
|
||||||
name: 'Skeleton Match',
|
name: 'Skeleton Match',
|
||||||
game: Game(
|
game: Game(
|
||||||
name: '',
|
name: 'Skeleton Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: '',
|
description: 'This is a skeleton game description.',
|
||||||
color: GameColor.blue,
|
color: GameColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
),
|
),
|
||||||
group: Group(
|
group: Group(
|
||||||
name: 'Skeleton Group',
|
name: 'Skeleton Group',
|
||||||
description: '',
|
description: 'This is a skeleton group description.',
|
||||||
members: [
|
members: [
|
||||||
Player(name: 'Skeleton Player 1', description: ''),
|
Player(name: 'Skeleton Player 1', description: ''),
|
||||||
Player(name: 'Skeleton Player 2', description: ''),
|
Player(name: 'Skeleton Player 2', description: ''),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
notes: '',
|
notes: 'These are skeleton notes.',
|
||||||
|
players: [
|
||||||
|
Player(name: 'Skeleton Player 1', description: ''),
|
||||||
|
Player(name: 'Skeleton Player 2', description: ''),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -231,7 +235,8 @@ class _HomeViewState extends State<HomeView> {
|
|||||||
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
|
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
|
||||||
if (matchIndex != -1) {
|
if (matchIndex != -1) {
|
||||||
setState(() {
|
setState(() {
|
||||||
recentMatches[matchIndex].winner = winner;
|
// TODO: fix
|
||||||
|
//recentMatches[matchIndex].winner = winner;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
group: selectedGroup,
|
group: selectedGroup,
|
||||||
players: selectedPlayers,
|
players: selectedPlayers,
|
||||||
game: tempGame,
|
game: tempGame,
|
||||||
winner: widget.matchToEdit!.winner,
|
|
||||||
createdAt: widget.matchToEdit!.createdAt,
|
createdAt: widget.matchToEdit!.createdAt,
|
||||||
endedAt: widget.matchToEdit!.endedAt,
|
endedAt: widget.matchToEdit!.endedAt,
|
||||||
notes: widget.matchToEdit!.notes,
|
notes: widget.matchToEdit!.notes,
|
||||||
@@ -314,9 +313,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
matchId: widget.matchToEdit!.id,
|
matchId: widget.matchToEdit!.id,
|
||||||
playerId: player.id,
|
playerId: player.id,
|
||||||
);
|
);
|
||||||
if (widget.matchToEdit!.winner?.id == player.id) {
|
|
||||||
updatedMatch.winner = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
text: loc.enter_results,
|
text: loc.enter_results,
|
||||||
icon: Icons.emoji_events,
|
icon: Icons.emoji_events,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
match.winner = await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
@@ -237,54 +237,29 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the widget to be displayed in the result [InfoTile]
|
/// Returns the widget to be displayed in the result [InfoTile]
|
||||||
/// TODO: Update when score logic is overhauled
|
|
||||||
Widget getResultWidget(AppLocalizations loc) {
|
Widget getResultWidget(AppLocalizations loc) {
|
||||||
if (isSingleRowResult()) {
|
if (isSingleRowResult()) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: getResultRow(loc),
|
children: getSingleResultRow(loc),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Column(
|
return getScoreResultWidget(loc);
|
||||||
children: [
|
|
||||||
for (var player in match.players)
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
player.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'0 ${loc.points}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: CustomTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the result row for single winner/loser rulesets or a placeholder
|
/// Returns the result row for single winner/loser rulesets or a placeholder
|
||||||
/// if no result is entered yet
|
/// if no result is entered yet
|
||||||
/// TODO: Update when score logic is overhauled
|
List<Widget> getSingleResultRow(AppLocalizations loc) {
|
||||||
List<Widget> getResultRow(AppLocalizations loc) {
|
// Single Winner
|
||||||
if (match.winner != null && match.game.ruleset == Ruleset.singleWinner) {
|
if (match.mvp.isNotEmpty && match.game.ruleset == Ruleset.singleWinner) {
|
||||||
return [
|
return [
|
||||||
Text(
|
Text(
|
||||||
loc.winner,
|
loc.winner,
|
||||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
match.winner!.name,
|
match.mvp.first.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -292,6 +267,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
// Single Loser
|
||||||
} else if (match.game.ruleset == Ruleset.singleLoser) {
|
} else if (match.game.ruleset == Ruleset.singleLoser) {
|
||||||
return [
|
return [
|
||||||
Text(
|
Text(
|
||||||
@@ -299,7 +275,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
match.winner!.name,
|
match.mvp.first.name,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -307,6 +283,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
// No result entered yet
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
Text(
|
Text(
|
||||||
@@ -317,6 +294,46 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the result widget for scores
|
||||||
|
Widget getScoreResultWidget(AppLocalizations loc) {
|
||||||
|
List<(String, int)> playerScores = [];
|
||||||
|
for (var player in match.players) {
|
||||||
|
int score = match.scores[player.id]?.score ?? 0;
|
||||||
|
playerScores.add((player.name, score));
|
||||||
|
}
|
||||||
|
if (widget.match.game.ruleset == Ruleset.highestScore) {
|
||||||
|
playerScores.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
|
} else if (widget.match.game.ruleset == Ruleset.lowestScore) {
|
||||||
|
playerScores.sort((a, b) => a.$2.compareTo(b.$2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
for (var score in playerScores)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
score.$1,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
getPointLabel(loc, score.$2),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Returns if the result can be displayed in a single row
|
// Returns if the result can be displayed in a single row
|
||||||
bool isSingleRowResult() {
|
bool isSingleRowResult() {
|
||||||
return match.game.ruleset == Ruleset.singleWinner ||
|
return match.game.ruleset == Ruleset.singleWinner ||
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:tallee/core/enums.dart';
|
|||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/models/match.dart';
|
import 'package:tallee/data/models/match.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
||||||
@@ -14,20 +15,11 @@ class MatchResultView extends StatefulWidget {
|
|||||||
/// A view that allows selecting and saving the winner of a match
|
/// A view that allows selecting and saving the winner of a match
|
||||||
/// [match]: The match for which the winner is to be selected
|
/// [match]: The match for which the winner is to be selected
|
||||||
/// [onWinnerChanged]: Optional callback invoked when the winner is changed
|
/// [onWinnerChanged]: Optional callback invoked when the winner is changed
|
||||||
const MatchResultView({
|
const MatchResultView({super.key, required this.match, this.onWinnerChanged});
|
||||||
super.key,
|
|
||||||
required this.match,
|
|
||||||
this.ruleset = Ruleset.singleWinner,
|
|
||||||
this.onWinnerChanged,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The match for which the winner is to be selected
|
/// The match for which the winner is to be selected
|
||||||
final Match match;
|
final Match match;
|
||||||
|
|
||||||
/// The ruleset of the match, determines how the winner is selected or how
|
|
||||||
/// scores are entered
|
|
||||||
final Ruleset ruleset;
|
|
||||||
|
|
||||||
/// Optional callback invoked when the winner is changed
|
/// Optional callback invoked when the winner is changed
|
||||||
final VoidCallback? onWinnerChanged;
|
final VoidCallback? onWinnerChanged;
|
||||||
|
|
||||||
@@ -38,6 +30,8 @@ class MatchResultView extends StatefulWidget {
|
|||||||
class _MatchResultViewState extends State<MatchResultView> {
|
class _MatchResultViewState extends State<MatchResultView> {
|
||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
|
|
||||||
|
late final Ruleset ruleset;
|
||||||
|
|
||||||
/// List of all players who participated in the match
|
/// List of all players who participated in the match
|
||||||
late final List<Player> allPlayers;
|
late final List<Player> allPlayers;
|
||||||
|
|
||||||
@@ -51,6 +45,8 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
|
||||||
|
ruleset = widget.match.game.ruleset;
|
||||||
|
|
||||||
allPlayers = widget.match.players;
|
allPlayers = widget.match.players;
|
||||||
allPlayers.sort((a, b) => a.name.compareTo(b.name));
|
allPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
|
||||||
@@ -59,13 +55,17 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
(index) => TextEditingController(),
|
(index) => TextEditingController(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.match.winner != null) {
|
if (widget.match.mvp.isNotEmpty) {
|
||||||
if (rulesetSupportsWinnerSelection()) {
|
if (rulesetSupportsWinnerSelection()) {
|
||||||
_selectedPlayer = allPlayers.firstWhere(
|
_selectedPlayer = allPlayers.firstWhere(
|
||||||
(p) => p.id == widget.match.winner!.id,
|
(p) => p.id == widget.match.mvp.first.id,
|
||||||
);
|
);
|
||||||
} else if (rulesetSupportsScoreEntry()) {
|
} else if (rulesetSupportsScoreEntry()) {
|
||||||
/// TODO: Update when score logic is overhauled
|
for (int i = 0; i < allPlayers.length; i++) {
|
||||||
|
final scoreList = widget.match.scores[allPlayers[i].id];
|
||||||
|
final score = scoreList?.score ?? 0;
|
||||||
|
controller[i].text = score.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -154,7 +154,6 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: allPlayers.length,
|
itemCount: allPlayers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
print(allPlayers[index].name);
|
|
||||||
return ScoreListTile(
|
return ScoreListTile(
|
||||||
text: allPlayers[index].name,
|
text: allPlayers[index].name,
|
||||||
controller: controller[index],
|
controller: controller[index],
|
||||||
@@ -176,6 +175,11 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
text: loc.save_changes,
|
text: loc.save_changes,
|
||||||
sizeRelativeToWidth: 0.95,
|
sizeRelativeToWidth: 0.95,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
final ending = DateTime.now();
|
||||||
|
await db.matchDao.updateMatchEndedAt(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
endedAt: ending,
|
||||||
|
);
|
||||||
await _handleSaving();
|
await _handleSaving();
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
Navigator.of(context).pop(_selectedPlayer);
|
Navigator.of(context).pop(_selectedPlayer);
|
||||||
@@ -190,12 +194,12 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
/// Handles saving or removing the winner in the database
|
/// Handles saving or removing the winner in the database
|
||||||
/// based on the current selection.
|
/// based on the current selection.
|
||||||
Future<void> _handleSaving() async {
|
Future<void> _handleSaving() async {
|
||||||
if (widget.ruleset == Ruleset.singleWinner) {
|
if (ruleset == Ruleset.singleWinner) {
|
||||||
await _handleWinner();
|
await _handleWinner();
|
||||||
} else if (widget.ruleset == Ruleset.singleLoser) {
|
} else if (ruleset == Ruleset.singleLoser) {
|
||||||
await _handleLoser();
|
await _handleLoser();
|
||||||
} else if (widget.ruleset == Ruleset.lowestScore ||
|
} else if (ruleset == Ruleset.lowestScore ||
|
||||||
widget.ruleset == Ruleset.highestScore) {
|
ruleset == Ruleset.highestScore) {
|
||||||
await _handleScores();
|
await _handleScores();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +208,9 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
|
|
||||||
Future<bool> _handleWinner() async {
|
Future<bool> _handleWinner() async {
|
||||||
if (_selectedPlayer == null) {
|
if (_selectedPlayer == null) {
|
||||||
await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
||||||
} else {
|
} else {
|
||||||
await db.scoreEntryDao.setWinner(
|
return await db.scoreEntryDao.setWinner(
|
||||||
matchId: widget.match.id,
|
matchId: widget.match.id,
|
||||||
playerId: _selectedPlayer!.id,
|
playerId: _selectedPlayer!.id,
|
||||||
);
|
);
|
||||||
@@ -215,33 +219,33 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
|
|
||||||
Future<bool> _handleLoser() async {
|
Future<bool> _handleLoser() async {
|
||||||
if (_selectedPlayer == null) {
|
if (_selectedPlayer == null) {
|
||||||
/// TODO: Update when score logic is overhauled
|
return await db.scoreEntryDao.removeLooser(matchId: widget.match.id);
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
/// TODO: Update when score logic is overhauled
|
return await db.scoreEntryDao.setLooser(
|
||||||
return false;
|
matchId: widget.match.id,
|
||||||
|
playerId: _selectedPlayer!.id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles saving the scores for each player in the database.
|
/// Handles saving the scores for each player in the database.
|
||||||
Future<bool> _handleScores() async {
|
Future<void> _handleScores() async {
|
||||||
for (int i = 0; i < allPlayers.length; i++) {
|
for (int i = 0; i < allPlayers.length; i++) {
|
||||||
var text = controller[i].text;
|
var text = controller[i].text;
|
||||||
if (text.isEmpty) {
|
if (text.isEmpty) {
|
||||||
text = '0';
|
text = '0';
|
||||||
}
|
}
|
||||||
final score = int.parse(text);
|
final score = int.parse(text);
|
||||||
await db.playerMatchDao.updatePlayerScore(
|
await db.scoreEntryDao.addScore(
|
||||||
matchId: widget.match.id,
|
matchId: widget.match.id,
|
||||||
playerId: allPlayers[i].id,
|
playerId: allPlayers[i].id,
|
||||||
newScore: score,
|
entry: ScoreEntry(roundNumber: 0, score: score, change: 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTitleForRuleset(AppLocalizations loc) {
|
String getTitleForRuleset(AppLocalizations loc) {
|
||||||
switch (widget.ruleset) {
|
switch (ruleset) {
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
return loc.select_winner;
|
return loc.select_winner;
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
@@ -252,12 +256,10 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool rulesetSupportsWinnerSelection() {
|
bool rulesetSupportsWinnerSelection() {
|
||||||
return widget.ruleset == Ruleset.singleWinner ||
|
return ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser;
|
||||||
widget.ruleset == Ruleset.singleLoser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rulesetSupportsScoreEntry() {
|
bool rulesetSupportsScoreEntry() {
|
||||||
return widget.ruleset == Ruleset.lowestScore ||
|
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
|
||||||
widget.ruleset == Ruleset.highestScore;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,20 +37,16 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
Match(
|
Match(
|
||||||
name: 'Skeleton match name',
|
name: 'Skeleton match name',
|
||||||
game: Game(
|
game: Game(
|
||||||
name: '',
|
name: 'Game name',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
description: '',
|
|
||||||
color: GameColor.blue,
|
color: GameColor.blue,
|
||||||
icon: '',
|
icon: '',
|
||||||
),
|
),
|
||||||
group: Group(
|
group: Group(
|
||||||
name: 'Group name',
|
name: 'Group name',
|
||||||
description: '',
|
members: List.filled(5, Player(name: 'Player')),
|
||||||
members: List.filled(5, Player(name: 'Player', description: '')),
|
|
||||||
),
|
),
|
||||||
winner: Player(name: 'Player', description: ''),
|
players: [Player(name: 'Player')],
|
||||||
players: [Player(name: 'Player', description: '')],
|
|
||||||
notes: '',
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,7 +112,7 @@ class _MatchViewState extends State<MatchView> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
bottom: MediaQuery.paddingOf(context).bottom + 20,
|
bottom: MediaQuery.paddingOf(context).bottom + 20,
|
||||||
child: MainMenuButton(
|
child: MainMenuButton(
|
||||||
text: 'Spiel erstellen',
|
text: loc.create_match,
|
||||||
icon: RpgAwesome.clovers_card,
|
icon: RpgAwesome.clovers_card,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
|
|||||||
@@ -140,8 +140,8 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
|
|
||||||
// Getting the winners
|
// Getting the winners
|
||||||
for (var match in matches) {
|
for (var match in matches) {
|
||||||
final winner = match.winner;
|
final mvps = match.mvp;
|
||||||
if (winner != null) {
|
for (var winner in mvps) {
|
||||||
final index = winCounts.indexWhere((entry) => entry.$1 == winner.id);
|
final index = winCounts.indexWhere((entry) => entry.$1 == winner.id);
|
||||||
// -1 means winner not found in winCounts
|
// -1 means winner not found in winCounts
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:tallee/core/common.dart';
|
import 'package:tallee/core/common.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/data/models/match.dart';
|
import 'package:tallee/data/models/match.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
@@ -44,7 +45,6 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final match = widget.match;
|
final match = widget.match;
|
||||||
final group = match.group;
|
final group = match.group;
|
||||||
final winner = match.winner;
|
|
||||||
final players = [...match.players]
|
final players = [...match.players]
|
||||||
..sort((a, b) => a.name.compareTo(b.name));
|
..sort((a, b) => a.name.compareTo(b.name));
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
@@ -131,7 +131,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
|
|
||||||
if (winner != null) ...[
|
if (match.mvp.isNotEmpty) ...[
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
@@ -155,7 +155,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${loc.winner}: ${winner.name}',
|
getWinner(loc),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -248,4 +248,21 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
return '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(dateTime)}';
|
return '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(dateTime)}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getWinner(AppLocalizations loc) {
|
||||||
|
if (widget.match.mvp.isEmpty) return '';
|
||||||
|
final ruleset = widget.match.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser) {
|
||||||
|
return '${loc.winner}: ${widget.match.mvp.first.name}';
|
||||||
|
} else if (ruleset == Ruleset.lowestScore ||
|
||||||
|
ruleset == Ruleset.lowestScore) {
|
||||||
|
final mvp = widget.match.mvp;
|
||||||
|
final mvpScore = widget.match.scores[mvp.first.id]?.score ?? 0;
|
||||||
|
final mvpNames = mvp.map((player) => player.name).join(', ');
|
||||||
|
|
||||||
|
return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})';
|
||||||
|
}
|
||||||
|
return '${loc.winner}: n.A.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,20 +71,7 @@ class DataTransferService {
|
|||||||
'gameId': m.game.id,
|
'gameId': m.game.id,
|
||||||
'groupId': m.group?.id,
|
'groupId': m.group?.id,
|
||||||
'playerIds': m.players.map((p) => p.id).toList(),
|
'playerIds': m.players.map((p) => p.id).toList(),
|
||||||
'scores': m.scores.map(
|
'scores': m.scores,
|
||||||
(playerId, scores) => MapEntry(
|
|
||||||
playerId,
|
|
||||||
scores
|
|
||||||
.map(
|
|
||||||
(s) => {
|
|
||||||
'roundNumber': s.roundNumber,
|
|
||||||
'score': s.score,
|
|
||||||
'change': s.change,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'notes': m.notes,
|
'notes': m.notes,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ void main() {
|
|||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup1,
|
group: testGroup1,
|
||||||
players: [testPlayer4, testPlayer5],
|
players: [testPlayer4, testPlayer5],
|
||||||
winner: testPlayer4,
|
|
||||||
notes: '',
|
notes: '',
|
||||||
);
|
);
|
||||||
testMatch2 = Match(
|
testMatch2 = Match(
|
||||||
@@ -71,20 +70,19 @@ void main() {
|
|||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup2,
|
group: testGroup2,
|
||||||
players: [testPlayer1, testPlayer2, testPlayer3],
|
players: [testPlayer1, testPlayer2, testPlayer3],
|
||||||
winner: testPlayer2,
|
|
||||||
notes: '',
|
notes: '',
|
||||||
);
|
);
|
||||||
testMatchOnlyPlayers = Match(
|
testMatchOnlyPlayers = Match(
|
||||||
name: 'Test Match with Players',
|
name: 'Test Match with Players',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: [testPlayer1, testPlayer2, testPlayer3],
|
players: [testPlayer1, testPlayer2, testPlayer3],
|
||||||
winner: testPlayer3,
|
|
||||||
notes: '',
|
notes: '',
|
||||||
);
|
);
|
||||||
testMatchOnlyGroup = Match(
|
testMatchOnlyGroup = Match(
|
||||||
name: 'Test Match with Group',
|
name: 'Test Match with Group',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup2,
|
group: testGroup2,
|
||||||
|
players: testGroup2.members,
|
||||||
notes: '',
|
notes: '',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -289,8 +287,8 @@ void main() {
|
|||||||
matchId: testMatch1.id,
|
matchId: testMatch1.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchedMatch.winner, isNotNull);
|
expect(fetchedMatch.mvp, isNotNull);
|
||||||
expect(fetchedMatch.winner!.id, testPlayer4.id);
|
expect(fetchedMatch.mvp.first.id, testPlayer4.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Setting a winner works correctly', () async {
|
test('Setting a winner works correctly', () async {
|
||||||
@@ -304,8 +302,8 @@ void main() {
|
|||||||
final fetchedMatch = await database.matchDao.getMatchById(
|
final fetchedMatch = await database.matchDao.getMatchById(
|
||||||
matchId: testMatch1.id,
|
matchId: testMatch1.id,
|
||||||
);
|
);
|
||||||
expect(fetchedMatch.winner, isNotNull);
|
expect(fetchedMatch.mvp, isNotNull);
|
||||||
expect(fetchedMatch.winner!.id, testPlayer5.id);
|
expect(fetchedMatch.mvp.first.id, testPlayer5.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
|
|||||||
@@ -343,8 +343,16 @@ void main() {
|
|||||||
// Verifies that teams with overlapping members are independent.
|
// Verifies that teams with overlapping members are independent.
|
||||||
test('Teams with overlapping members are independent', () async {
|
test('Teams with overlapping members are independent', () async {
|
||||||
// Create two matches since player_match has primary key {playerId, matchId}
|
// Create two matches since player_match has primary key {playerId, matchId}
|
||||||
final match1 = Match(name: 'Match 1', game: testGame1, notes: '');
|
final match1 = Match(
|
||||||
final match2 = Match(name: 'Match 2', game: testGame2, notes: '');
|
name: 'Match 1',
|
||||||
|
game: testGame1,
|
||||||
|
players: [testPlayer1, testPlayer2],
|
||||||
|
);
|
||||||
|
final match2 = Match(
|
||||||
|
name: 'Match 2',
|
||||||
|
game: testGame2,
|
||||||
|
players: [testPlayer1, testPlayer2],
|
||||||
|
);
|
||||||
await database.matchDao.addMatch(match: match1);
|
await database.matchDao.addMatch(match: match1);
|
||||||
await database.matchDao.addMatch(match: match2);
|
await database.matchDao.addMatch(match: match2);
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ void main() {
|
|||||||
testMatchOnlyGroup = Match(
|
testMatchOnlyGroup = Match(
|
||||||
name: 'Test Match with Group',
|
name: 'Test Match with Group',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
|
players: testGroup.members,
|
||||||
group: testGroup,
|
group: testGroup,
|
||||||
notes: '',
|
notes: '',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -231,8 +231,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(scores.length, 2);
|
expect(scores.length, 2);
|
||||||
expect(scores[testPlayer1.id]!.length, 2);
|
expect(scores[testPlayer1.id]!, isNotNull);
|
||||||
expect(scores[testPlayer2.id]!.length, 1);
|
expect(scores[testPlayer2.id]!, isNotNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getAllMatchScores() with no scores saved', () async {
|
test('getAllMatchScores() with no scores saved', () async {
|
||||||
|
|||||||
@@ -64,14 +64,8 @@ void main() {
|
|||||||
players: [testPlayer1, testPlayer2],
|
players: [testPlayer1, testPlayer2],
|
||||||
notes: 'Test notes',
|
notes: 'Test notes',
|
||||||
scores: {
|
scores: {
|
||||||
testPlayer1.id: [
|
testPlayer1.id: ScoreEntry(roundNumber: 1, score: 10, change: 10),
|
||||||
ScoreEntry(roundNumber: 1, score: 10, change: 10),
|
testPlayer2.id: ScoreEntry(roundNumber: 1, score: 15, change: 15),
|
||||||
ScoreEntry(roundNumber: 2, score: 20, change: 10),
|
|
||||||
],
|
|
||||||
testPlayer2.id: [
|
|
||||||
ScoreEntry(roundNumber: 1, score: 15, change: 15),
|
|
||||||
ScoreEntry(roundNumber: 2, score: 25, change: 10),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user