WIP: Implementierung von deleted Attribut #204
@@ -1,5 +1,9 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
avoid_print: false
|
avoid_print: false
|
||||||
@@ -11,8 +15,4 @@ linter:
|
|||||||
prefer_const_literals_to_create_immutables: true
|
prefer_const_literals_to_create_immutables: true
|
||||||
unnecessary_const: true
|
unnecessary_const: true
|
||||||
lines_longer_than_80_chars: false
|
lines_longer_than_80_chars: false
|
||||||
constant_identifier_names: false
|
constant_identifier_names: false
|
||||||
|
|
||||||
analyzer:
|
|
||||||
exclude:
|
|
||||||
- lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
|
||||||
@@ -147,13 +147,19 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"endedAt": {
|
"endedAt": {
|
||||||
"type": ["string", "null"]
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"gameId": {
|
"gameId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": ["string", "null"]
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"playerIds": {
|
"playerIds": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -163,22 +169,28 @@
|
|||||||
},
|
},
|
||||||
"scores": {
|
"scores": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"items": {
|
"additionalProperties": {
|
||||||
"type": "array",
|
"oneOf": [
|
||||||
"items": {
|
{
|
||||||
"type": "string",
|
"type": "null"
|
||||||
"properties": {
|
},
|
||||||
"roundNumber": {
|
{
|
||||||
"type": "number"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"roundNumber": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"score": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"change": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"score": {
|
"required": ["roundNumber", "score", "change"],
|
||||||
"type": "number"
|
"additionalProperties": false
|
||||||
},
|
|
||||||
"change": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.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/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
||||||
@@ -43,3 +44,18 @@ String getExtraPlayerCount(Match match) {
|
|||||||
}
|
}
|
||||||
return ' + ${count.toString()}';
|
return ' + ${count.toString()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getNameCountText(Player player) {
|
||||||
|
if (player.nameCount >= 1) {
|
||||||
|
return ' #${player.nameCount}';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPointLabel(AppLocalizations loc, int points) {
|
||||||
|
if (points == 1) {
|
||||||
|
return '$points ${loc.point}';
|
||||||
|
} else {
|
||||||
|
return '$points ${loc.points}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,21 +85,21 @@ class CustomTheme {
|
|||||||
);
|
);
|
||||||
|
|
||||||
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||||
textStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.textColor)),
|
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
||||||
hintStyle: WidgetStatePropertyAll(TextStyle(color: CustomTheme.hintColor)),
|
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
||||||
);
|
);
|
||||||
|
|
||||||
static final RadioThemeData radioTheme = RadioThemeData(
|
static final RadioThemeData radioTheme = RadioThemeData(
|
||||||
fillColor: WidgetStateProperty.resolveWith<Color>((states) {
|
fillColor: WidgetStateProperty.resolveWith<Color>((states) {
|
||||||
if (states.contains(WidgetState.selected)) {
|
if (states.contains(WidgetState.selected)) {
|
||||||
return CustomTheme.primaryColor;
|
return primaryColor;
|
||||||
}
|
}
|
||||||
return CustomTheme.textColor;
|
return textColor;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
static const InputDecorationTheme inputDecorationTheme = InputDecorationTheme(
|
static const InputDecorationTheme inputDecorationTheme = InputDecorationTheme(
|
||||||
labelStyle: TextStyle(color: CustomTheme.textColor),
|
labelStyle: TextStyle(color: textColor),
|
||||||
hintStyle: TextStyle(color: CustomTheme.hintColor),
|
hintStyle: TextStyle(color: hintColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,6 +130,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
uniqueGames[match.game.id] = match.game;
|
uniqueGames[match.game.id] = match.game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add games
|
||||||
if (uniqueGames.isNotEmpty) {
|
if (uniqueGames.isNotEmpty) {
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
@@ -162,7 +153,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all groups of the matches in batch
|
// Add groups
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
db.groupTable,
|
db.groupTable,
|
||||||
@@ -181,7 +172,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all matches in batch
|
// Add matches
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
matchTable,
|
matchTable,
|
||||||
@@ -202,7 +193,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all players of the matches in batch (unique)
|
// Add players
|
||||||
final uniquePlayers = <String, Player>{};
|
final uniquePlayers = <String, Player>{};
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
for (final p in match.players) {
|
for (final p in match.players) {
|
||||||
@@ -235,7 +226,27 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all player-match associations in batch
|
await db.batch((b) {
|
||||||
|
for (final match in matches) {
|
||||||
|
for (final entry in match.scores.entries) {
|
||||||
|
if (entry.value != null) {
|
||||||
|
b.insert(
|
||||||
|
db.scoreEntryTable,
|
||||||
|
ScoreEntryTableCompanion.insert(
|
||||||
|
matchId: match.id,
|
||||||
|
playerId: entry.key,
|
||||||
|
score: entry.value!.score,
|
||||||
|
roundNumber: entry.value!.roundNumber,
|
||||||
|
change: entry.value!.change,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add player-match associations
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
for (final p in match.players) {
|
for (final p in match.players) {
|
||||||
@@ -251,7 +262,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add all player-group associations in batch
|
// Add player-group associations
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
if (match.group != null) {
|
if (match.group != null) {
|
||||||
@@ -300,7 +311,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 +320,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,4 +1,5 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
import 'package:tallee/data/db/tables/player_table.dart';
|
||||||
import 'package:tallee/data/models/player.dart';
|
import 'package:tallee/data/models/player.dart';
|
||||||
@@ -20,6 +21,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
|
nameCount: row.nameCount,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -34,6 +36,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
name: result.name,
|
name: result.name,
|
||||||
description: result.description,
|
description: result.description,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
|
nameCount: result.nameCount,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +45,15 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
/// the new one.
|
/// the new one.
|
||||||
Future<bool> addPlayer({required Player player}) async {
|
Future<bool> addPlayer({required Player player}) async {
|
||||||
if (!await playerExists(playerId: player.id)) {
|
if (!await playerExists(playerId: player.id)) {
|
||||||
|
final int nameCount = await calculateNameCount(name: player.name);
|
||||||
|
|
||||||
await into(playerTable).insert(
|
await into(playerTable).insert(
|
||||||
PlayerTableCompanion.insert(
|
PlayerTableCompanion.insert(
|
||||||
id: player.id,
|
id: player.id,
|
||||||
name: player.name,
|
name: player.name,
|
||||||
description: player.description,
|
description: player.description,
|
||||||
createdAt: player.createdAt,
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
@@ -62,20 +68,67 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
Future<bool> addPlayersAsList({required List<Player> players}) async {
|
Future<bool> addPlayersAsList({required List<Player> players}) async {
|
||||||
if (players.isEmpty) return false;
|
if (players.isEmpty) return false;
|
||||||
|
|
||||||
|
// Filter out players that already exist
|
||||||
|
final newPlayers = <Player>[];
|
||||||
|
for (final player in players) {
|
||||||
|
if (!await playerExists(playerId: player.id)) {
|
||||||
|
newPlayers.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPlayers.isEmpty) return false;
|
||||||
|
|
||||||
|
// Group players by name
|
||||||
|
final nameGroups = <String, List<Player>>{};
|
||||||
|
for (final player in newPlayers) {
|
||||||
|
nameGroups.putIfAbsent(player.name, () => []).add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
final playersToInsert = <PlayerTableCompanion>[];
|
||||||
|
|
||||||
|
// Process each group of players with the same name
|
||||||
|
for (final entry in nameGroups.entries) {
|
||||||
|
final name = entry.key;
|
||||||
|
final playersWithName = entry.value;
|
||||||
|
|
||||||
|
// Get the current nameCount
|
||||||
|
var nameCount = await calculateNameCount(name: name);
|
||||||
|
|
||||||
|
// One player with the same name
|
||||||
|
if (playersWithName.length == 1) {
|
||||||
|
final player = playersWithName[0];
|
||||||
|
playersToInsert.add(
|
||||||
|
PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
description: player.description,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (nameCount == 0) nameCount++;
|
||||||
|
// Multiple players with the same name
|
||||||
|
for (var i = 0; i < playersWithName.length; i++) {
|
||||||
|
final player = playersWithName[i];
|
||||||
|
playersToInsert.add(
|
||||||
|
PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
description: player.description,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
nameCount: Value(nameCount + i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
playerTable,
|
playerTable,
|
||||||
players
|
playersToInsert,
|
||||||
.map(
|
mode: InsertMode.insertOrReplace,
|
||||||
(player) => PlayerTableCompanion.insert(
|
|
||||||
id: player.id,
|
|
||||||
name: player.name,
|
|
||||||
description: player.description,
|
|
||||||
createdAt: player.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -90,7 +143,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a player with the given [id] exists in the database.
|
/// Checks if a player with the given [playerId] exists in the database.
|
||||||
/// Returns `true` if the player exists, `false` otherwise.
|
/// Returns `true` if the player exists, `false` otherwise.
|
||||||
Future<bool> playerExists({required String playerId}) async {
|
Future<bool> playerExists({required String playerId}) async {
|
||||||
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
@@ -103,9 +156,38 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
required String playerId,
|
required String playerId,
|
||||||
required String newName,
|
required String newName,
|
||||||
}) async {
|
}) async {
|
||||||
|
// Get previous name and name count for the player before updating
|
||||||
|
final previousPlayerName =
|
||||||
|
await (select(playerTable)..where((p) => p.id.equals(playerId)))
|
||||||
|
.map((row) => row.name)
|
||||||
|
.getSingleOrNull() ??
|
||||||
|
'';
|
||||||
|
final previousNameCount = await getNameCount(name: previousPlayerName);
|
||||||
|
|
||||||
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
|
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
|
||||||
PlayerTableCompanion(name: Value(newName)),
|
PlayerTableCompanion(name: Value(newName)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update name count for the new name
|
||||||
|
final count = await calculateNameCount(name: newName);
|
||||||
|
if (count > 0) {
|
||||||
|
await (update(playerTable)..where((p) => p.name.equals(newName))).write(
|
||||||
|
PlayerTableCompanion(nameCount: Value(count)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousNameCount > 0) {
|
||||||
|
// Get the player with that name and the hightest nameCount, and update their nameCount to previousNameCount
|
||||||
|
final player = await getPlayerWithHighestNameCount(
|
||||||
|
name: previousPlayerName,
|
||||||
|
);
|
||||||
|
if (player != null) {
|
||||||
|
await updateNameCount(
|
||||||
|
playerId: player.id,
|
||||||
|
nameCount: previousNameCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the total count of players in the database.
|
/// Retrieves the total count of players in the database.
|
||||||
@@ -117,6 +199,76 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
return count ?? 0;
|
return count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the count of players with the given [name].
|
||||||
|
Future<int> getNameCount({required String name}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.name.equals(name));
|
||||||
|
final result = await query.get();
|
||||||
|
return result.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the nameCount for the player with the given [playerId] to [nameCount].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateNameCount({
|
||||||
|
required String playerId,
|
||||||
|
required int nameCount,
|
||||||
|
}) async {
|
||||||
|
final query = update(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
PlayerTableCompanion(nameCount: Value(nameCount)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<Player?> getPlayerWithHighestNameCount({required String name}) async {
|
||||||
|
final query = select(playerTable)
|
||||||
|
..where((p) => p.name.equals(name))
|
||||||
|
..orderBy([(p) => OrderingTerm.desc(p.nameCount)])
|
||||||
|
..limit(1);
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
if (result != null) {
|
||||||
|
return Player(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
description: result.description,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
nameCount: result.nameCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<int> calculateNameCount({required String name}) async {
|
||||||
|
final count = await getNameCount(name: name);
|
||||||
|
final int nameCount;
|
||||||
|
|
||||||
|
if (count == 1) {
|
||||||
|
// If one other player exists with the same name, initialize the nameCount
|
||||||
|
await initializeNameCount(name: name);
|
||||||
|
// And for the new player, set nameCount to 2
|
||||||
|
nameCount = 2;
|
||||||
|
} else if (count > 1) {
|
||||||
|
// If more than one player exists with the same name, just increment
|
||||||
|
// the nameCount for the new player
|
||||||
|
nameCount = count + 1;
|
||||||
|
} else {
|
||||||
|
// If no other players exist with the same name, set nameCount to 0
|
||||||
|
nameCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<bool> initializeNameCount({required String name}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(playerTable)..where((p) => p.name.equals(name))).write(
|
||||||
|
const PlayerTableCompanion(nameCount: Value(1)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes all players from the database.
|
/// Deletes all players from the database.
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> deleteAllPlayers() async {
|
Future<bool> deleteAllPlayers() async {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
matchId: matchId,
|
matchId: matchId,
|
||||||
teamId: Value(teamId),
|
teamId: Value(teamId),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,21 +83,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;
|
||||||
@@ -235,22 +235,29 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves the winner of a match based on the highest score.
|
// Retrieves the winner of a match by looking for a score entry where score
|
||||||
|
/// is 1. Returns `null` if no player found, else the first with the score.
|
||||||
Future<Player?> getWinner({required String matchId}) async {
|
Future<Player?> getWinner({required String matchId}) async {
|
||||||
final query = select(scoreEntryTable)
|
final query =
|
||||||
..where((s) => s.matchId.equals(matchId))
|
select(scoreEntryTable).join([
|
||||||
..orderBy([(s) => OrderingTerm.desc(s.score)])
|
innerJoin(
|
||||||
..limit(1);
|
db.playerTable,
|
||||||
final result = await query.getSingleOrNull();
|
db.playerTable.id.equalsExp(scoreEntryTable.playerId),
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
scoreEntryTable.matchId.equals(matchId) &
|
||||||
|
scoreEntryTable.score.equals(1),
|
||||||
|
);
|
||||||
|
|
||||||
if (result == null) return null;
|
final result = await query.get();
|
||||||
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
final player = await db.playerDao.getPlayerById(playerId: result.playerId);
|
final playerData = result.first.readTable(db.playerTable);
|
||||||
return Player(
|
return Player(
|
||||||
id: player.id,
|
id: playerData.id,
|
||||||
name: player.name,
|
name: playerData.name,
|
||||||
createdAt: player.createdAt,
|
createdAt: playerData.createdAt,
|
||||||
description: player.description,
|
description: playerData.description,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,20 +302,29 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the looser of a match based on the score 0.
|
/// Retrieves the looser of a match by looking for a score entry where score
|
||||||
|
/// is 0. Returns `null` if no player found, else the first with the score.
|
||||||
Future<Player?> getLooser({required String matchId}) async {
|
Future<Player?> getLooser({required String matchId}) async {
|
||||||
final query = select(scoreEntryTable)
|
final query =
|
||||||
..where((s) => s.matchId.equals(matchId) & s.score.equals(0));
|
select(scoreEntryTable).join([
|
||||||
final result = await query.getSingleOrNull();
|
innerJoin(
|
||||||
|
db.playerTable,
|
||||||
|
db.playerTable.id.equalsExp(scoreEntryTable.playerId),
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
scoreEntryTable.matchId.equals(matchId) &
|
||||||
|
scoreEntryTable.score.equals(0),
|
||||||
|
);
|
||||||
|
|
||||||
if (result == null) return null;
|
final result = await query.get();
|
||||||
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
final player = await db.playerDao.getPlayerById(playerId: result.playerId);
|
final playerData = result.first.readTable(db.playerTable);
|
||||||
return Player(
|
return Player(
|
||||||
id: player.id,
|
id: playerData.id,
|
||||||
name: player.name,
|
name: playerData.name,
|
||||||
createdAt: player.createdAt,
|
createdAt: playerData.createdAt,
|
||||||
description: player.description,
|
description: playerData.description,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,17 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||||
|
'createdAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
||||||
|
'created_at',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
);
|
||||||
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
static const VerificationMeta _nameMeta = const VerificationMeta('name');
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||||
@@ -27,6 +38,18 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
);
|
);
|
||||||
|
static const VerificationMeta _nameCountMeta = const VerificationMeta(
|
||||||
|
'nameCount',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<int> nameCount = GeneratedColumn<int>(
|
||||||
|
'name_count',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: DriftSqlType.int,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: const Constant(0),
|
||||||
|
);
|
||||||
static const VerificationMeta _descriptionMeta = const VerificationMeta(
|
static const VerificationMeta _descriptionMeta = const VerificationMeta(
|
||||||
'description',
|
'description',
|
||||||
);
|
);
|
||||||
@@ -38,19 +61,14 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: true,
|
||||||
);
|
);
|
||||||
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
|
||||||
'createdAt',
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
late final GeneratedColumn<DateTime> createdAt = GeneratedColumn<DateTime>(
|
List<GeneratedColumn> get $columns => [
|
||||||
'created_at',
|
id,
|
||||||
aliasedName,
|
createdAt,
|
||||||
false,
|
name,
|
||||||
type: DriftSqlType.dateTime,
|
nameCount,
|
||||||
requiredDuringInsert: true,
|
description,
|
||||||
);
|
];
|
||||||
@override
|
|
||||||
List<GeneratedColumn> get $columns => [id, name, description, createdAt];
|
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@override
|
@override
|
||||||
@@ -68,6 +86,14 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_idMeta);
|
context.missing(_idMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('created_at')) {
|
||||||
|
context.handle(
|
||||||
|
_createdAtMeta,
|
||||||
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_createdAtMeta);
|
||||||
|
}
|
||||||
if (data.containsKey('name')) {
|
if (data.containsKey('name')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_nameMeta,
|
_nameMeta,
|
||||||
@@ -76,6 +102,12 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_nameMeta);
|
context.missing(_nameMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('name_count')) {
|
||||||
|
context.handle(
|
||||||
|
_nameCountMeta,
|
||||||
|
nameCount.isAcceptableOrUnknown(data['name_count']!, _nameCountMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data.containsKey('description')) {
|
if (data.containsKey('description')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_descriptionMeta,
|
_descriptionMeta,
|
||||||
@@ -87,14 +119,6 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_descriptionMeta);
|
context.missing(_descriptionMeta);
|
||||||
}
|
}
|
||||||
if (data.containsKey('created_at')) {
|
|
||||||
context.handle(
|
|
||||||
_createdAtMeta,
|
|
||||||
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_createdAtMeta);
|
|
||||||
}
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,18 +132,22 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}id'],
|
data['${effectivePrefix}id'],
|
||||||
)!,
|
)!,
|
||||||
|
createdAt: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}created_at'],
|
||||||
|
)!,
|
||||||
name: attachedDatabase.typeMapping.read(
|
name: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}name'],
|
data['${effectivePrefix}name'],
|
||||||
)!,
|
)!,
|
||||||
|
nameCount: attachedDatabase.typeMapping.read(
|
||||||
|
DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}name_count'],
|
||||||
|
)!,
|
||||||
description: attachedDatabase.typeMapping.read(
|
description: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string,
|
DriftSqlType.string,
|
||||||
data['${effectivePrefix}description'],
|
data['${effectivePrefix}description'],
|
||||||
)!,
|
)!,
|
||||||
createdAt: attachedDatabase.typeMapping.read(
|
|
||||||
DriftSqlType.dateTime,
|
|
||||||
data['${effectivePrefix}created_at'],
|
|
||||||
)!,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,31 +159,35 @@ class $PlayerTableTable extends PlayerTable
|
|||||||
|
|
||||||
class PlayerTableData extends DataClass implements Insertable<PlayerTableData> {
|
class PlayerTableData extends DataClass implements Insertable<PlayerTableData> {
|
||||||
final String id;
|
final String id;
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
|
final String name;
|
||||||
|
final int nameCount;
|
||||||
|
final String description;
|
||||||
const PlayerTableData({
|
const PlayerTableData({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.name,
|
|
||||||
required this.description,
|
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
|
required this.name,
|
||||||
|
required this.nameCount,
|
||||||
|
required this.description,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['id'] = Variable<String>(id);
|
map['id'] = Variable<String>(id);
|
||||||
map['name'] = Variable<String>(name);
|
|
||||||
map['description'] = Variable<String>(description);
|
|
||||||
map['created_at'] = Variable<DateTime>(createdAt);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
|
map['name'] = Variable<String>(name);
|
||||||
|
map['name_count'] = Variable<int>(nameCount);
|
||||||
|
map['description'] = Variable<String>(description);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTableCompanion toCompanion(bool nullToAbsent) {
|
PlayerTableCompanion toCompanion(bool nullToAbsent) {
|
||||||
return PlayerTableCompanion(
|
return PlayerTableCompanion(
|
||||||
id: Value(id),
|
id: Value(id),
|
||||||
name: Value(name),
|
|
||||||
description: Value(description),
|
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
|
name: Value(name),
|
||||||
|
nameCount: Value(nameCount),
|
||||||
|
description: Value(description),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,9 +198,10 @@ class PlayerTableData extends DataClass implements Insertable<PlayerTableData> {
|
|||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return PlayerTableData(
|
return PlayerTableData(
|
||||||
id: serializer.fromJson<String>(json['id']),
|
id: serializer.fromJson<String>(json['id']),
|
||||||
name: serializer.fromJson<String>(json['name']),
|
|
||||||
description: serializer.fromJson<String>(json['description']),
|
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
name: serializer.fromJson<String>(json['name']),
|
||||||
|
nameCount: serializer.fromJson<int>(json['nameCount']),
|
||||||
|
description: serializer.fromJson<String>(json['description']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -176,31 +209,35 @@ class PlayerTableData extends DataClass implements Insertable<PlayerTableData> {
|
|||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<String>(id),
|
'id': serializer.toJson<String>(id),
|
||||||
'name': serializer.toJson<String>(name),
|
|
||||||
'description': serializer.toJson<String>(description),
|
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
|
'name': serializer.toJson<String>(name),
|
||||||
|
'nameCount': serializer.toJson<int>(nameCount),
|
||||||
|
'description': serializer.toJson<String>(description),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTableData copyWith({
|
PlayerTableData copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? name,
|
|
||||||
String? description,
|
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
|
String? name,
|
||||||
|
int? nameCount,
|
||||||
|
String? description,
|
||||||
}) => PlayerTableData(
|
}) => PlayerTableData(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
|
||||||
description: description ?? this.description,
|
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
name: name ?? this.name,
|
||||||
|
nameCount: nameCount ?? this.nameCount,
|
||||||
|
description: description ?? this.description,
|
||||||
);
|
);
|
||||||
PlayerTableData copyWithCompanion(PlayerTableCompanion data) {
|
PlayerTableData copyWithCompanion(PlayerTableCompanion data) {
|
||||||
return PlayerTableData(
|
return PlayerTableData(
|
||||||
id: data.id.present ? data.id.value : this.id,
|
id: data.id.present ? data.id.value : this.id,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
name: data.name.present ? data.name.value : this.name,
|
name: data.name.present ? data.name.value : this.name,
|
||||||
|
nameCount: data.nameCount.present ? data.nameCount.value : this.nameCount,
|
||||||
description: data.description.present
|
description: data.description.present
|
||||||
? data.description.value
|
? data.description.value
|
||||||
: this.description,
|
: this.description,
|
||||||
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,76 +245,85 @@ class PlayerTableData extends DataClass implements Insertable<PlayerTableData> {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('PlayerTableData(')
|
return (StringBuffer('PlayerTableData(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('description: $description, ')
|
..write('nameCount: $nameCount, ')
|
||||||
..write('createdAt: $createdAt')
|
..write('description: $description')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(id, name, description, createdAt);
|
int get hashCode => Object.hash(id, createdAt, name, nameCount, description);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is PlayerTableData &&
|
(other is PlayerTableData &&
|
||||||
other.id == this.id &&
|
other.id == this.id &&
|
||||||
|
other.createdAt == this.createdAt &&
|
||||||
other.name == this.name &&
|
other.name == this.name &&
|
||||||
other.description == this.description &&
|
other.nameCount == this.nameCount &&
|
||||||
other.createdAt == this.createdAt);
|
other.description == this.description);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerTableCompanion extends UpdateCompanion<PlayerTableData> {
|
class PlayerTableCompanion extends UpdateCompanion<PlayerTableData> {
|
||||||
final Value<String> id;
|
final Value<String> id;
|
||||||
final Value<String> name;
|
|
||||||
final Value<String> description;
|
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
|
final Value<String> name;
|
||||||
|
final Value<int> nameCount;
|
||||||
|
final Value<String> description;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const PlayerTableCompanion({
|
const PlayerTableCompanion({
|
||||||
this.id = const Value.absent(),
|
this.id = const Value.absent(),
|
||||||
this.name = const Value.absent(),
|
|
||||||
this.description = const Value.absent(),
|
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
|
this.name = const Value.absent(),
|
||||||
|
this.nameCount = const Value.absent(),
|
||||||
|
this.description = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
PlayerTableCompanion.insert({
|
PlayerTableCompanion.insert({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
|
||||||
required String description,
|
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
required String name,
|
||||||
|
this.nameCount = const Value.absent(),
|
||||||
|
required String description,
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : id = Value(id),
|
}) : id = Value(id),
|
||||||
|
createdAt = Value(createdAt),
|
||||||
name = Value(name),
|
name = Value(name),
|
||||||
description = Value(description),
|
description = Value(description);
|
||||||
createdAt = Value(createdAt);
|
|
||||||
static Insertable<PlayerTableData> custom({
|
static Insertable<PlayerTableData> custom({
|
||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
Expression<String>? name,
|
|
||||||
Expression<String>? description,
|
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
|
Expression<String>? name,
|
||||||
|
Expression<int>? nameCount,
|
||||||
|
Expression<String>? description,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (id != null) 'id': id,
|
if (id != null) 'id': id,
|
||||||
if (name != null) 'name': name,
|
|
||||||
if (description != null) 'description': description,
|
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (name != null) 'name': name,
|
||||||
|
if (nameCount != null) 'name_count': nameCount,
|
||||||
|
if (description != null) 'description': description,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTableCompanion copyWith({
|
PlayerTableCompanion copyWith({
|
||||||
Value<String>? id,
|
Value<String>? id,
|
||||||
Value<String>? name,
|
|
||||||
Value<String>? description,
|
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
|
Value<String>? name,
|
||||||
|
Value<int>? nameCount,
|
||||||
|
Value<String>? description,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return PlayerTableCompanion(
|
return PlayerTableCompanion(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
|
||||||
description: description ?? this.description,
|
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
name: name ?? this.name,
|
||||||
|
nameCount: nameCount ?? this.nameCount,
|
||||||
|
description: description ?? this.description,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -288,15 +334,18 @@ class PlayerTableCompanion extends UpdateCompanion<PlayerTableData> {
|
|||||||
if (id.present) {
|
if (id.present) {
|
||||||
map['id'] = Variable<String>(id.value);
|
map['id'] = Variable<String>(id.value);
|
||||||
}
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
if (name.present) {
|
if (name.present) {
|
||||||
map['name'] = Variable<String>(name.value);
|
map['name'] = Variable<String>(name.value);
|
||||||
}
|
}
|
||||||
|
if (nameCount.present) {
|
||||||
|
map['name_count'] = Variable<int>(nameCount.value);
|
||||||
|
}
|
||||||
if (description.present) {
|
if (description.present) {
|
||||||
map['description'] = Variable<String>(description.value);
|
map['description'] = Variable<String>(description.value);
|
||||||
}
|
}
|
||||||
if (createdAt.present) {
|
|
||||||
map['created_at'] = Variable<DateTime>(createdAt.value);
|
|
||||||
}
|
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@@ -307,9 +356,10 @@ class PlayerTableCompanion extends UpdateCompanion<PlayerTableData> {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('PlayerTableCompanion(')
|
return (StringBuffer('PlayerTableCompanion(')
|
||||||
..write('id: $id, ')
|
..write('id: $id, ')
|
||||||
..write('name: $name, ')
|
|
||||||
..write('description: $description, ')
|
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('name: $name, ')
|
||||||
|
..write('nameCount: $nameCount, ')
|
||||||
|
..write('description: $description, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@@ -2790,17 +2840,19 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
typedef $$PlayerTableTableCreateCompanionBuilder =
|
typedef $$PlayerTableTableCreateCompanionBuilder =
|
||||||
PlayerTableCompanion Function({
|
PlayerTableCompanion Function({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
|
||||||
required String description,
|
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
required String name,
|
||||||
|
Value<int> nameCount,
|
||||||
|
required String description,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $$PlayerTableTableUpdateCompanionBuilder =
|
typedef $$PlayerTableTableUpdateCompanionBuilder =
|
||||||
PlayerTableCompanion Function({
|
PlayerTableCompanion Function({
|
||||||
Value<String> id,
|
Value<String> id,
|
||||||
Value<String> name,
|
|
||||||
Value<String> description,
|
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
|
Value<String> name,
|
||||||
|
Value<int> nameCount,
|
||||||
|
Value<String> description,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2892,18 +2944,23 @@ class $$PlayerTableTableFilterComposer
|
|||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt,
|
||||||
|
builder: (column) => ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnFilters<String> get name => $composableBuilder(
|
ColumnFilters<String> get name => $composableBuilder(
|
||||||
column: $table.name,
|
column: $table.name,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnFilters<String> get description => $composableBuilder(
|
ColumnFilters<int> get nameCount => $composableBuilder(
|
||||||
column: $table.description,
|
column: $table.nameCount,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
ColumnFilters<String> get description => $composableBuilder(
|
||||||
column: $table.createdAt,
|
column: $table.description,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2997,18 +3054,23 @@ class $$PlayerTableTableOrderingComposer
|
|||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt,
|
||||||
|
builder: (column) => ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
ColumnOrderings<String> get name => $composableBuilder(
|
ColumnOrderings<String> get name => $composableBuilder(
|
||||||
column: $table.name,
|
column: $table.name,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnOrderings<String> get description => $composableBuilder(
|
ColumnOrderings<int> get nameCount => $composableBuilder(
|
||||||
column: $table.description,
|
column: $table.nameCount,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
ColumnOrderings<String> get description => $composableBuilder(
|
||||||
column: $table.createdAt,
|
column: $table.description,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -3025,17 +3087,20 @@ class $$PlayerTableTableAnnotationComposer
|
|||||||
GeneratedColumn<String> get id =>
|
GeneratedColumn<String> get id =>
|
||||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<String> get name =>
|
GeneratedColumn<String> get name =>
|
||||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<int> get nameCount =>
|
||||||
|
$composableBuilder(column: $table.nameCount, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<String> get description => $composableBuilder(
|
GeneratedColumn<String> get description => $composableBuilder(
|
||||||
column: $table.description,
|
column: $table.description,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get createdAt =>
|
|
||||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
|
||||||
|
|
||||||
Expression<T> playerGroupTableRefs<T extends Object>(
|
Expression<T> playerGroupTableRefs<T extends Object>(
|
||||||
Expression<T> Function($$PlayerGroupTableTableAnnotationComposer a) f,
|
Expression<T> Function($$PlayerGroupTableTableAnnotationComposer a) f,
|
||||||
) {
|
) {
|
||||||
@@ -3145,29 +3210,33 @@ class $$PlayerTableTableTableManager
|
|||||||
updateCompanionCallback:
|
updateCompanionCallback:
|
||||||
({
|
({
|
||||||
Value<String> id = const Value.absent(),
|
Value<String> id = const Value.absent(),
|
||||||
Value<String> name = const Value.absent(),
|
|
||||||
Value<String> description = const Value.absent(),
|
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
|
Value<String> name = const Value.absent(),
|
||||||
|
Value<int> nameCount = const Value.absent(),
|
||||||
|
Value<String> description = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => PlayerTableCompanion(
|
}) => PlayerTableCompanion(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
|
||||||
description: description,
|
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
|
name: name,
|
||||||
|
nameCount: nameCount,
|
||||||
|
description: description,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
required String id,
|
required String id,
|
||||||
required String name,
|
|
||||||
required String description,
|
|
||||||
required DateTime createdAt,
|
required DateTime createdAt,
|
||||||
|
required String name,
|
||||||
|
Value<int> nameCount = const Value.absent(),
|
||||||
|
required String description,
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => PlayerTableCompanion.insert(
|
}) => PlayerTableCompanion.insert(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
|
||||||
description: description,
|
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
|
name: name,
|
||||||
|
nameCount: nameCount,
|
||||||
|
description: description,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import 'package:drift/drift.dart';
|
|||||||
|
|
||||||
class PlayerTable extends Table {
|
class PlayerTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
|
||||||
TextColumn get description => text()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
IntColumn get nameCount => integer().withDefault(const Constant(0))();
|
||||||
|
TextColumn get description => text()();
|
||||||
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -33,7 +33,10 @@ class Game {
|
|||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
ruleset = Ruleset.values.firstWhere((e) => e.name == json['ruleset']),
|
ruleset = Ruleset.values.firstWhere(
|
||||||
|
(e) => e.name == json['ruleset'],
|
||||||
|
orElse: () => Ruleset.singleWinner,
|
||||||
|
),
|
||||||
description = json['description'],
|
description = json['description'],
|
||||||
color = GameColor.values.firstWhere((e) => e.name == json['color']),
|
color = GameColor.values.firstWhere((e) => e.name == json['color']),
|
||||||
icon = json['icon'];
|
icon = json['icon'];
|
||||||
@@ -49,4 +52,3 @@ class Game {
|
|||||||
'icon': icon,
|
'icon': icon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -57,7 +55,16 @@ class Match {
|
|||||||
),
|
),
|
||||||
group = null,
|
group = null,
|
||||||
players = [],
|
players = [],
|
||||||
scores = json['scores'],
|
scores = json['scores'] != null
|
||||||
|
? (json['scores'] as Map<String, dynamic>).map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
key,
|
||||||
|
value != null
|
||||||
|
? ScoreEntry.fromJson(value as Map<String, dynamic>)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: {},
|
||||||
notes = json['notes'] ?? '';
|
notes = json['notes'] ?? '';
|
||||||
|
|
||||||
/// Converts the Match instance to a JSON object. Related objects are
|
/// Converts the Match instance to a JSON object. Related objects are
|
||||||
@@ -71,10 +78,62 @@ 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.map((key, value) => MapEntry(key, value?.toJson())),
|
||||||
(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().take(1).toList();
|
||||||
|
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return _getPlayersWithLowestScore().take(1).toList();
|
||||||
|
|
||||||
|
case Ruleset.multipleWinners:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Player> _getPlayersWithHighestScore() {
|
||||||
|
if (players.isEmpty || scores.values.every((score) => score == null)) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ class Player {
|
|||||||
final String id;
|
final String id;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final String name;
|
final String name;
|
||||||
|
int nameCount;
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
Player({
|
Player({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
this.nameCount = 0,
|
||||||
String? description,
|
String? description,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
createdAt = createdAt ?? clock.now(),
|
createdAt = createdAt ?? clock.now(),
|
||||||
@@ -18,7 +20,7 @@ class Player {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Player{id: $id, name: $name, description: $description}';
|
return 'Player{id: $id, createdAt: $createdAt, name: $name, nameCount: $nameCount, description: $description}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Player instance from a JSON object.
|
/// Creates a Player instance from a JSON object.
|
||||||
@@ -26,6 +28,7 @@ class Player {
|
|||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
|
nameCount = 0,
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
|
|
||||||
/// Converts the Player instance to a JSON object.
|
/// Converts the Player instance to a JSON object.
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
class ScoreEntry {
|
class ScoreEntry {
|
||||||
int roundNumber = 0;
|
final int roundNumber;
|
||||||
final int score;
|
final int score;
|
||||||
final int change;
|
final int change;
|
||||||
|
|
||||||
ScoreEntry({
|
ScoreEntry({required this.score, this.roundNumber = 0, this.change = 0});
|
||||||
required this.roundNumber,
|
|
||||||
required this.score,
|
@override
|
||||||
required this.change,
|
String toString() {
|
||||||
});
|
return 'ScoreEntry{roundNumber: $roundNumber, score: $score, change: $change}';
|
||||||
|
}
|
||||||
|
|
||||||
ScoreEntry.fromJson(Map<String, dynamic> json)
|
ScoreEntry.fromJson(Map<String, dynamic> json)
|
||||||
: roundNumber = json['roundNumber'],
|
: roundNumber = json['roundNumber'],
|
||||||
|
|||||||
@@ -22,10 +22,11 @@
|
|||||||
"days_ago": "vor {count} Tagen",
|
"days_ago": "vor {count} Tagen",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"delete_all_data": "Alle Daten löschen",
|
"delete_all_data": "Alle Daten löschen",
|
||||||
"delete_group": "Diese Gruppe löschen",
|
"delete_group": "Gruppe löschen",
|
||||||
"delete_match": "Spiel löschen",
|
"delete_match": "Spiel löschen",
|
||||||
"edit_group": "Gruppe bearbeiten",
|
"edit_group": "Gruppe bearbeiten",
|
||||||
"edit_match": "Gruppe bearbeiten",
|
"edit_match": "Gruppe bearbeiten",
|
||||||
|
"enter_points": "Punkte eingeben",
|
||||||
"enter_results": "Ergebnisse eintragen",
|
"enter_results": "Ergebnisse eintragen",
|
||||||
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||||
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
||||||
@@ -74,6 +75,8 @@
|
|||||||
"player_name": "Spieler:innenname",
|
"player_name": "Spieler:innenname",
|
||||||
"players": "Spieler:innen",
|
"players": "Spieler:innen",
|
||||||
"players_count": "{count} Spieler",
|
"players_count": "{count} Spieler",
|
||||||
|
"point": "Punkt",
|
||||||
|
"points": "Punkte",
|
||||||
"privacy_policy": "Datenschutzerklärung",
|
"privacy_policy": "Datenschutzerklärung",
|
||||||
"quick_create": "Schnellzugriff",
|
"quick_create": "Schnellzugriff",
|
||||||
"recent_matches": "Letzte Spiele",
|
"recent_matches": "Letzte Spiele",
|
||||||
@@ -87,12 +90,14 @@
|
|||||||
"save_changes": "Änderungen speichern",
|
"save_changes": "Änderungen speichern",
|
||||||
"search_for_groups": "Nach Gruppen suchen",
|
"search_for_groups": "Nach Gruppen suchen",
|
||||||
"search_for_players": "Nach Spieler:innen suchen",
|
"search_for_players": "Nach Spieler:innen suchen",
|
||||||
"select_winner": "Gewinner:in wählen:",
|
"select_winner": "Gewinner:in wählen",
|
||||||
|
"select_loser": "Verlierer:in wählen",
|
||||||
"selected_players": "Ausgewählte Spieler:innen",
|
"selected_players": "Ausgewählte Spieler:innen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"single_loser": "Ein:e Verlierer:in",
|
"single_loser": "Ein:e Verlierer:in",
|
||||||
"single_winner": "Ein:e Gewinner:in",
|
"single_winner": "Ein:e Gewinner:in",
|
||||||
"highest_score": "Höchste Punkte",
|
"highest_score": "Höchste Punkte",
|
||||||
|
"loser": "Verlierer:in",
|
||||||
"lowest_score": "Niedrigste Punkte",
|
"lowest_score": "Niedrigste Punkte",
|
||||||
"multiple_winners": "Mehrere Gewinner:innen",
|
"multiple_winners": "Mehrere Gewinner:innen",
|
||||||
"statistics": "Statistiken",
|
"statistics": "Statistiken",
|
||||||
@@ -100,6 +105,7 @@
|
|||||||
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
||||||
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
|
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
|
||||||
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden.",
|
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden.",
|
||||||
|
"tie": "Unentschieden",
|
||||||
"today_at": "Heute um",
|
"today_at": "Heute um",
|
||||||
"undo": "Rückgängig",
|
"undo": "Rückgängig",
|
||||||
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
||||||
|
|||||||
@@ -83,6 +83,9 @@
|
|||||||
"@edit_match": {
|
"@edit_match": {
|
||||||
"description": "Button & Appbar label for editing a match"
|
"description": "Button & Appbar label for editing a match"
|
||||||
},
|
},
|
||||||
|
"@enter_points": {
|
||||||
|
"description": "Label to enter players points"
|
||||||
|
},
|
||||||
"@enter_results": {
|
"@enter_results": {
|
||||||
"description": "Button text to enter match results"
|
"description": "Button text to enter match results"
|
||||||
},
|
},
|
||||||
@@ -232,6 +235,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@points": {
|
||||||
|
"description": "Points label"
|
||||||
|
},
|
||||||
"@privacy_policy": {
|
"@privacy_policy": {
|
||||||
"description": "Privacy policy menu item"
|
"description": "Privacy policy menu item"
|
||||||
},
|
},
|
||||||
@@ -271,6 +277,9 @@
|
|||||||
"@select_winner": {
|
"@select_winner": {
|
||||||
"description": "Label to select the winner"
|
"description": "Label to select the winner"
|
||||||
},
|
},
|
||||||
|
"@select_loser": {
|
||||||
|
"description": "Label to select the loser"
|
||||||
|
},
|
||||||
"@selected_players": {
|
"@selected_players": {
|
||||||
"description": "Shows the number of selected players"
|
"description": "Shows the number of selected players"
|
||||||
},
|
},
|
||||||
@@ -351,6 +360,7 @@
|
|||||||
"delete_match": "Delete Match",
|
"delete_match": "Delete Match",
|
||||||
"edit_group": "Edit Group",
|
"edit_group": "Edit Group",
|
||||||
"edit_match": "Edit Match",
|
"edit_match": "Edit Match",
|
||||||
|
"enter_points": "Enter points",
|
||||||
"enter_results": "Enter Results",
|
"enter_results": "Enter Results",
|
||||||
"error_creating_group": "Error while creating group, please try again",
|
"error_creating_group": "Error while creating group, please try again",
|
||||||
"error_deleting_group": "Error while deleting group, please try again",
|
"error_deleting_group": "Error while deleting group, please try again",
|
||||||
@@ -399,6 +409,8 @@
|
|||||||
"player_name": "Player name",
|
"player_name": "Player name",
|
||||||
"players": "Players",
|
"players": "Players",
|
||||||
"players_count": "{count} Players",
|
"players_count": "{count} Players",
|
||||||
|
"point": "Point",
|
||||||
|
"points": "Points",
|
||||||
"privacy_policy": "Privacy Policy",
|
"privacy_policy": "Privacy Policy",
|
||||||
"quick_create": "Quick Create",
|
"quick_create": "Quick Create",
|
||||||
"recent_matches": "Recent Matches",
|
"recent_matches": "Recent Matches",
|
||||||
@@ -411,12 +423,14 @@
|
|||||||
"save_changes": "Save Changes",
|
"save_changes": "Save Changes",
|
||||||
"search_for_groups": "Search for groups",
|
"search_for_groups": "Search for groups",
|
||||||
"search_for_players": "Search for players",
|
"search_for_players": "Search for players",
|
||||||
"select_winner": "Select Winner:",
|
"select_winner": "Select Winner",
|
||||||
|
"select_loser": "Select Loser",
|
||||||
"selected_players": "Selected players",
|
"selected_players": "Selected players",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"single_loser": "Single Loser",
|
"single_loser": "Single Loser",
|
||||||
"single_winner": "Single Winner",
|
"single_winner": "Single Winner",
|
||||||
"highest_score": "Highest Score",
|
"highest_score": "Highest Score",
|
||||||
|
"loser": "Loser",
|
||||||
"lowest_score": "Lowest Score",
|
"lowest_score": "Lowest Score",
|
||||||
"multiple_winners": "Multiple Winners",
|
"multiple_winners": "Multiple Winners",
|
||||||
"statistics": "Statistics",
|
"statistics": "Statistics",
|
||||||
@@ -424,6 +438,7 @@
|
|||||||
"successfully_added_player": "Successfully added player {playerName}",
|
"successfully_added_player": "Successfully added player {playerName}",
|
||||||
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
||||||
"this_cannot_be_undone": "This can't be undone.",
|
"this_cannot_be_undone": "This can't be undone.",
|
||||||
|
"tie": "Tie",
|
||||||
"today_at": "Today at",
|
"today_at": "Today at",
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"unknown_exception": "Unknown Exception (see console)",
|
"unknown_exception": "Unknown Exception (see console)",
|
||||||
|
|||||||
@@ -254,6 +254,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Edit Match'**
|
/// **'Edit Match'**
|
||||||
String get edit_match;
|
String get edit_match;
|
||||||
|
|
||||||
|
/// Label to enter players points
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Enter points'**
|
||||||
|
String get enter_points;
|
||||||
|
|
||||||
/// Button text to enter match results
|
/// Button text to enter match results
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -542,6 +548,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'{count} Players'**
|
/// **'{count} Players'**
|
||||||
String players_count(int count);
|
String players_count(int count);
|
||||||
|
|
||||||
|
/// No description provided for @point.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Point'**
|
||||||
|
String get point;
|
||||||
|
|
||||||
|
/// Points label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Points'**
|
||||||
|
String get points;
|
||||||
|
|
||||||
/// Privacy policy menu item
|
/// Privacy policy menu item
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -617,9 +635,15 @@ abstract class AppLocalizations {
|
|||||||
/// Label to select the winner
|
/// Label to select the winner
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Select Winner:'**
|
/// **'Select Winner'**
|
||||||
String get select_winner;
|
String get select_winner;
|
||||||
|
|
||||||
|
/// Label to select the loser
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Select Loser'**
|
||||||
|
String get select_loser;
|
||||||
|
|
||||||
/// Shows the number of selected players
|
/// Shows the number of selected players
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -650,6 +674,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Highest Score'**
|
/// **'Highest Score'**
|
||||||
String get highest_score;
|
String get highest_score;
|
||||||
|
|
||||||
|
/// No description provided for @loser.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Loser'**
|
||||||
|
String get loser;
|
||||||
|
|
||||||
/// No description provided for @lowest_score.
|
/// No description provided for @lowest_score.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -692,6 +722,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'This can\'t be undone.'**
|
/// **'This can\'t be undone.'**
|
||||||
String get this_cannot_be_undone;
|
String get this_cannot_be_undone;
|
||||||
|
|
||||||
|
/// No description provided for @tie.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tie'**
|
||||||
|
String get tie;
|
||||||
|
|
||||||
/// Date format for today
|
/// Date format for today
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get delete_all_data => 'Alle Daten löschen';
|
String get delete_all_data => 'Alle Daten löschen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete_group => 'Diese Gruppe löschen';
|
String get delete_group => 'Gruppe löschen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete_match => 'Spiel löschen';
|
String get delete_match => 'Spiel löschen';
|
||||||
@@ -90,6 +90,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get edit_match => 'Gruppe bearbeiten';
|
String get edit_match => 'Gruppe bearbeiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enter_points => 'Punkte eingeben';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get enter_results => 'Ergebnisse eintragen';
|
String get enter_results => 'Ergebnisse eintragen';
|
||||||
|
|
||||||
@@ -240,6 +243,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
return '$count Spieler';
|
return '$count Spieler';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get point => 'Punkt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get points => 'Punkte';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacy_policy => 'Datenschutzerklärung';
|
String get privacy_policy => 'Datenschutzerklärung';
|
||||||
|
|
||||||
@@ -281,7 +290,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get search_for_players => 'Nach Spieler:innen suchen';
|
String get search_for_players => 'Nach Spieler:innen suchen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get select_winner => 'Gewinner:in wählen:';
|
String get select_winner => 'Gewinner:in wählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get select_loser => 'Verlierer:in wählen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get selected_players => 'Ausgewählte Spieler:innen';
|
String get selected_players => 'Ausgewählte Spieler:innen';
|
||||||
@@ -298,6 +310,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highest_score => 'Höchste Punkte';
|
String get highest_score => 'Höchste Punkte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loser => 'Verlierer:in';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get lowest_score => 'Niedrigste Punkte';
|
String get lowest_score => 'Niedrigste Punkte';
|
||||||
|
|
||||||
@@ -323,6 +338,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get this_cannot_be_undone =>
|
String get this_cannot_be_undone =>
|
||||||
'Dies kann nicht rückgängig gemacht werden.';
|
'Dies kann nicht rückgängig gemacht werden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tie => 'Unentschieden';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get today_at => 'Heute um';
|
String get today_at => 'Heute um';
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get edit_match => 'Edit Match';
|
String get edit_match => 'Edit Match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get enter_points => 'Enter points';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get enter_results => 'Enter Results';
|
String get enter_results => 'Enter Results';
|
||||||
|
|
||||||
@@ -240,6 +243,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
return '$count Players';
|
return '$count Players';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get point => 'Point';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get points => 'Points';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacy_policy => 'Privacy Policy';
|
String get privacy_policy => 'Privacy Policy';
|
||||||
|
|
||||||
@@ -281,7 +290,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get search_for_players => 'Search for players';
|
String get search_for_players => 'Search for players';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get select_winner => 'Select Winner:';
|
String get select_winner => 'Select Winner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get select_loser => 'Select Loser';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get selected_players => 'Selected players';
|
String get selected_players => 'Selected players';
|
||||||
@@ -298,6 +310,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get highest_score => 'Highest Score';
|
String get highest_score => 'Highest Score';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get loser => 'Loser';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get lowest_score => 'Lowest Score';
|
String get lowest_score => 'Lowest Score';
|
||||||
|
|
||||||
@@ -322,6 +337,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get this_cannot_be_undone => 'This can\'t be undone.';
|
String get this_cannot_be_undone => 'This can\'t be undone.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tie => 'Tie';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get today_at => 'Today at';
|
String get today_at => 'Today at';
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
final groupName = _groupNameController.text.trim();
|
final groupName = _groupNameController.text.trim();
|
||||||
|
|
||||||
final success = await db.groupDao.addGroup(
|
final success = await db.groupDao.addGroup(
|
||||||
group: Group(name: groupName, description: '', members: selectedPlayers),
|
group: Group(name: groupName, members: selectedPlayers),
|
||||||
);
|
);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
import 'package:tallee/core/adaptive_page_route.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/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
import 'package:tallee/data/models/match.dart';
|
import 'package:tallee/data/models/match.dart';
|
||||||
@@ -10,10 +12,10 @@ import 'package:tallee/data/models/player.dart';
|
|||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart';
|
import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart';
|
||||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||||
import 'package:tallee/presentation/widgets/custom_alert_dialog.dart';
|
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
|
|
||||||
@@ -70,23 +72,16 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomAlertDialog(
|
builder: (context) => CustomAlertDialog(
|
||||||
title: '${loc.delete_group}?',
|
title: '${loc.delete_group}?',
|
||||||
content: loc.this_cannot_be_undone,
|
content: Text(loc.this_cannot_be_undone),
|
||||||
actions: [
|
actions: [
|
||||||
AnimatedDialogButton(
|
CustomDialogAction(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: Text(
|
|
||||||
loc.cancel,
|
|
||||||
style: const TextStyle(color: CustomTheme.textColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedDialogButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(
|
text: loc.delete,
|
||||||
loc.delete,
|
),
|
||||||
style: const TextStyle(
|
CustomDialogAction(
|
||||||
color: CustomTheme.secondaryColor,
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
),
|
buttonType: ButtonType.secondary,
|
||||||
),
|
text: loc.cancel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -153,6 +148,7 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
children: _group.members.map((member) {
|
children: _group.members.map((member) {
|
||||||
return TextIconTile(
|
return TextIconTile(
|
||||||
text: member.name,
|
text: member.name,
|
||||||
|
suffixText: getNameCountText(member),
|
||||||
iconEnabled: false,
|
iconEnabled: false,
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@@ -259,28 +255,37 @@ 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:
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return loc.tie;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class _GroupViewState extends State<GroupView> {
|
|||||||
Group(
|
Group(
|
||||||
name: 'Skeleton Group',
|
name: 'Skeleton Group',
|
||||||
description: '',
|
description: '',
|
||||||
members: List.filled(6, Player(name: 'Skeleton Player', description: '')),
|
members: List.filled(6, Player(name: 'Skeleton Player')),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -43,21 +43,41 @@ 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(
|
||||||
Player(name: 'Skeleton Player 2', description: ''),
|
name:
|
||||||
|
'Skeleton Player 1'
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
Player(
|
||||||
|
name:
|
||||||
|
'Skeleton Player 2'
|
||||||
|
'',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
notes: '',
|
notes: 'These are skeleton notes.',
|
||||||
|
players: [
|
||||||
|
Player(
|
||||||
|
name:
|
||||||
|
'Skeleton Player 1'
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
Player(
|
||||||
|
name:
|
||||||
|
'Skeleton Player 2'
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -125,7 +145,11 @@ class _HomeViewState extends State<HomeView> {
|
|||||||
MatchResultView(match: match),
|
MatchResultView(match: match),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await updatedWinnerInRecentMatches(match.id);
|
await loadRecentMatches();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
print('loaded');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -224,15 +248,12 @@ class _HomeViewState extends State<HomeView> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the winner information for a specific match in the recent matches list.
|
Future<void> loadRecentMatches() async {
|
||||||
Future<void> updatedWinnerInRecentMatches(String matchId) async {
|
|
||||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
final winner = await db.scoreEntryDao.getWinner(matchId: matchId);
|
final matches = await db.matchDao.getAllMatches();
|
||||||
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
|
recentMatches =
|
||||||
if (matchIndex != -1) {
|
(matches..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||||
setState(() {
|
.take(2)
|
||||||
recentMatches[matchIndex].winner = winner;
|
.toList();
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/game.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||||
@@ -13,14 +13,14 @@ class ChooseGameView extends StatefulWidget {
|
|||||||
const ChooseGameView({
|
const ChooseGameView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.games,
|
required this.games,
|
||||||
required this.initialGameIndex,
|
required this.initialGameId,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// A list of tuples containing the game name, description and ruleset
|
/// A list of tuples containing the game name, description and ruleset
|
||||||
final List<(String, String, Ruleset)> games;
|
final List<Game> games;
|
||||||
|
|
||||||
/// The index of the initially selected game
|
/// The id of the initially selected game
|
||||||
final int initialGameIndex;
|
final String initialGameId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChooseGameView> createState() => _ChooseGameViewState();
|
State<ChooseGameView> createState() => _ChooseGameViewState();
|
||||||
@@ -31,11 +31,11 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
final TextEditingController searchBarController = TextEditingController();
|
final TextEditingController searchBarController = TextEditingController();
|
||||||
|
|
||||||
/// Currently selected game index
|
/// Currently selected game index
|
||||||
late int selectedGameIndex;
|
late String selectedGameId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
selectedGameIndex = widget.initialGameIndex;
|
selectedGameId = widget.initialGameId;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,13 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back_ios),
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop(selectedGameIndex);
|
Navigator.of(context).pop(
|
||||||
|
selectedGameId == ''
|
||||||
|
? null
|
||||||
|
: widget.games.firstWhere(
|
||||||
|
(game) => game.id == selectedGameId,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
title: Text(loc.choose_game),
|
title: Text(loc.choose_game),
|
||||||
@@ -62,7 +68,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
if (didPop) {
|
if (didPop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Navigator.of(context).pop(selectedGameIndex);
|
Navigator.of(context).pop(widget.initialGameId);
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -79,19 +85,19 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
|||||||
itemCount: widget.games.length,
|
itemCount: widget.games.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return TitleDescriptionListTile(
|
return TitleDescriptionListTile(
|
||||||
title: widget.games[index].$1,
|
title: widget.games[index].name,
|
||||||
description: widget.games[index].$2,
|
description: widget.games[index].description,
|
||||||
badgeText: translateRulesetToString(
|
badgeText: translateRulesetToString(
|
||||||
widget.games[index].$3,
|
widget.games[index].ruleset,
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
isHighlighted: selectedGameIndex == index,
|
isHighlighted: selectedGameId == widget.games[index].id,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selectedGameIndex == index) {
|
if (selectedGameId != widget.games[index].id) {
|
||||||
selectedGameIndex = -1;
|
selectedGameId = widget.games[index].id;
|
||||||
} else {
|
} else {
|
||||||
selectedGameIndex = index;
|
selectedGameId = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
|||||||
|
|
||||||
class CreateMatchView extends StatefulWidget {
|
class CreateMatchView extends StatefulWidget {
|
||||||
/// A view that allows creating a new match
|
/// A view that allows creating a new match
|
||||||
/// [onWinnerChanged]: Optional callback invoked when the winner is changed
|
/// - [onWinnerChanged]: Optional callback invoked when the winner is changed
|
||||||
|
/// - [matchToEdit]: An optional match to prefill the fields for editing.
|
||||||
|
/// - [onMatchUpdated]: Optional callback invoked when the match is updated (only in
|
||||||
const CreateMatchView({
|
const CreateMatchView({
|
||||||
super.key,
|
super.key,
|
||||||
this.onWinnerChanged,
|
this.onWinnerChanged,
|
||||||
@@ -28,13 +30,11 @@ class CreateMatchView extends StatefulWidget {
|
|||||||
this.onMatchUpdated,
|
this.onMatchUpdated,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Optional callback invoked when the winner is changed
|
|
||||||
final VoidCallback? onWinnerChanged;
|
final VoidCallback? onWinnerChanged;
|
||||||
|
|
||||||
/// Optional callback invoked when the match is updated
|
|
||||||
final void Function(Match)? onMatchUpdated;
|
final void Function(Match)? onMatchUpdated;
|
||||||
|
|
||||||
/// An optional match to prefill the fields
|
/// An optional match to prefill the fields for editing.
|
||||||
final Match? matchToEdit;
|
final Match? matchToEdit;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -50,20 +50,12 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
/// Hint text for the match name input field
|
/// Hint text for the match name input field
|
||||||
String? hintText;
|
String? hintText;
|
||||||
|
|
||||||
/// List of all groups from the database
|
|
||||||
List<Group> groupsList = [];
|
List<Group> groupsList = [];
|
||||||
|
|
||||||
/// List of all players from the database
|
|
||||||
List<Player> playerList = [];
|
List<Player> playerList = [];
|
||||||
|
List<Game> gamesList = [];
|
||||||
|
|
||||||
/// The currently selected group
|
|
||||||
Group? selectedGroup;
|
Group? selectedGroup;
|
||||||
|
Game? selectedGame;
|
||||||
/// The index of the currently selected game in [games] to mark it in
|
|
||||||
/// the [ChooseGameView]
|
|
||||||
int selectedGameIndex = -1;
|
|
||||||
|
|
||||||
/// The currently selected players
|
|
||||||
List<Player> selectedPlayers = [];
|
List<Player> selectedPlayers = [];
|
||||||
|
|
||||||
/// GlobalKey for ScaffoldMessenger to show snackbars
|
/// GlobalKey for ScaffoldMessenger to show snackbars
|
||||||
@@ -81,12 +73,14 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
Future.wait([
|
Future.wait([
|
||||||
db.groupDao.getAllGroups(),
|
db.groupDao.getAllGroups(),
|
||||||
db.playerDao.getAllPlayers(),
|
db.playerDao.getAllPlayers(),
|
||||||
|
db.gameDao.getAllGames(),
|
||||||
]).then((result) async {
|
]).then((result) async {
|
||||||
groupsList = result[0] as List<Group>;
|
groupsList = result[0] as List<Group>;
|
||||||
playerList = result[1] as List<Player>;
|
playerList = result[1] as List<Player>;
|
||||||
|
gamesList = (result[2] as List<Game>);
|
||||||
|
|
||||||
// If a match is provided, prefill the fields
|
// If a match is provided, prefill the fields
|
||||||
if (widget.matchToEdit != null) {
|
if (isEditMode()) {
|
||||||
prefillMatchDetails();
|
prefillMatchDetails();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -105,20 +99,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
hintText ??= loc.match_name;
|
hintText ??= loc.match_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<(String, String, Ruleset)> games = [
|
|
||||||
('Example Game 1', 'This is a description', Ruleset.lowestScore),
|
|
||||||
('Example Game 2', '', Ruleset.singleWinner),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
final buttonText = widget.matchToEdit != null
|
final buttonText = isEditMode() ? loc.save_changes : loc.create_match;
|
||||||
? loc.save_changes
|
final viewTitle = isEditMode() ? loc.edit_match : loc.create_new_match;
|
||||||
: loc.create_match;
|
|
||||||
final viewTitle = widget.matchToEdit != null
|
|
||||||
? loc.edit_match
|
|
||||||
: loc.create_new_match;
|
|
||||||
|
|
||||||
return ScaffoldMessenger(
|
return ScaffoldMessenger(
|
||||||
key: _scaffoldMessengerKey,
|
key: _scaffoldMessengerKey,
|
||||||
@@ -140,21 +125,21 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
),
|
),
|
||||||
ChooseTile(
|
ChooseTile(
|
||||||
title: loc.game,
|
title: loc.game,
|
||||||
trailingText: selectedGameIndex == -1
|
trailingText: selectedGame == null
|
||||||
? loc.none
|
? loc.none_group
|
||||||
: games[selectedGameIndex].$1,
|
: selectedGame!.name,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
selectedGameIndex = await Navigator.of(context).push(
|
selectedGame = await Navigator.of(context).push(
|
||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
builder: (context) => ChooseGameView(
|
builder: (context) => ChooseGameView(
|
||||||
games: games,
|
games: gamesList,
|
||||||
initialGameIndex: selectedGameIndex,
|
initialGameId: selectedGame?.id ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selectedGameIndex != -1) {
|
if (selectedGame != null) {
|
||||||
hintText = games[selectedGameIndex].$1;
|
hintText = selectedGame!.name;
|
||||||
} else {
|
} else {
|
||||||
hintText = loc.match_name;
|
hintText = loc.match_name;
|
||||||
}
|
}
|
||||||
@@ -225,6 +210,10 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isEditMode() {
|
||||||
|
return widget.matchToEdit != null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Determines whether the "Create Match" button should be enabled.
|
/// Determines whether the "Create Match" button should be enabled.
|
||||||
///
|
///
|
||||||
/// Returns `true` if:
|
/// Returns `true` if:
|
||||||
@@ -232,7 +221,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
/// - Either a group is selected OR at least 2 players are selected
|
/// - Either a group is selected OR at least 2 players are selected
|
||||||
bool _enableCreateGameButton() {
|
bool _enableCreateGameButton() {
|
||||||
return (selectedGroup != null ||
|
return (selectedGroup != null ||
|
||||||
(selectedPlayers.length > 1) && selectedGameIndex != -1);
|
(selectedPlayers.length > 1) && selectedGame != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a match was provided to the view, it updates the match in the database
|
// If a match was provided to the view, it updates the match in the database
|
||||||
@@ -240,7 +229,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
// If no match was provided, it creates a new match in the database and
|
// If no match was provided, it creates a new match in the database and
|
||||||
// navigates to the MatchResultView for the newly created match.
|
// navigates to the MatchResultView for the newly created match.
|
||||||
void buttonNavigation(BuildContext context) async {
|
void buttonNavigation(BuildContext context) async {
|
||||||
if (widget.matchToEdit != null) {
|
if (isEditMode()) {
|
||||||
await updateMatch();
|
await updateMatch();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -266,9 +255,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
/// Updates attributes of the existing match in the database based on the
|
/// Updates attributes of the existing match in the database based on the
|
||||||
/// changes made in the edit view.
|
/// changes made in the edit view.
|
||||||
Future<void> updateMatch() async {
|
Future<void> updateMatch() async {
|
||||||
//TODO: Remove when Games implemented
|
|
||||||
final tempGame = await getTemporaryGame();
|
|
||||||
|
|
||||||
final updatedMatch = Match(
|
final updatedMatch = Match(
|
||||||
id: widget.matchToEdit!.id,
|
id: widget.matchToEdit!.id,
|
||||||
name: _matchNameController.text.isEmpty
|
name: _matchNameController.text.isEmpty
|
||||||
@@ -276,8 +262,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
: _matchNameController.text.trim(),
|
: _matchNameController.text.trim(),
|
||||||
group: selectedGroup,
|
group: selectedGroup,
|
||||||
players: selectedPlayers,
|
players: selectedPlayers,
|
||||||
game: tempGame,
|
game: widget.matchToEdit!.game,
|
||||||
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 +299,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,8 +308,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
// Creates a new match and adds it to the database.
|
// Creates a new match and adds it to the database.
|
||||||
// Returns the created match.
|
// Returns the created match.
|
||||||
Future<Match> createMatch() async {
|
Future<Match> createMatch() async {
|
||||||
final tempGame = await getTemporaryGame();
|
|
||||||
|
|
||||||
Match match = Match(
|
Match match = Match(
|
||||||
name: _matchNameController.text.isEmpty
|
name: _matchNameController.text.isEmpty
|
||||||
? (hintText ?? '')
|
? (hintText ?? '')
|
||||||
@@ -335,35 +315,18 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
|||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
group: selectedGroup,
|
group: selectedGroup,
|
||||||
players: selectedPlayers,
|
players: selectedPlayers,
|
||||||
game: tempGame,
|
game: selectedGame!,
|
||||||
);
|
);
|
||||||
await db.matchDao.addMatch(match: match);
|
await db.matchDao.addMatch(match: match);
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove when games fully implemented
|
|
||||||
Future<Game> getTemporaryGame() async {
|
|
||||||
Game? game;
|
|
||||||
|
|
||||||
final selectedGame = games[selectedGameIndex];
|
|
||||||
game = Game(
|
|
||||||
name: selectedGame.$1,
|
|
||||||
description: selectedGame.$2,
|
|
||||||
ruleset: selectedGame.$3,
|
|
||||||
color: GameColor.blue,
|
|
||||||
icon: '',
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.gameDao.addGame(game: game);
|
|
||||||
return game;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a match was provided to the view, this method prefills the input fields
|
// If a match was provided to the view, this method prefills the input fields
|
||||||
void prefillMatchDetails() {
|
void prefillMatchDetails() {
|
||||||
final match = widget.matchToEdit!;
|
final match = widget.matchToEdit!;
|
||||||
_matchNameController.text = match.name;
|
_matchNameController.text = match.name;
|
||||||
selectedPlayers = match.players;
|
selectedPlayers = match.players;
|
||||||
selectedGameIndex = 0;
|
selectedGame = match.game;
|
||||||
|
|
||||||
if (match.group != null) {
|
if (match.group != null) {
|
||||||
selectedGroup = match.group;
|
selectedGroup = match.group;
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
import 'package:tallee/core/adaptive_page_route.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/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/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
|
||||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||||
import 'package:tallee/presentation/widgets/custom_alert_dialog.dart';
|
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
|
|
||||||
@@ -64,23 +65,16 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomAlertDialog(
|
builder: (context) => CustomAlertDialog(
|
||||||
title: '${loc.delete_match}?',
|
title: '${loc.delete_match}?',
|
||||||
content: loc.this_cannot_be_undone,
|
content: Text(loc.this_cannot_be_undone),
|
||||||
actions: [
|
actions: [
|
||||||
AnimatedDialogButton(
|
CustomDialogAction(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: Text(
|
|
||||||
loc.cancel,
|
|
||||||
style: const TextStyle(color: CustomTheme.textColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedDialogButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(
|
text: loc.delete,
|
||||||
loc.delete,
|
),
|
||||||
style: const TextStyle(
|
CustomDialogAction(
|
||||||
color: CustomTheme.secondaryColor,
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
),
|
buttonType: ButtonType.secondary,
|
||||||
),
|
text: loc.cancel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -161,6 +155,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
children: match.players.map((player) {
|
children: match.players.map((player) {
|
||||||
return TextIconTile(
|
return TextIconTile(
|
||||||
text: player.name,
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
iconEnabled: false,
|
iconEnabled: false,
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@@ -175,37 +170,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: getResultWidget(loc),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
/// TODO: Implement different ruleset results display
|
|
||||||
if (match.winner != null) ...[
|
|
||||||
Text(
|
|
||||||
loc.winner,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
match.winner!.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: CustomTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] else ...[
|
|
||||||
Text(
|
|
||||||
loc.no_results_entered_yet,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -232,7 +197,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,
|
||||||
@@ -264,4 +229,108 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
});
|
});
|
||||||
widget.onMatchUpdate.call();
|
widget.onMatchUpdate.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the widget to be displayed in the result [InfoTile]
|
||||||
|
Widget getResultWidget(AppLocalizations loc) {
|
||||||
|
if (isSingleRowResult()) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: getSingleResultRow(loc),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return getScoreResultWidget(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the result row for single winner/loser rulesets or a placeholder
|
||||||
|
/// if no result is entered yet
|
||||||
|
List<Widget> getSingleResultRow(AppLocalizations loc) {
|
||||||
|
// Single Winner
|
||||||
|
if (match.mvp.isNotEmpty && match.game.ruleset == Ruleset.singleWinner) {
|
||||||
|
return [
|
||||||
|
Text(
|
||||||
|
loc.winner,
|
||||||
|
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
match.mvp.first.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
// Single Loser
|
||||||
|
} else if (match.game.ruleset == Ruleset.singleLoser) {
|
||||||
|
return [
|
||||||
|
Text(
|
||||||
|
loc.loser,
|
||||||
|
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
match.mvp.first.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
// No result entered yet
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
Text(
|
||||||
|
loc.no_results_entered_yet,
|
||||||
|
style: const TextStyle(fontSize: 14, color: CustomTheme.textColor),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
bool isSingleRowResult() {
|
||||||
|
return match.game.ruleset == Ruleset.singleWinner ||
|
||||||
|
match.game.ruleset == Ruleset.singleLoser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.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/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/tiles/custom_radio_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/score_list_tile.dart';
|
||||||
|
|
||||||
class MatchResultView extends StatefulWidget {
|
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
|
||||||
@@ -26,30 +30,61 @@ 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;
|
||||||
|
|
||||||
|
/// List of text controllers for score entry, one for each player
|
||||||
|
late final List<TextEditingController> controller;
|
||||||
|
|
||||||
|
late bool canSave;
|
||||||
|
|
||||||
/// Currently selected winner player
|
/// Currently selected winner player
|
||||||
Player? _selectedPlayer;
|
Player? _selectedPlayer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
ruleset = widget.match.game.ruleset;
|
||||||
|
canSave = !rulesetSupportsScoreEntry();
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
if (widget.match.winner != null) {
|
controller = List.generate(
|
||||||
_selectedPlayer = allPlayers.firstWhere(
|
allPlayers.length,
|
||||||
(p) => p.id == widget.match.winner!.id,
|
(index) => TextEditingController()..addListener(() => onTextEnter()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (widget.match.mvp.isNotEmpty) {
|
||||||
|
if (rulesetSupportsWinnerSelection()) {
|
||||||
|
_selectedPlayer = allPlayers.firstWhere(
|
||||||
|
(p) => p.id == widget.match.mvp.first.id,
|
||||||
|
);
|
||||||
|
} else if (rulesetSupportsScoreEntry()) {
|
||||||
|
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();
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (final c in controller) {
|
||||||
|
c.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: CustomTheme.backgroundColor,
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -85,67 +120,169 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
loc.select_winner,
|
'${getTitleForRuleset(loc)}:',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Expanded(
|
if (rulesetSupportsWinnerSelection())
|
||||||
child: RadioGroup<Player>(
|
Expanded(
|
||||||
groupValue: _selectedPlayer,
|
child: RadioGroup<Player>(
|
||||||
onChanged: (Player? value) async {
|
groupValue: _selectedPlayer,
|
||||||
setState(() {
|
onChanged: (Player? value) async {
|
||||||
_selectedPlayer = value;
|
setState(() {
|
||||||
});
|
_selectedPlayer = value;
|
||||||
await _handleWinnerSaving();
|
});
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return CustomRadioListTile(
|
||||||
|
text: allPlayers[index].name,
|
||||||
|
value: allPlayers[index],
|
||||||
|
onContainerTap: (value) async {
|
||||||
|
setState(() {
|
||||||
|
// Check if the already selected player is the same as the newly tapped player.
|
||||||
|
if (_selectedPlayer == value) {
|
||||||
|
// If yes deselected the player by setting it to null.
|
||||||
|
_selectedPlayer = null;
|
||||||
|
} else {
|
||||||
|
// If no assign the newly tapped player to the selected player.
|
||||||
|
(_selectedPlayer = value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (rulesetSupportsScoreEntry())
|
||||||
|
Expanded(
|
||||||
|
child: ListView.separated(
|
||||||
itemCount: allPlayers.length,
|
itemCount: allPlayers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return CustomRadioListTile(
|
return ScoreListTile(
|
||||||
text: allPlayers[index].name,
|
text: allPlayers[index].name,
|
||||||
value: allPlayers[index],
|
controller: controller[index],
|
||||||
onContainerTap: (value) async {
|
);
|
||||||
setState(() {
|
},
|
||||||
// Check if the already selected player is the same as the newly tapped player.
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
if (_selectedPlayer == value) {
|
return const Padding(
|
||||||
// If yes deselected the player by setting it to null.
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
_selectedPlayer = null;
|
child: Divider(indent: 20),
|
||||||
} else {
|
|
||||||
// If no assign the newly tapped player to the selected player.
|
|
||||||
(_selectedPlayer = value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await _handleWinnerSaving();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
CustomWidthButton(
|
||||||
|
text: loc.save_changes,
|
||||||
|
sizeRelativeToWidth: 0.95,
|
||||||
|
onPressed: canSave
|
||||||
|
? () async {
|
||||||
|
final ending = DateTime.now();
|
||||||
|
await db.matchDao.updateMatchEndedAt(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
endedAt: ending,
|
||||||
|
);
|
||||||
|
await _handleSaving();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Navigator.of(context).pop(_selectedPlayer);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updated [canSave] everytime a text is entered in one of the score entry fields.
|
||||||
|
void onTextEnter() {
|
||||||
|
if (rulesetSupportsScoreEntry()) {
|
||||||
|
setState(() {
|
||||||
|
canSave = controller.every((c) => c.text.isNotEmpty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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> _handleWinnerSaving() async {
|
Future<void> _handleSaving() async {
|
||||||
|
if (ruleset == Ruleset.singleWinner) {
|
||||||
|
await _handleWinner();
|
||||||
|
} else if (ruleset == Ruleset.singleLoser) {
|
||||||
|
await _handleLoser();
|
||||||
|
} else if (ruleset == Ruleset.lowestScore ||
|
||||||
|
ruleset == Ruleset.highestScore) {
|
||||||
|
await _handleScores();
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.onWinnerChanged?.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles saving or removing the winner in the database.
|
||||||
|
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
widget.onWinnerChanged?.call();
|
}
|
||||||
|
|
||||||
|
/// Handles saving or removing the loser in the database.
|
||||||
|
Future<bool> _handleLoser() async {
|
||||||
|
if (_selectedPlayer == null) {
|
||||||
|
return await db.scoreEntryDao.removeLooser(matchId: widget.match.id);
|
||||||
|
} else {
|
||||||
|
return await db.scoreEntryDao.setLooser(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
playerId: _selectedPlayer!.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles saving the scores for each player in the database.
|
||||||
|
Future<void> _handleScores() async {
|
||||||
|
for (int i = 0; i < allPlayers.length; i++) {
|
||||||
|
var text = controller[i].text;
|
||||||
|
if (text.isEmpty) {
|
||||||
|
text = '0';
|
||||||
|
}
|
||||||
|
final score = int.parse(text);
|
||||||
|
await db.scoreEntryDao.addScore(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
playerId: allPlayers[i].id,
|
||||||
|
entry: ScoreEntry(roundNumber: 0, score: score, change: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTitleForRuleset(AppLocalizations loc) {
|
||||||
|
switch (ruleset) {
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return loc.select_winner;
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return loc.select_loser;
|
||||||
|
default:
|
||||||
|
return loc.enter_points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rulesetSupportsWinnerSelection() {
|
||||||
|
return ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rulesetSupportsScoreEntry() {
|
||||||
|
return ruleset == Ruleset.lowestScore || 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(
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ const allDependencies = <Package>[
|
|||||||
_cli_util,
|
_cli_util,
|
||||||
_clock,
|
_clock,
|
||||||
_code_assets,
|
_code_assets,
|
||||||
_code_builder,
|
|
||||||
_collection,
|
_collection,
|
||||||
_convert,
|
_convert,
|
||||||
_coverage,
|
_coverage,
|
||||||
@@ -109,6 +108,7 @@ const allDependencies = <Package>[
|
|||||||
_pubspec_parse,
|
_pubspec_parse,
|
||||||
_quiver,
|
_quiver,
|
||||||
_recase,
|
_recase,
|
||||||
|
_record_use,
|
||||||
_retry,
|
_retry,
|
||||||
_rfc_6901,
|
_rfc_6901,
|
||||||
_safe_url_check,
|
_safe_url_check,
|
||||||
@@ -159,14 +159,14 @@ const allDependencies = <Package>[
|
|||||||
|
|
||||||
/// Direct `dependencies`.
|
/// Direct `dependencies`.
|
||||||
const dependencies = <Package>[
|
const dependencies = <Package>[
|
||||||
_flutter,
|
|
||||||
_flutter_localizations,
|
|
||||||
_clock,
|
_clock,
|
||||||
_cupertino_icons,
|
_cupertino_icons,
|
||||||
_drift,
|
_drift,
|
||||||
_drift_flutter,
|
_drift_flutter,
|
||||||
_file_picker,
|
_file_picker,
|
||||||
_file_saver,
|
_file_saver,
|
||||||
|
_flutter,
|
||||||
|
_flutter_localizations,
|
||||||
_fluttericon,
|
_fluttericon,
|
||||||
_font_awesome_flutter,
|
_font_awesome_flutter,
|
||||||
_intl,
|
_intl,
|
||||||
@@ -567,17 +567,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// build_runner 2.13.1
|
/// build_runner 2.14.0
|
||||||
const _build_runner = Package(
|
const _build_runner = Package(
|
||||||
name: 'build_runner',
|
name: 'build_runner',
|
||||||
description: 'A build system for Dart code generation and modular compilation.',
|
description: 'A build system for Dart code generation and modular compilation.',
|
||||||
repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
|
repository: 'https://github.com/dart-lang/build/tree/master/build_runner',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '2.13.1',
|
version: '2.14.0',
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('built_collection'), PackageRef('built_value'), PackageRef('code_builder'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel'), PackageRef('yaml')],
|
dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel'), PackageRef('yaml')],
|
||||||
devDependencies: [PackageRef('stream_channel'), PackageRef('test')],
|
devDependencies: [PackageRef('stream_channel'), PackageRef('test')],
|
||||||
license: '''Copyright 2016, the Dart project authors.
|
license: '''Copyright 2016, the Dart project authors.
|
||||||
|
|
||||||
@@ -1153,47 +1153,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// code_builder 4.11.1
|
|
||||||
const _code_builder = Package(
|
|
||||||
name: 'code_builder',
|
|
||||||
description: 'A fluent, builder-based library for generating valid Dart code.',
|
|
||||||
repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder',
|
|
||||||
authors: [],
|
|
||||||
version: '4.11.1',
|
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
|
||||||
isMarkdown: false,
|
|
||||||
isSdk: false,
|
|
||||||
dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('matcher'), PackageRef('meta')],
|
|
||||||
devDependencies: [PackageRef('build'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('source_gen'), PackageRef('test')],
|
|
||||||
license: '''Copyright 2016, the Dart project authors.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer in the documentation and/or other materials provided
|
|
||||||
with the distribution.
|
|
||||||
* Neither the name of Google LLC nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived
|
|
||||||
from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
|
||||||
);
|
|
||||||
|
|
||||||
/// collection 1.19.1
|
/// collection 1.19.1
|
||||||
const _collection = Package(
|
const _collection = Package(
|
||||||
name: 'collection',
|
name: 'collection',
|
||||||
@@ -2418,14 +2377,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// file_picker 10.3.10
|
/// file_picker 11.0.2
|
||||||
const _file_picker = Package(
|
const _file_picker = Package(
|
||||||
name: 'file_picker',
|
name: 'file_picker',
|
||||||
description: 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.',
|
description: 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.',
|
||||||
homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker',
|
homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker',
|
||||||
repository: 'https://github.com/miguelpruivo/flutter_file_picker',
|
repository: 'https://github.com/miguelpruivo/flutter_file_picker',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '10.3.10',
|
version: '11.0.2',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -2731,13 +2690,13 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// font_awesome_flutter 10.12.0
|
/// font_awesome_flutter 11.0.0
|
||||||
const _font_awesome_flutter = Package(
|
const _font_awesome_flutter = Package(
|
||||||
name: 'font_awesome_flutter',
|
name: 'font_awesome_flutter',
|
||||||
description: 'The Font Awesome Icon pack available as Flutter Icons. Provides 2000 additional icons to use in your apps.',
|
description: 'The Font Awesome Icon pack available as Flutter Icons. Provides 2000 additional icons to use in your apps.',
|
||||||
repository: 'https://github.com/fluttercommunity/font_awesome_flutter',
|
repository: 'https://github.com/fluttercommunity/font_awesome_flutter',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '10.12.0',
|
version: '11.0.0',
|
||||||
spdxIdentifiers: ['MIT'],
|
spdxIdentifiers: ['MIT'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -2892,17 +2851,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// hooks 1.0.2
|
/// hooks 1.0.3
|
||||||
const _hooks = Package(
|
const _hooks = Package(
|
||||||
name: 'hooks',
|
name: 'hooks',
|
||||||
description: 'A library that contains a Dart API for the JSON-based protocol for `hook/build.dart` and `hook/link.dart`.',
|
description: 'A library that contains a Dart API for the JSON-based protocol for `hook/build.dart` and `hook/link.dart`.',
|
||||||
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/hooks',
|
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/hooks',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '1.0.2',
|
version: '1.0.3',
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('collection'), PackageRef('crypto'), PackageRef('logging'), PackageRef('meta'), PackageRef('pub_semver'), PackageRef('yaml')],
|
dependencies: [PackageRef('collection'), PackageRef('crypto'), PackageRef('logging'), PackageRef('meta'), PackageRef('pub_semver'), PackageRef('record_use'), PackageRef('yaml')],
|
||||||
devDependencies: [PackageRef('args'), PackageRef('code_assets'), PackageRef('glob'), PackageRef('json_schema'), PackageRef('path'), PackageRef('test')],
|
devDependencies: [PackageRef('args'), PackageRef('code_assets'), PackageRef('glob'), PackageRef('json_schema'), PackageRef('path'), PackageRef('test')],
|
||||||
license: '''Copyright 2025, the Dart project authors.
|
license: '''Copyright 2025, the Dart project authors.
|
||||||
|
|
||||||
@@ -5106,6 +5065,48 @@ Redistribution and use in source and binary forms, with or without modification,
|
|||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// record_use 0.6.0
|
||||||
|
const _record_use = Package(
|
||||||
|
name: 'record_use',
|
||||||
|
description: '''The serialization logic and API for the usage recording SDK feature.
|
||||||
|
''',
|
||||||
|
repository: 'https://github.com/dart-lang/native/tree/main/pkgs/record_use',
|
||||||
|
authors: [],
|
||||||
|
version: '0.6.0',
|
||||||
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
|
isMarkdown: false,
|
||||||
|
isSdk: false,
|
||||||
|
dependencies: [PackageRef('collection'), PackageRef('meta'), PackageRef('pub_semver')],
|
||||||
|
devDependencies: [PackageRef('json_schema'), PackageRef('test')],
|
||||||
|
license: '''Copyright 2024, the Dart project authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
|
||||||
|
);
|
||||||
|
|
||||||
/// retry 3.1.2
|
/// retry 3.1.2
|
||||||
const _retry = Package(
|
const _retry = Package(
|
||||||
name: 'retry',
|
name: 'retry',
|
||||||
@@ -37480,13 +37481,13 @@ freely, subject to the following restrictions:
|
|||||||
3. This notice may not be removed or altered from any source distribution.''',
|
3. This notice may not be removed or altered from any source distribution.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// vm_service 15.0.2
|
/// vm_service 15.1.0
|
||||||
const _vm_service = Package(
|
const _vm_service = Package(
|
||||||
name: 'vm_service',
|
name: 'vm_service',
|
||||||
description: 'A library to communicate with a service implementing the Dart VM service protocol.',
|
description: 'A library to communicate with a service implementing the Dart VM service protocol.',
|
||||||
repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service',
|
repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '15.0.2',
|
version: '15.1.0',
|
||||||
spdxIdentifiers: ['BSD-3-Clause'],
|
spdxIdentifiers: ['BSD-3-Clause'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
@@ -37880,16 +37881,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// tallee 0.0.20+254
|
/// tallee 0.0.23+257
|
||||||
const _tallee = Package(
|
const _tallee = Package(
|
||||||
name: 'tallee',
|
name: 'tallee',
|
||||||
description: 'Tracking App for Card Games',
|
description: 'Tracking App for Card Games',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.0.20+254',
|
version: '0.0.23+257',
|
||||||
spdxIdentifiers: ['LGPL-3.0'],
|
spdxIdentifiers: ['LGPL-3.0'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')],
|
dependencies: [PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')],
|
||||||
devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')],
|
devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')],
|
||||||
license: '''GNU LESSER GENERAL PUBLIC LICENSE
|
license: '''GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import 'package:tallee/core/custom_theme.dart';
|
|||||||
import 'package:tallee/core/enums.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/settings_view/licenses_view.dart';
|
import 'package:tallee/presentation/views/main_menu/settings_view/licenses_view.dart';
|
||||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
|
||||||
import 'package:tallee/presentation/widgets/custom_alert_dialog.dart';
|
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/settings_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/settings_list_tile.dart';
|
||||||
import 'package:tallee/services/data_transfer_service.dart';
|
import 'package:tallee/services/data_transfer_service.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -122,25 +122,16 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomAlertDialog(
|
builder: (context) => CustomAlertDialog(
|
||||||
title: '${loc.delete_all_data}?',
|
title: '${loc.delete_all_data}?',
|
||||||
content: loc.this_cannot_be_undone,
|
content: Text(loc.this_cannot_be_undone),
|
||||||
actions: [
|
actions: [
|
||||||
AnimatedDialogButton(
|
CustomDialogAction(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: Text(
|
|
||||||
loc.cancel,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedDialogButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: Text(
|
text: loc.delete,
|
||||||
loc.delete,
|
),
|
||||||
style: const TextStyle(
|
CustomDialogAction(
|
||||||
color: CustomTheme.secondaryColor,
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
),
|
buttonType: ButtonType.secondary,
|
||||||
),
|
text: loc.cancel,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,9 +18,18 @@ class StatisticsView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _StatisticsViewState extends State<StatisticsView> {
|
class _StatisticsViewState extends State<StatisticsView> {
|
||||||
List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1));
|
List<(Player, int)> winCounts = List.filled(6, (
|
||||||
List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1));
|
Player(name: 'Skeleton Player'),
|
||||||
List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1));
|
1,
|
||||||
|
));
|
||||||
|
List<(Player, int)> matchCounts = List.filled(6, (
|
||||||
|
Player(name: 'Skeleton Player'),
|
||||||
|
1,
|
||||||
|
));
|
||||||
|
List<(Player, double)> winRates = List.filled(6, (
|
||||||
|
Player(name: 'Skeleton Player'),
|
||||||
|
1,
|
||||||
|
));
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -121,7 +130,10 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
players: players,
|
players: players,
|
||||||
context: context,
|
context: context,
|
||||||
);
|
);
|
||||||
winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts);
|
winRates = computeWinRatePercent(
|
||||||
|
winCounts: winCounts,
|
||||||
|
matchCounts: matchCounts,
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
@@ -130,47 +142,46 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
|
|
||||||
/// Calculates the number of wins for each player
|
/// Calculates the number of wins for each player
|
||||||
/// and returns a sorted list of tuples (playerName, winCount)
|
/// and returns a sorted list of tuples (playerName, winCount)
|
||||||
List<(String, int)> _calculateWinsForAllPlayers({
|
List<(Player, int)> _calculateWinsForAllPlayers({
|
||||||
required List<Match> matches,
|
required List<Match> matches,
|
||||||
required List<Player> players,
|
required List<Player> players,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) {
|
}) {
|
||||||
List<(String, int)> winCounts = [];
|
List<(Player, int)> winCounts = [];
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
// 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.id == winner.id);
|
||||||
// -1 means winner not found in winCounts
|
// -1 means winner not found in winCounts
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final current = winCounts[index].$2;
|
final current = winCounts[index].$2;
|
||||||
winCounts[index] = (winner.id, current + 1);
|
winCounts[index] = (winner, current + 1);
|
||||||
} else {
|
} else {
|
||||||
winCounts.add((winner.id, 1));
|
winCounts.add((winner, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding all players with zero wins
|
// Adding all players with zero wins
|
||||||
for (var player in players) {
|
for (var player in players) {
|
||||||
final index = winCounts.indexWhere((entry) => entry.$1 == player.id);
|
final index = winCounts.indexWhere((entry) => entry.$1.id == player.id);
|
||||||
// -1 means player not found in winCounts
|
// -1 means player not found in winCounts
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
winCounts.add((player.id, 0));
|
winCounts.add((player, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace player IDs with names
|
// Replace player IDs with names
|
||||||
for (int i = 0; i < winCounts.length; i++) {
|
for (int i = 0; i < winCounts.length; i++) {
|
||||||
final playerId = winCounts[i].$1;
|
final playerId = winCounts[i].$1.id;
|
||||||
final player = players.firstWhere(
|
final player = players.firstWhere(
|
||||||
(p) => p.id == playerId,
|
(p) => p.id == playerId,
|
||||||
orElse: () =>
|
orElse: () => Player(id: playerId, name: loc.not_available),
|
||||||
Player(id: playerId, name: loc.not_available, description: ''),
|
|
||||||
);
|
);
|
||||||
winCounts[i] = (player.name, winCounts[i].$2);
|
winCounts[i] = (player, winCounts[i].$2);
|
||||||
}
|
}
|
||||||
|
|
||||||
winCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
winCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
@@ -180,60 +191,51 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
|
|
||||||
/// Calculates the number of matches played for each player
|
/// Calculates the number of matches played for each player
|
||||||
/// and returns a sorted list of tuples (playerName, matchCount)
|
/// and returns a sorted list of tuples (playerName, matchCount)
|
||||||
List<(String, int)> _calculateMatchAmountsForAllPlayers({
|
List<(Player, int)> _calculateMatchAmountsForAllPlayers({
|
||||||
required List<Match> matches,
|
required List<Match> matches,
|
||||||
required List<Player> players,
|
required List<Player> players,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) {
|
}) {
|
||||||
List<(String, int)> matchCounts = [];
|
List<(Player, int)> matchCounts = [];
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
// Counting matches for each player
|
// Counting matches for each player
|
||||||
for (var match in matches) {
|
for (var match in matches) {
|
||||||
if (match.group != null) {
|
for (Player player in match.players) {
|
||||||
final members = match.group!.members.map((p) => p.id).toList();
|
// Check if the player is already in matchCounts
|
||||||
for (var playerId in members) {
|
final index = matchCounts.indexWhere(
|
||||||
final index = matchCounts.indexWhere((entry) => entry.$1 == playerId);
|
(entry) => entry.$1.id == player.id,
|
||||||
// -1 means player not found in matchCounts
|
);
|
||||||
if (index != -1) {
|
|
||||||
final current = matchCounts[index].$2;
|
// -1 -> not found
|
||||||
matchCounts[index] = (playerId, current + 1);
|
if (index == -1) {
|
||||||
} else {
|
// Add new entry
|
||||||
matchCounts.add((playerId, 1));
|
matchCounts.add((player, 1));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final members = match.players.map((p) => p.id).toList();
|
|
||||||
for (var playerId in members) {
|
|
||||||
final index = matchCounts.indexWhere((entry) => entry.$1 == playerId);
|
|
||||||
// -1 means player not found in matchCounts
|
|
||||||
if (index != -1) {
|
|
||||||
final current = matchCounts[index].$2;
|
|
||||||
matchCounts[index] = (playerId, current + 1);
|
|
||||||
} else {
|
} else {
|
||||||
matchCounts.add((playerId, 1));
|
// Update existing entry
|
||||||
|
final currentMatchAmount = matchCounts[index].$2;
|
||||||
|
matchCounts[index] = (player, currentMatchAmount + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adding all players with zero matches
|
// Adding all players with zero matches
|
||||||
for (var player in players) {
|
for (var player in players) {
|
||||||
final index = matchCounts.indexWhere((entry) => entry.$1 == player.id);
|
final index = matchCounts.indexWhere((entry) => entry.$1.id == player.id);
|
||||||
// -1 means player not found in matchCounts
|
// -1 means player not found in matchCounts
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
matchCounts.add((player.id, 0));
|
matchCounts.add((player, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace player IDs with names
|
// Replace player IDs with names
|
||||||
for (int i = 0; i < matchCounts.length; i++) {
|
for (int i = 0; i < matchCounts.length; i++) {
|
||||||
final playerId = matchCounts[i].$1;
|
final playerId = matchCounts[i].$1.id;
|
||||||
final player = players.firstWhere(
|
final player = players.firstWhere(
|
||||||
(p) => p.id == playerId,
|
(p) => p.id == playerId,
|
||||||
orElse: () =>
|
orElse: () => Player(id: playerId, name: loc.not_available),
|
||||||
Player(id: playerId, name: loc.not_available, description: ''),
|
|
||||||
);
|
);
|
||||||
matchCounts[i] = (player.name, matchCounts[i].$2);
|
matchCounts[i] = (player, matchCounts[i].$2);
|
||||||
}
|
}
|
||||||
|
|
||||||
matchCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
matchCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
@@ -241,25 +243,24 @@ class _StatisticsViewState extends State<StatisticsView> {
|
|||||||
return matchCounts;
|
return matchCounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// dart
|
List<(Player, double)> computeWinRatePercent({
|
||||||
List<(String, double)> computeWinRatePercent({
|
required List<(Player, int)> winCounts,
|
||||||
required List<(String, int)> wins,
|
required List<(Player, int)> matchCounts,
|
||||||
required List<(String, int)> matches,
|
|
||||||
}) {
|
}) {
|
||||||
final Map<String, int> winsMap = {for (var e in wins) e.$1: e.$2};
|
final Map<Player, int> winsMap = {for (var e in winCounts) e.$1: e.$2};
|
||||||
final Map<String, int> matchesMap = {for (var e in matches) e.$1: e.$2};
|
final Map<Player, int> matchesMap = {for (var e in matchCounts) e.$1: e.$2};
|
||||||
|
|
||||||
// Get all unique player names
|
// Get all unique player names
|
||||||
final names = {...winsMap.keys, ...matchesMap.keys};
|
final player = {...matchesMap.keys};
|
||||||
|
|
||||||
// Calculate win rates
|
// Calculate win rates
|
||||||
final result = names.map((name) {
|
final result = player.map((name) {
|
||||||
final int w = winsMap[name] ?? 0;
|
final int w = winsMap[name] ?? 0;
|
||||||
final int g = matchesMap[name] ?? 0;
|
final int m = matchesMap[name] ?? 0;
|
||||||
// Calculate percentage and round to 2 decimal places
|
// Calculate percentage and round to 2 decimal places
|
||||||
// Avoid division by zero
|
// Avoid division by zero
|
||||||
final double percent = (g > 0)
|
final double percent = (m > 0)
|
||||||
? double.parse(((w / g)).toStringAsFixed(2))
|
? double.parse(((w / m)).toStringAsFixed(2))
|
||||||
: 0;
|
: 0;
|
||||||
return (name, percent);
|
return (name, percent);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/enums.dart';
|
||||||
|
|
||||||
class AnimatedDialogButton extends StatefulWidget {
|
class AnimatedDialogButton extends StatefulWidget {
|
||||||
/// A custom animated button widget that provides a scaling and opacity effect
|
/// A custom animated button widget that provides a scaling and opacity effect
|
||||||
/// when pressed.
|
/// when pressed.
|
||||||
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
||||||
/// - [child]: The child widget to be displayed inside the button, typically a text or icon.
|
/// - [buttonText]: The text to be displayed on the button.
|
||||||
|
/// - [buttonType]: The type of the button, which determines its styling.
|
||||||
|
/// - [buttonConstraints]: Optional constraints to control the button's size.
|
||||||
const AnimatedDialogButton({
|
const AnimatedDialogButton({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.buttonText,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
required this.child,
|
this.buttonConstraints,
|
||||||
|
this.buttonType = ButtonType.primary,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Callback function that is triggered when the button is pressed.
|
final String buttonText;
|
||||||
|
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
/// The child widget to be displayed inside the button, typically a text or icon.
|
final BoxConstraints? buttonConstraints;
|
||||||
final Widget child;
|
|
||||||
|
final ButtonType buttonType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AnimatedDialogButton> createState() => _AnimatedDialogButtonState();
|
State<AnimatedDialogButton> createState() => _AnimatedDialogButtonState();
|
||||||
@@ -27,6 +33,29 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final textStyling = TextStyle(
|
||||||
|
color: widget.buttonType == ButtonType.primary
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonDecoration = widget.buttonType == ButtonType.primary
|
||||||
|
// Primary
|
||||||
|
? BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
: widget.buttonType == ButtonType.secondary
|
||||||
|
// Secondary
|
||||||
|
? BoxDecoration(
|
||||||
|
border: BoxBorder.all(color: Colors.white, width: 2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
)
|
||||||
|
// Tertiary
|
||||||
|
: const BoxDecoration();
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTapDown: (_) => setState(() => _isPressed = true),
|
onTapDown: (_) => setState(() => _isPressed = true),
|
||||||
onTapUp: (_) => setState(() => _isPressed = false),
|
onTapUp: (_) => setState(() => _isPressed = false),
|
||||||
@@ -38,10 +67,18 @@ class _AnimatedDialogButtonState extends State<AnimatedDialogButton> {
|
|||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
opacity: _isPressed ? 0.6 : 1.0,
|
opacity: _isPressed ? 0.6 : 1.0,
|
||||||
duration: const Duration(milliseconds: 100),
|
duration: const Duration(milliseconds: 100),
|
||||||
child: Container(
|
child: Center(
|
||||||
decoration: CustomTheme.standardBoxDecoration,
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 6),
|
constraints: widget.buttonConstraints,
|
||||||
child: widget.child,
|
decoration: buttonDecoration,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: Text(
|
||||||
|
widget.buttonText,
|
||||||
|
style: textStyling,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
|
||||||
|
|
||||||
class CustomAlertDialog extends StatelessWidget {
|
class CustomAlertDialog extends StatelessWidget {
|
||||||
/// A custom alert dialog widget that provides a os unspecific AlertDialog,
|
/// A custom alert dialog widget that provides a os unspecific AlertDialog,
|
||||||
@@ -16,20 +17,23 @@ class CustomAlertDialog extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String content;
|
final Widget content;
|
||||||
final List<Widget> actions;
|
final List<CustomDialogAction> actions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(title, style: const TextStyle(color: CustomTheme.textColor)),
|
title: Text(
|
||||||
content: Text(
|
title,
|
||||||
content,
|
style: const TextStyle(
|
||||||
style: const TextStyle(color: CustomTheme.textColor),
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
content: content,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
backgroundColor: CustomTheme.boxColor,
|
backgroundColor: CustomTheme.boxColor,
|
||||||
actionsAlignment: MainAxisAlignment.spaceAround,
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||||
side: const BorderSide(color: CustomTheme.boxBorderColor),
|
side: const BorderSide(color: CustomTheme.boxBorderColor),
|
||||||
32
lib/presentation/widgets/dialog/custom_dialog_action.dart
Normal file
32
lib/presentation/widgets/dialog/custom_dialog_action.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:tallee/core/enums.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||||
|
|
||||||
|
class CustomDialogAction extends StatelessWidget {
|
||||||
|
/// A custom dialog action widget that represents a button in a dialog.
|
||||||
|
/// - [text]: The text to be displayed on the button.
|
||||||
|
/// - [buttonType]: The type of the button, which determines its styling.
|
||||||
|
/// - [onPressed]: Callback function that is triggered when the button is pressed.
|
||||||
|
const CustomDialogAction({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.text,
|
||||||
|
this.buttonType = ButtonType.primary,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
final ButtonType buttonType;
|
||||||
|
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedDialogButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
buttonText: text,
|
||||||
|
buttonType: buttonType,
|
||||||
|
buttonConstraints: const BoxConstraints(minWidth: 300),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tallee/core/common.dart';
|
||||||
import 'package:tallee/core/constants.dart';
|
import 'package:tallee/core/constants.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
@@ -62,7 +63,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
/// Skeleton data used while loading players.
|
/// Skeleton data used while loading players.
|
||||||
late final List<Player> skeletonData = List.filled(
|
late final List<Player> skeletonData = List.filled(
|
||||||
7,
|
7,
|
||||||
Player(name: 'Player 0', description: ''),
|
Player(name: 'Player 0'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -140,6 +141,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: TextIconTile(
|
child: TextIconTile(
|
||||||
text: player.name,
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
onIconTap: () {
|
onIconTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Removes the player from the selection and notifies the parent.
|
// Removes the player from the selection and notifies the parent.
|
||||||
@@ -193,6 +195,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return TextIconListTile(
|
return TextIconListTile(
|
||||||
text: suggestedPlayers[index].name,
|
text: suggestedPlayers[index].name,
|
||||||
|
suffixText: getNameCountText(suggestedPlayers[index]),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
// If the player is not already selected
|
// If the player is not already selected
|
||||||
@@ -282,7 +285,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
final playerName = _searchBarController.text.trim();
|
final playerName = _searchBarController.text.trim();
|
||||||
|
|
||||||
final createdPlayer = Player(name: playerName, description: '');
|
int nameCount = _calculateNameCount(playerName);
|
||||||
|
final createdPlayer = Player(name: playerName, nameCount: nameCount);
|
||||||
final success = await db.playerDao.addPlayer(player: createdPlayer);
|
final success = await db.playerDao.addPlayer(player: createdPlayer);
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
@@ -295,6 +299,22 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _calculateNameCount(String playerName) {
|
||||||
|
final playersWithSameName =
|
||||||
|
allPlayers.where((player) => player.name == playerName).toList()
|
||||||
|
..sort((a, b) => a.nameCount.compareTo(b.nameCount));
|
||||||
|
|
||||||
|
if (playersWithSameName.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
} else if (playersWithSameName.length == 1) {
|
||||||
|
// Initialize nameCount
|
||||||
|
playersWithSameName[0].nameCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return following count
|
||||||
|
return playersWithSameName.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the state after successfully adding a new player.
|
/// Updates the state after successfully adding a new player.
|
||||||
void _handleSuccessfulPlayerCreation(Player player) {
|
void _handleSuccessfulPlayerCreation(Player player) {
|
||||||
selectedPlayers.insert(0, player);
|
selectedPlayers.insert(0, player);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/core/common.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||||
@@ -81,7 +82,11 @@ class _GroupTileState extends State<GroupTile> {
|
|||||||
for (var member in [
|
for (var member in [
|
||||||
...widget.group.members,
|
...widget.group.members,
|
||||||
]..sort((a, b) => a.name.compareTo(b.name)))
|
]..sort((a, b) => a.name.compareTo(b.name)))
|
||||||
TextIconTile(text: member.name, iconEnabled: false),
|
TextIconTile(
|
||||||
|
text: member.name,
|
||||||
|
suffixText: getNameCountText(member),
|
||||||
|
iconEnabled: false,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2.5),
|
const SizedBox(height: 2.5),
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -79,8 +79,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 8),
|
// Group Info
|
||||||
|
|
||||||
if (group != null) ...[
|
if (group != null) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -95,7 +94,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 4),
|
||||||
] else if (widget.compact) ...[
|
] else if (widget.compact) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -110,10 +109,69 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 6),
|
||||||
|
] else ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
|
|
||||||
if (winner != null) ...[
|
// Game + Ruleset Badge
|
||||||
|
if (!widget.compact)
|
||||||
|
IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Game
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CustomTheme.primaryColor.withAlpha(230),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8),
|
||||||
|
bottomLeft: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 4,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
match.game.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Ruleset
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CustomTheme.primaryColor.withAlpha(140),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topRight: Radius.circular(8),
|
||||||
|
bottomRight: Radius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 4,
|
||||||
|
horizontal: 8,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
translateRulesetToString(match.game.ruleset, context),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Winner / In Progress Info
|
||||||
|
if (match.mvp.isNotEmpty) ...[
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 8,
|
vertical: 8,
|
||||||
@@ -129,15 +187,11 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
getMvpIcon(),
|
||||||
Icons.emoji_events,
|
|
||||||
size: 20,
|
|
||||||
color: Colors.amber,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${loc.winner}: ${winner.name}',
|
getMvpText(loc),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -189,6 +243,7 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Players List
|
||||||
if (players.isNotEmpty && widget.compact == false) ...[
|
if (players.isNotEmpty && widget.compact == false) ...[
|
||||||
Text(
|
Text(
|
||||||
loc.players,
|
loc.players,
|
||||||
@@ -203,7 +258,11 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
spacing: 6,
|
spacing: 6,
|
||||||
runSpacing: 6,
|
runSpacing: 6,
|
||||||
children: players.map((player) {
|
children: players.map((player) {
|
||||||
return TextIconTile(text: player.name, iconEnabled: false);
|
return TextIconTile(
|
||||||
|
text: player.name,
|
||||||
|
suffixText: getNameCountText(player),
|
||||||
|
iconEnabled: false,
|
||||||
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -230,4 +289,44 @@ 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 getMvpText(AppLocalizations loc) {
|
||||||
|
if (widget.match.mvp.isEmpty) return '';
|
||||||
|
final ruleset = widget.match.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.singleWinner) {
|
||||||
|
return '${loc.winner}: ${widget.match.mvp.first.name}';
|
||||||
|
} else if (ruleset == Ruleset.singleLoser) {
|
||||||
|
return '${loc.loser}: ${widget.match.mvp.first.name}';
|
||||||
|
} else if (ruleset == Ruleset.highestScore ||
|
||||||
|
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.';
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon getMvpIcon() {
|
||||||
|
const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
||||||
|
|
||||||
|
switch (widget.match.game.ruleset) {
|
||||||
|
case Ruleset.singleWinner:
|
||||||
|
return const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
||||||
|
case Ruleset.singleLoser:
|
||||||
|
return const Icon(
|
||||||
|
Icons.sentiment_dissatisfied_outlined,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.blue,
|
||||||
|
);
|
||||||
|
case Ruleset.lowestScore:
|
||||||
|
return const Icon(Icons.arrow_downward, size: 20, color: Colors.orange);
|
||||||
|
case Ruleset.highestScore:
|
||||||
|
return const Icon(Icons.arrow_upward, size: 20, color: Colors.green);
|
||||||
|
default:
|
||||||
|
return const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
lib/presentation/widgets/tiles/score_list_tile.dart
Normal file
83
lib/presentation/widgets/tiles/score_list_tile.dart
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
class ScoreListTile extends StatelessWidget {
|
||||||
|
/// A custom list tile widget that has a text field for inputting a score.
|
||||||
|
/// - [text]: The leading text to be displayed.
|
||||||
|
/// - [controller]: The controller for the text field to input the score.
|
||||||
|
const ScoreListTile({
|
||||||
|
super.key,
|
||||||
|
required this.text,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The text to display next to the radio button.
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
final TextEditingController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
decoration: const BoxDecoration(color: CustomTheme.boxColor),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
maxLength: 4,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
),
|
||||||
|
cursorColor: CustomTheme.textColor,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: loc.points,
|
||||||
|
counterText: '',
|
||||||
|
filled: true,
|
||||||
|
fillColor: CustomTheme.onBoxColor,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 0,
|
||||||
|
vertical: 0,
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: CustomTheme.textColor.withAlpha(100),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/core/common.dart';
|
||||||
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/data/models/player.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ class StatisticsTile extends StatelessWidget {
|
|||||||
final double width;
|
final double width;
|
||||||
|
|
||||||
/// A list of tuples containing labels and their corresponding numeric values.
|
/// A list of tuples containing labels and their corresponding numeric values.
|
||||||
final List<(String, num)> values;
|
final List<(Player, num)> values;
|
||||||
|
|
||||||
/// The maximum number of items to display.
|
/// The maximum number of items to display.
|
||||||
final int itemCount;
|
final int itemCount;
|
||||||
@@ -89,11 +92,29 @@ class StatisticsTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 4.0),
|
padding: const EdgeInsets.only(left: 4.0),
|
||||||
child: Text(
|
child: RichText(
|
||||||
values[index].$1,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
text: TextSpan(
|
||||||
fontSize: 16,
|
style: DefaultTextStyle.of(context).style,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: values[index].$1.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: getNameCountText(values[index].$1),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.textColor.withAlpha(
|
||||||
|
150,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
const TextIconListTile({
|
const TextIconListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
this.suffixText = '',
|
||||||
this.iconEnabled = true,
|
this.iconEnabled = true,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
});
|
});
|
||||||
@@ -16,6 +17,9 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
/// The text to display in the tile.
|
/// The text to display in the tile.
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
|
/// An optional suffix text to display after the main text.
|
||||||
|
final String suffixText;
|
||||||
|
|
||||||
/// A boolean to determine if the icon should be displayed.
|
/// A boolean to determine if the icon should be displayed.
|
||||||
final bool iconEnabled;
|
final bool iconEnabled;
|
||||||
|
|
||||||
@@ -35,12 +39,27 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12.5),
|
padding: const EdgeInsets.symmetric(vertical: 12.5),
|
||||||
child: Text(
|
child: RichText(
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
text: TextSpan(
|
||||||
fontSize: 16,
|
style: DefaultTextStyle.of(context).style,
|
||||||
fontWeight: FontWeight.w500,
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: suffixText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: CustomTheme.textColor.withAlpha(100),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class TextIconTile extends StatelessWidget {
|
|||||||
const TextIconTile({
|
const TextIconTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
this.suffixText = '',
|
||||||
this.iconEnabled = true,
|
this.iconEnabled = true,
|
||||||
this.onIconTap,
|
this.onIconTap,
|
||||||
});
|
});
|
||||||
@@ -16,6 +17,8 @@ class TextIconTile extends StatelessWidget {
|
|||||||
/// The text to display in the tile.
|
/// The text to display in the tile.
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
|
final String suffixText;
|
||||||
|
|
||||||
/// A boolean to determine if the icon should be displayed.
|
/// A boolean to determine if the icon should be displayed.
|
||||||
final bool iconEnabled;
|
final bool iconEnabled;
|
||||||
|
|
||||||
@@ -36,10 +39,28 @@ class TextIconTile extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (iconEnabled) const SizedBox(width: 3),
|
if (iconEnabled) const SizedBox(width: 3),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: RichText(
|
||||||
text,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
text: TextSpan(
|
||||||
|
style: DefaultTextStyle.of(context).style,
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: suffixText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: CustomTheme.textColor.withAlpha(120),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (iconEnabled) ...<Widget>[
|
if (iconEnabled) ...<Widget>[
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:tallee/data/models/game.dart';
|
|||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.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/data/models/team.dart';
|
import 'package:tallee/data/models/team.dart';
|
||||||
|
|
||||||
class DataTransferService {
|
class DataTransferService {
|
||||||
@@ -36,59 +37,12 @@ class DataTransferService {
|
|||||||
final games = await db.gameDao.getAllGames();
|
final games = await db.gameDao.getAllGames();
|
||||||
final teams = await db.teamDao.getAllTeams();
|
final teams = await db.teamDao.getAllTeams();
|
||||||
|
|
||||||
// Construct a JSON representation of the data in normalized format
|
|
||||||
final Map<String, dynamic> jsonMap = {
|
final Map<String, dynamic> jsonMap = {
|
||||||
'players': players.map((p) => p.toJson()).toList(),
|
'players': players.map((player) => player.toJson()).toList(),
|
||||||
'games': games.map((g) => g.toJson()).toList(),
|
'games': games.map((game) => game.toJson()).toList(),
|
||||||
'groups': groups
|
'groups': groups.map((group) => group.toJson()).toList(),
|
||||||
.map(
|
'teams': teams.map((team) => team.toJson()).toList(),
|
||||||
(g) => {
|
'matches': matches.map((match) => match.toJson()).toList(),
|
||||||
'id': g.id,
|
|
||||||
'name': g.name,
|
|
||||||
'description': g.description,
|
|
||||||
'createdAt': g.createdAt.toIso8601String(),
|
|
||||||
'memberIds': (g.members).map((m) => m.id).toList(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
'teams': teams
|
|
||||||
.map(
|
|
||||||
(t) => {
|
|
||||||
'id': t.id,
|
|
||||||
'name': t.name,
|
|
||||||
'createdAt': t.createdAt.toIso8601String(),
|
|
||||||
'memberIds': (t.members).map((m) => m.id).toList(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
'matches': matches
|
|
||||||
.map(
|
|
||||||
(m) => {
|
|
||||||
'id': m.id,
|
|
||||||
'name': m.name,
|
|
||||||
'createdAt': m.createdAt.toIso8601String(),
|
|
||||||
'endedAt': m.endedAt?.toIso8601String(),
|
|
||||||
'gameId': m.game.id,
|
|
||||||
'groupId': m.group?.id,
|
|
||||||
'playerIds': m.players.map((p) => p.id).toList(),
|
|
||||||
'scores': m.scores.map(
|
|
||||||
(playerId, scores) => MapEntry(
|
|
||||||
playerId,
|
|
||||||
scores
|
|
||||||
.map(
|
|
||||||
(s) => {
|
|
||||||
'roundNumber': s.roundNumber,
|
|
||||||
'score': s.score,
|
|
||||||
'change': s.change,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'notes': m.notes,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return json.encode(jsonMap);
|
return json.encode(jsonMap);
|
||||||
@@ -105,7 +59,7 @@ class DataTransferService {
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
||||||
final path = await FilePicker.platform.saveFile(
|
final path = await FilePicker.saveFile(
|
||||||
fileName: '$fileName.json',
|
fileName: '$fileName.json',
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
);
|
);
|
||||||
@@ -126,7 +80,7 @@ class DataTransferService {
|
|||||||
static Future<ImportResult> importData(BuildContext context) async {
|
static Future<ImportResult> importData(BuildContext context) async {
|
||||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
|
||||||
final path = await FilePicker.platform.pickFiles(
|
final path = await FilePicker.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['json'],
|
allowedExtensions: ['json'],
|
||||||
);
|
);
|
||||||
@@ -284,6 +238,15 @@ class DataTransferService {
|
|||||||
? DateTime.parse(map['endedAt'] as String)
|
? DateTime.parse(map['endedAt'] as String)
|
||||||
: null;
|
: null;
|
||||||
final notes = map['notes'] as String? ?? '';
|
final notes = map['notes'] as String? ?? '';
|
||||||
|
final scoresJson = map['scores'] as Map<String, dynamic>? ?? {};
|
||||||
|
final scores = scoresJson.map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
key,
|
||||||
|
value != null
|
||||||
|
? ScoreEntry.fromJson(value as Map<String, dynamic>)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Link attributes to objects
|
// Link attributes to objects
|
||||||
final game = gamesMap[gameId] ?? getFallbackGame();
|
final game = gamesMap[gameId] ?? getFallbackGame();
|
||||||
@@ -305,6 +268,7 @@ class DataTransferService {
|
|||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
endedAt: endedAt,
|
endedAt: endedAt,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
|
scores: scores,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
16
pubspec.yaml
16
pubspec.yaml
@@ -1,24 +1,24 @@
|
|||||||
name: tallee
|
name: tallee
|
||||||
description: "Tracking App for Card Games"
|
description: "Tracking App for Card Games"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.20+254
|
version: 0.0.23+257
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
|
||||||
sdk: flutter
|
|
||||||
flutter_localizations:
|
|
||||||
sdk: flutter
|
|
||||||
clock: ^1.1.2
|
clock: ^1.1.2
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
drift: ^2.27.0
|
drift: ^2.27.0
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
file_picker: ^10.3.6
|
file_picker: ^11.0.2
|
||||||
file_saver: ^0.3.1
|
file_saver: ^0.3.1
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_localizations:
|
||||||
|
sdk: flutter
|
||||||
fluttericon: ^2.0.0
|
fluttericon: ^2.0.0
|
||||||
font_awesome_flutter: ^10.12.0
|
font_awesome_flutter: ^11.0.0
|
||||||
intl: any
|
intl: any
|
||||||
json_schema: ^5.2.2
|
json_schema: ^5.2.2
|
||||||
package_info_plus: ^9.0.0
|
package_info_plus: ^9.0.0
|
||||||
@@ -33,7 +33,7 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.7.0
|
build_runner: ^2.7.0
|
||||||
dart_pubspec_licenses: ^3.0.14
|
dart_pubspec_licenses: ^3.0.14
|
||||||
drift_dev: ^2.29.0
|
drift_dev: ^2.27.0
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
testGroup1 = Group(
|
testGroup1 = Group(
|
||||||
name: 'Test Group',
|
name: 'Test Group',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:tallee/data/models/game.dart';
|
|||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.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';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
late AppDatabase database;
|
late AppDatabase database;
|
||||||
@@ -36,11 +37,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
testPlayer5 = Player(name: 'Eve', description: '');
|
testPlayer5 = Player(name: 'Eve');
|
||||||
testGroup1 = Group(
|
testGroup1 = Group(
|
||||||
name: 'Test Group 1',
|
name: 'Test Group 1',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -63,29 +64,24 @@ void main() {
|
|||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup1,
|
group: testGroup1,
|
||||||
players: [testPlayer4, testPlayer5],
|
players: [testPlayer4, testPlayer5],
|
||||||
winner: testPlayer4,
|
scores: {testPlayer4.id: ScoreEntry(score: 1)},
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
testMatch2 = Match(
|
testMatch2 = Match(
|
||||||
name: 'Second Test Match',
|
name: 'Second Test Match',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup2,
|
group: testGroup2,
|
||||||
players: [testPlayer1, testPlayer2, testPlayer3],
|
players: [testPlayer1, testPlayer2, testPlayer3],
|
||||||
winner: testPlayer2,
|
|
||||||
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: '',
|
|
||||||
);
|
);
|
||||||
testMatchOnlyGroup = Match(
|
testMatchOnlyGroup = Match(
|
||||||
name: 'Test Match with Group',
|
name: 'Test Match with Group',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
group: testGroup2,
|
group: testGroup2,
|
||||||
notes: '',
|
players: testGroup2.members,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await database.playerDao.addPlayersAsList(
|
await database.playerDao.addPlayersAsList(
|
||||||
@@ -289,8 +285,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 +300,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(
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
|
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
|
||||||
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
|
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
|
||||||
testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]);
|
testTeam3 = Team(name: 'Team Gamma', members: [testPlayer1, testPlayer3]);
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:drift/drift.dart' hide isNull;
|
import 'package:drift/drift.dart' hide isNull, isNotNull;
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:tallee/data/db/database.dart';
|
||||||
@@ -24,10 +24,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Test Player', description: '');
|
testPlayer1 = Player(name: 'Test Player');
|
||||||
testPlayer2 = Player(name: 'Second Player', description: '');
|
testPlayer2 = Player(name: 'Second Player');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
@@ -348,7 +348,7 @@ void main() {
|
|||||||
|
|
||||||
// Verifies that a player with empty string name is stored correctly.
|
// Verifies that a player with empty string name is stored correctly.
|
||||||
test('Player with empty string name is stored correctly', () async {
|
test('Player with empty string name is stored correctly', () async {
|
||||||
final emptyNamePlayer = Player(name: '', description: '');
|
final emptyNamePlayer = Player(name: '');
|
||||||
|
|
||||||
await database.playerDao.addPlayer(player: emptyNamePlayer);
|
await database.playerDao.addPlayer(player: emptyNamePlayer);
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ void main() {
|
|||||||
// Verifies that a player with very long name is stored correctly.
|
// Verifies that a player with very long name is stored correctly.
|
||||||
test('Player with very long name is stored correctly', () async {
|
test('Player with very long name is stored correctly', () async {
|
||||||
final longName = 'A' * 1000;
|
final longName = 'A' * 1000;
|
||||||
final longNamePlayer = Player(name: longName, description: '');
|
final longNamePlayer = Player(name: longName);
|
||||||
|
|
||||||
await database.playerDao.addPlayer(player: longNamePlayer);
|
await database.playerDao.addPlayer(player: longNamePlayer);
|
||||||
|
|
||||||
@@ -381,5 +381,160 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(playerExists, true);
|
expect(playerExists, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Name Count Tests', () {
|
||||||
|
test('Single player gets initialized wih name count 0', () async {
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
|
||||||
|
final player = await database.playerDao.getPlayerById(
|
||||||
|
playerId: testPlayer1.id,
|
||||||
|
);
|
||||||
|
expect(player.nameCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Multiple players get initialized wih name count 0', () async {
|
||||||
|
await database.playerDao.addPlayersAsList(
|
||||||
|
players: [testPlayer1, testPlayer2],
|
||||||
|
);
|
||||||
|
|
||||||
|
final players = await database.playerDao.getAllPlayers();
|
||||||
|
|
||||||
|
expect(players.length, 2);
|
||||||
|
for (Player p in players) {
|
||||||
|
expect(p.nameCount, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Seperatly added players nameCount gets increased correctly',
|
||||||
|
() async {
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
|
||||||
|
final player1 = Player(name: testPlayer1.name, description: '');
|
||||||
|
await database.playerDao.addPlayer(player: player1);
|
||||||
|
|
||||||
|
var players = await database.playerDao.getAllPlayers();
|
||||||
|
|
||||||
|
expect(players.length, 2);
|
||||||
|
players.sort((a, b) => a.nameCount.compareTo(b.nameCount));
|
||||||
|
|
||||||
|
for (int i = 0; i < players.length - 1; i++) {
|
||||||
|
expect(players[i].nameCount, i + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Together added players nameCount gets increased correctly',
|
||||||
|
() async {
|
||||||
|
final player1 = Player(name: testPlayer1.name, description: '');
|
||||||
|
final player2 = Player(name: testPlayer1.name, description: '');
|
||||||
|
final player3 = Player(name: testPlayer1.name, description: '');
|
||||||
|
|
||||||
|
// addPlayersAsList() with multiple players and with one player
|
||||||
|
await database.playerDao.addPlayersAsList(players: [testPlayer1]);
|
||||||
|
await database.playerDao.addPlayersAsList(
|
||||||
|
players: [player1, player2, player3],
|
||||||
|
);
|
||||||
|
|
||||||
|
var players = await database.playerDao.getAllPlayers();
|
||||||
|
|
||||||
|
expect(players.length, 4);
|
||||||
|
players.sort((a, b) => a.nameCount.compareTo(b.nameCount));
|
||||||
|
|
||||||
|
for (int i = 0; i < players.length - 1; i++) {
|
||||||
|
expect(players[i].nameCount, i + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test('getNameCount works correctly', () async {
|
||||||
|
final player2 = Player(name: testPlayer1.name);
|
||||||
|
final player3 = Player(name: testPlayer1.name);
|
||||||
|
|
||||||
|
await database.playerDao.addPlayersAsList(
|
||||||
|
players: [testPlayer1, player2, player3],
|
||||||
|
);
|
||||||
|
|
||||||
|
final nameCount = await database.playerDao.getNameCount(
|
||||||
|
name: testPlayer1.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(nameCount, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateNameCount works correctly', () async {
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
|
||||||
|
final success = await database.playerDao.updateNameCount(
|
||||||
|
playerId: testPlayer1.id,
|
||||||
|
nameCount: 2,
|
||||||
|
);
|
||||||
|
expect(success, true);
|
||||||
|
|
||||||
|
final player = await database.playerDao.getPlayerById(
|
||||||
|
playerId: testPlayer1.id,
|
||||||
|
);
|
||||||
|
expect(player.nameCount, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPlayerWithHighestNameCount works correctly', () async {
|
||||||
|
final player2 = Player(name: testPlayer1.name, description: '');
|
||||||
|
final player3 = Player(name: testPlayer1.name, description: '');
|
||||||
|
|
||||||
|
await database.playerDao.addPlayersAsList(
|
||||||
|
players: [testPlayer1, player2, player3],
|
||||||
|
);
|
||||||
|
|
||||||
|
final player = await database.playerDao.getPlayerWithHighestNameCount(
|
||||||
|
name: testPlayer1.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(player, isNotNull);
|
||||||
|
expect(player!.nameCount, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPlayerWithHighestNameCount with non existing player', () async {
|
||||||
|
final player = await database.playerDao.getPlayerWithHighestNameCount(
|
||||||
|
name: 'non-existing-name',
|
||||||
|
);
|
||||||
|
expect(player, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculateNameCount works correctly', () async {
|
||||||
|
// Case 1: No existing players with the name
|
||||||
|
var count = await database.playerDao.calculateNameCount(
|
||||||
|
name: testPlayer1.name,
|
||||||
|
);
|
||||||
|
expect(count, 0);
|
||||||
|
|
||||||
|
// Case 2: One existing player with the name. Should update that
|
||||||
|
// player's nameCount to 1 and return 2 for the new player
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
|
||||||
|
count = await database.playerDao.calculateNameCount(
|
||||||
|
name: testPlayer1.name,
|
||||||
|
);
|
||||||
|
expect(count, 2);
|
||||||
|
|
||||||
|
// Case 3: Multiple existing players with the name.
|
||||||
|
final player2 = Player(name: testPlayer1.name, nameCount: count);
|
||||||
|
await database.playerDao.addPlayer(player: player2);
|
||||||
|
|
||||||
|
count = await database.playerDao.calculateNameCount(
|
||||||
|
name: testPlayer1.name,
|
||||||
|
);
|
||||||
|
expect(count, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPlayerWithHighestNameCount with non existing player', () async {
|
||||||
|
await database.playerDao.addPlayer(player: testPlayer1);
|
||||||
|
await database.playerDao.initializeNameCount(name: testPlayer1.name);
|
||||||
|
final player = await database.playerDao.getPlayerById(
|
||||||
|
playerId: testPlayer1.id,
|
||||||
|
);
|
||||||
|
expect(player.nameCount, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
testGroup = Group(
|
testGroup = Group(
|
||||||
name: 'Test Group',
|
name: 'Test Group',
|
||||||
description: '',
|
description: '',
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testPlayer4 = Player(name: 'Diana', description: '');
|
testPlayer4 = Player(name: 'Diana');
|
||||||
testPlayer5 = Player(name: 'Eve', description: '');
|
testPlayer5 = Player(name: 'Eve');
|
||||||
testPlayer6 = Player(name: 'Frank', description: '');
|
testPlayer6 = Player(name: 'Frank');
|
||||||
testGroup = Group(
|
testGroup = Group(
|
||||||
name: 'Test Group',
|
name: 'Test Group',
|
||||||
description: '',
|
description: '',
|
||||||
@@ -58,14 +58,13 @@ 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: '',
|
|
||||||
);
|
);
|
||||||
testMatchOnlyPlayers = Match(
|
testMatchOnlyPlayers = Match(
|
||||||
name: 'Test Match with Players',
|
name: 'Test Match with Players',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: [testPlayer4, testPlayer5, testPlayer6],
|
players: [testPlayer4, testPlayer5, testPlayer6],
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
|
testTeam1 = Team(name: 'Team Alpha', members: [testPlayer1, testPlayer2]);
|
||||||
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
|
testTeam2 = Team(name: 'Team Beta', members: [testPlayer3, testPlayer4]);
|
||||||
@@ -96,7 +95,7 @@ void main() {
|
|||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(matchHasPlayers, false);
|
expect(matchHasPlayers, true);
|
||||||
|
|
||||||
await database.playerMatchDao.addPlayerToMatch(
|
await database.playerMatchDao.addPlayerToMatch(
|
||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
@@ -397,7 +396,6 @@ void main() {
|
|||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
teamId: testTeam1.id,
|
teamId: testTeam1.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(playersInTeam.length, 2);
|
expect(playersInTeam.length, 2);
|
||||||
final playerIds = playersInTeam.map((p) => p.id).toSet();
|
final playerIds = playersInTeam.map((p) => p.id).toSet();
|
||||||
expect(playerIds.contains(testPlayer1.id), true);
|
expect(playerIds.contains(testPlayer1.id), true);
|
||||||
@@ -426,18 +424,16 @@ void main() {
|
|||||||
playerId: testPlayer1.id,
|
playerId: testPlayer1.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to add the same player again with different score
|
|
||||||
await database.playerMatchDao.addPlayerToMatch(
|
await database.playerMatchDao.addPlayerToMatch(
|
||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
playerId: testPlayer1.id,
|
playerId: testPlayer1.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify player count is still 1
|
|
||||||
final players = await database.playerMatchDao.getPlayersOfMatch(
|
final players = await database.playerMatchDao.getPlayersOfMatch(
|
||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(players?.length, 1);
|
expect(players?.length, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
@@ -546,6 +542,7 @@ void main() {
|
|||||||
matchId: testMatchOnlyGroup.id,
|
matchId: testMatchOnlyGroup.id,
|
||||||
teamId: testTeam1.id,
|
teamId: testTeam1.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(playersInTeam1.length, 2);
|
expect(playersInTeam1.length, 2);
|
||||||
final team1Ids = playersInTeam1.map((p) => p.id).toSet();
|
final team1Ids = playersInTeam1.map((p) => p.id).toSet();
|
||||||
expect(team1Ids.contains(testPlayer1.id), true);
|
expect(team1Ids.contains(testPlayer1.id), true);
|
||||||
@@ -568,13 +565,11 @@ void main() {
|
|||||||
name: 'Match 1',
|
name: 'Match 1',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: playersList,
|
players: playersList,
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
final match2 = Match(
|
final match2 = Match(
|
||||||
name: 'Match 2',
|
name: 'Match 2',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: playersList,
|
players: playersList,
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
withClock(fakeClock, () {
|
withClock(fakeClock, () {
|
||||||
testPlayer1 = Player(name: 'Alice', description: '');
|
testPlayer1 = Player(name: 'Alice');
|
||||||
testPlayer2 = Player(name: 'Bob', description: '');
|
testPlayer2 = Player(name: 'Bob');
|
||||||
testPlayer3 = Player(name: 'Charlie', description: '');
|
testPlayer3 = Player(name: 'Charlie');
|
||||||
testGame = Game(
|
testGame = Game(
|
||||||
name: 'Test Game',
|
name: 'Test Game',
|
||||||
ruleset: Ruleset.singleWinner,
|
ruleset: Ruleset.singleWinner,
|
||||||
@@ -44,13 +44,11 @@ void main() {
|
|||||||
name: 'Test Match 1',
|
name: 'Test Match 1',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: [testPlayer1, testPlayer2],
|
players: [testPlayer1, testPlayer2],
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
testMatch2 = Match(
|
testMatch2 = Match(
|
||||||
name: 'Test Match 2',
|
name: 'Test Match 2',
|
||||||
game: testGame,
|
game: testGame,
|
||||||
players: [testPlayer2, testPlayer3],
|
players: [testPlayer2, testPlayer3],
|
||||||
notes: '',
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -231,8 +229,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),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -302,46 +296,25 @@ void main() {
|
|||||||
final scoresJson = matchData['scores'] as Map<String, dynamic>;
|
final scoresJson = matchData['scores'] as Map<String, dynamic>;
|
||||||
expect(scoresJson, isA<Map<String, dynamic>>());
|
expect(scoresJson, isA<Map<String, dynamic>>());
|
||||||
|
|
||||||
final scores = scoresJson.map(
|
// Verify scores are properly structured (single score per player, not list)
|
||||||
(playerId, scoreList) => MapEntry(
|
expect(scoresJson[testPlayer1.id], isNotNull);
|
||||||
playerId,
|
expect(scoresJson[testPlayer2.id], isNotNull);
|
||||||
(scoreList as List)
|
|
||||||
.map((s) => ScoreEntry.fromJson(s as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(scores, isA<Map<String, List<ScoreEntry>>>());
|
// Parse player 1 score
|
||||||
|
final player1ScoreJson =
|
||||||
|
scoresJson[testPlayer1.id] as Map<String, dynamic>;
|
||||||
|
final player1Score = ScoreEntry.fromJson(player1ScoreJson);
|
||||||
|
expect(player1Score.roundNumber, 1);
|
||||||
|
expect(player1Score.score, 10);
|
||||||
|
expect(player1Score.change, 10);
|
||||||
|
|
||||||
/* Player 1 scores */
|
// Parse player 2 score
|
||||||
// General structure
|
final player2ScoreJson =
|
||||||
expect(scores[testPlayer1.id], isNotNull);
|
scoresJson[testPlayer2.id] as Map<String, dynamic>;
|
||||||
expect(scores[testPlayer1.id]!.length, 2);
|
final player2Score = ScoreEntry.fromJson(player2ScoreJson);
|
||||||
|
expect(player2Score.roundNumber, 1);
|
||||||
// Round 1
|
expect(player2Score.score, 15);
|
||||||
expect(scores[testPlayer1.id]![0].roundNumber, 1);
|
expect(player2Score.change, 15);
|
||||||
expect(scores[testPlayer1.id]![0].score, 10);
|
|
||||||
expect(scores[testPlayer1.id]![0].change, 10);
|
|
||||||
|
|
||||||
// Round 2
|
|
||||||
expect(scores[testPlayer1.id]![1].roundNumber, 2);
|
|
||||||
expect(scores[testPlayer1.id]![1].score, 20);
|
|
||||||
expect(scores[testPlayer1.id]![1].change, 10);
|
|
||||||
|
|
||||||
/* Player 2 scores */
|
|
||||||
// General structure
|
|
||||||
expect(scores[testPlayer2.id], isNotNull);
|
|
||||||
expect(scores[testPlayer2.id]!.length, 2);
|
|
||||||
|
|
||||||
// Round 1
|
|
||||||
expect(scores[testPlayer2.id]![0].roundNumber, 1);
|
|
||||||
expect(scores[testPlayer2.id]![0].score, 15);
|
|
||||||
expect(scores[testPlayer2.id]![0].change, 15);
|
|
||||||
|
|
||||||
// Round 2
|
|
||||||
expect(scores[testPlayer2.id]![1].roundNumber, 2);
|
|
||||||
expect(scores[testPlayer2.id]![1].score, 25);
|
|
||||||
expect(scores[testPlayer2.id]![1].change, 10);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Match without group is handled correctly', (tester) async {
|
testWidgets('Match without group is handled correctly', (tester) async {
|
||||||
@@ -904,14 +877,8 @@ void main() {
|
|||||||
'playerIds': [testPlayer1.id, testPlayer2.id],
|
'playerIds': [testPlayer1.id, testPlayer2.id],
|
||||||
'notes': testMatch.notes,
|
'notes': testMatch.notes,
|
||||||
'scores': {
|
'scores': {
|
||||||
testPlayer1.id: [
|
testPlayer1.id: {'roundNumber': 1, 'score': 10, 'change': 10},
|
||||||
{'roundNumber': 1, 'score': 10, 'change': 10},
|
testPlayer2.id: {'roundNumber': 1, 'score': 15, 'change': 15},
|
||||||
{'roundNumber': 2, 'score': 20, 'change': 10},
|
|
||||||
],
|
|
||||||
testPlayer2.id: [
|
|
||||||
{'roundNumber': 1, 'score': 15, 'change': 15},
|
|
||||||
{'roundNumber': 2, 'score': 25, 'change': 10},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
'createdAt': testMatch.createdAt.toIso8601String(),
|
'createdAt': testMatch.createdAt.toIso8601String(),
|
||||||
'endedAt': null,
|
'endedAt': null,
|
||||||
|
|||||||
Reference in New Issue
Block a user