Merge pull request 'Implementierung von multiple Winners' (#214) from feature/205-Implementierung-von-multipleWinners into development
All checks were successful
All checks were successful
Reviewed-on: #214 Reviewed-by: Felix Kirchner <flixcoo@noreply.git.yannick-weigert.de>
This commit was merged in pull request #214.
This commit is contained in:
@@ -102,36 +102,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"teams": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"memberIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"createdAt",
|
||||
"memberIds"
|
||||
]
|
||||
}
|
||||
},
|
||||
"matches": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -195,6 +165,9 @@
|
||||
},
|
||||
"notes": {
|
||||
"type": "string"
|
||||
},
|
||||
"teams": {
|
||||
"type": ["array", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -214,7 +187,6 @@
|
||||
"players",
|
||||
"games",
|
||||
"groups",
|
||||
"teams",
|
||||
"matches"
|
||||
]
|
||||
}
|
||||
@@ -34,21 +34,13 @@ enum ExportResult { success, canceled, unknownException }
|
||||
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
||||
/// - [Ruleset.placement]: The player with the highest placement wins.
|
||||
enum Ruleset {
|
||||
singleWinner,
|
||||
multipleWinners,
|
||||
highestScore,
|
||||
lowestScore,
|
||||
singleWinner,
|
||||
singleLoser,
|
||||
multipleWinners,
|
||||
placement,
|
||||
singleLoser,
|
||||
}
|
||||
|
||||
/// Different colors available for games
|
||||
/// - [GameColor.red]: Red color
|
||||
/// - [GameColor.blue]: Blue color
|
||||
/// - [GameColor.green]: Green color
|
||||
/// - [GameColor.yellow]: Yellow color
|
||||
/// - [GameColor.purple]: Purple color
|
||||
/// - [GameColor.orange]: Orange color
|
||||
/// - [GameColor.pink]: Pink color
|
||||
/// - [GameColor.teal]: Teal color
|
||||
enum GameColor { red, blue, green, yellow, purple, orange, pink, teal }
|
||||
/// Different colors for highlighting games
|
||||
enum GameColor { red, orange, yellow, green, teal, blue, purple, pink }
|
||||
|
||||
@@ -228,7 +228,7 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
||||
required String playerId,
|
||||
}) async {
|
||||
// Clear previous winner if exists
|
||||
deleteAllScoresForMatch(matchId: matchId);
|
||||
await deleteAllScoresForMatch(matchId: matchId);
|
||||
|
||||
// Set the winner's score to 1
|
||||
final rowsAffected = await into(scoreEntryTable).insert(
|
||||
@@ -245,7 +245,7 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
// Retrieves the winner of a match by looking for a score entry where 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 {
|
||||
final query =
|
||||
@@ -276,13 +276,42 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
||||
/// Returns `true` if the winner was removed, `false` if there are multiple
|
||||
/// scores or if the winner cannot be removed.
|
||||
Future<bool> removeWinner({required String matchId}) async {
|
||||
final scores = await getAllMatchScores(matchId: matchId);
|
||||
return await deleteAllScoresForMatch(matchId: matchId);
|
||||
}
|
||||
|
||||
if (scores.length > 1) {
|
||||
return false;
|
||||
} else {
|
||||
return await deleteAllScoresForMatch(matchId: matchId);
|
||||
}
|
||||
/* multiple winners handling */
|
||||
|
||||
/// Sets the winners for a match.
|
||||
///
|
||||
/// Returns `true` if more than 0 rows were affected
|
||||
Future<bool> setWinners({
|
||||
required List<Player> winners,
|
||||
required String matchId,
|
||||
}) async {
|
||||
// Clear previous winners if exists
|
||||
await deleteAllScoresForMatch(matchId: matchId);
|
||||
|
||||
if (winners.isEmpty) return false;
|
||||
|
||||
await batch((batch) {
|
||||
batch.insertAll(
|
||||
scoreEntryTable,
|
||||
winners
|
||||
.map(
|
||||
(player) => ScoreEntryTableCompanion.insert(
|
||||
playerId: player.id,
|
||||
matchId: matchId,
|
||||
roundNumber: 0,
|
||||
score: 1,
|
||||
change: 0,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Loser handling */
|
||||
@@ -354,6 +383,8 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
||||
}
|
||||
}
|
||||
|
||||
/* placement handling */
|
||||
|
||||
/// Sets the placement for each player in a match.
|
||||
/// The highest score is assigned to the first player, the second highest to the second player, and so on.
|
||||
Future<void> setPlacements({
|
||||
|
||||
@@ -155,7 +155,7 @@ class Match {
|
||||
return _getPlayersWithLowestScore().take(1).toList();
|
||||
|
||||
case Ruleset.multipleWinners:
|
||||
return [];
|
||||
return _getPlayersWithHighestScore().toList();
|
||||
|
||||
case Ruleset.placement:
|
||||
return _getPlayersWithHighestScore().take(1).toList();
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"search_for_groups": "Nach Gruppen suchen",
|
||||
"search_for_players": "Nach Spieler:innen suchen",
|
||||
"select_winner": "Gewinner:in wählen",
|
||||
"select_winners": "Gewinner:innen wählen",
|
||||
"select_loser": "Verlierer:in wählen",
|
||||
"selected_players": "Ausgewählte Spieler:innen",
|
||||
"settings": "Einstellungen",
|
||||
@@ -140,6 +141,7 @@
|
||||
"undo": "Rückgängig",
|
||||
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
||||
"winner": "Gewinner:in",
|
||||
"winners": "Gewinner:innen",
|
||||
"winrate": "Siegquote",
|
||||
"wins": "Siege",
|
||||
"yesterday_at": "Gestern um"
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
"search_for_groups": "Search for groups",
|
||||
"search_for_players": "Search for players",
|
||||
"select_winner": "Select Winner",
|
||||
"select_winners": "Select Winners",
|
||||
"select_loser": "Select Loser",
|
||||
"selected_players": "Selected players",
|
||||
"settings": "Settings",
|
||||
@@ -149,6 +150,7 @@
|
||||
"undo": "Undo",
|
||||
"unknown_exception": "Unknown Exception (see console)",
|
||||
"winner": "Winner",
|
||||
"winners": "Winners",
|
||||
"winrate": "Winrate",
|
||||
"wins": "Wins",
|
||||
"yesterday_at": "Yesterday at"
|
||||
|
||||
@@ -770,6 +770,12 @@ abstract class AppLocalizations {
|
||||
/// **'Select Winner'**
|
||||
String get select_winner;
|
||||
|
||||
/// No description provided for @select_winners.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select Winners'**
|
||||
String get select_winners;
|
||||
|
||||
/// No description provided for @select_loser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -890,6 +896,12 @@ abstract class AppLocalizations {
|
||||
/// **'Winner'**
|
||||
String get winner;
|
||||
|
||||
/// No description provided for @winners.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Winners'**
|
||||
String get winners;
|
||||
|
||||
/// No description provided for @winrate.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -366,6 +366,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get select_winner => 'Gewinner:in wählen';
|
||||
|
||||
@override
|
||||
String get select_winners => 'Gewinner:innen wählen';
|
||||
|
||||
@override
|
||||
String get select_loser => 'Verlierer:in wählen';
|
||||
|
||||
@@ -431,6 +434,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get winner => 'Gewinner:in';
|
||||
|
||||
@override
|
||||
String get winners => 'Gewinner:innen';
|
||||
|
||||
@override
|
||||
String get winrate => 'Siegquote';
|
||||
|
||||
|
||||
@@ -366,6 +366,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get select_winner => 'Select Winner';
|
||||
|
||||
@override
|
||||
String get select_winners => 'Select Winners';
|
||||
|
||||
@override
|
||||
String get select_loser => 'Select Loser';
|
||||
|
||||
@@ -430,6 +433,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get winner => 'Winner';
|
||||
|
||||
@override
|
||||
String get winners => 'Winners';
|
||||
|
||||
@override
|
||||
String get winrate => 'Winrate';
|
||||
|
||||
|
||||
@@ -47,9 +47,9 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
late final AppDatabase db;
|
||||
|
||||
late List<(Ruleset, String)> _rulesets;
|
||||
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
||||
|
||||
late List<(GameColor, String)> _colors;
|
||||
|
||||
Ruleset? selectedRuleset = Ruleset.singleWinner;
|
||||
GameColor? selectedColor = GameColor.orange;
|
||||
|
||||
/// Controller for the game name input field.
|
||||
@@ -77,38 +77,20 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_rulesets = [
|
||||
(
|
||||
Ruleset.singleWinner,
|
||||
translateRulesetToString(Ruleset.singleWinner, context),
|
||||
_rulesets = List.generate(
|
||||
Ruleset.values.length,
|
||||
(index) => (
|
||||
Ruleset.values[index],
|
||||
translateRulesetToString(Ruleset.values[index], context),
|
||||
),
|
||||
(
|
||||
Ruleset.singleLoser,
|
||||
translateRulesetToString(Ruleset.singleLoser, context),
|
||||
);
|
||||
_colors = List.generate(
|
||||
GameColor.values.length,
|
||||
(index) => (
|
||||
GameColor.values[index],
|
||||
translateGameColorToString(GameColor.values[index], context),
|
||||
),
|
||||
(
|
||||
Ruleset.highestScore,
|
||||
translateRulesetToString(Ruleset.highestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.lowestScore,
|
||||
translateRulesetToString(Ruleset.lowestScore, context),
|
||||
),
|
||||
(
|
||||
Ruleset.multipleWinners,
|
||||
translateRulesetToString(Ruleset.multipleWinners, context),
|
||||
),
|
||||
];
|
||||
_colors = [
|
||||
(GameColor.green, translateGameColorToString(GameColor.green, context)),
|
||||
(GameColor.teal, translateGameColorToString(GameColor.teal, context)),
|
||||
(GameColor.blue, translateGameColorToString(GameColor.blue, context)),
|
||||
(GameColor.purple, translateGameColorToString(GameColor.purple, context)),
|
||||
(GameColor.pink, translateGameColorToString(GameColor.pink, context)),
|
||||
(GameColor.red, translateGameColorToString(GameColor.red, context)),
|
||||
(GameColor.orange, translateGameColorToString(GameColor.orange, context)),
|
||||
(GameColor.yellow, translateGameColorToString(GameColor.yellow, context)),
|
||||
];
|
||||
);
|
||||
|
||||
if (widget.gameToEdit != null) {
|
||||
_gameNameController.text = widget.gameToEdit!.name;
|
||||
@@ -214,10 +196,13 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
|
||||
// Choose ruleset tile
|
||||
if (!isEditMode())
|
||||
ChooseTile(title: loc.ruleset, trailing: getColorDropdown(loc)),
|
||||
ChooseTile(
|
||||
title: loc.ruleset,
|
||||
trailing: getRulesetDropdown(loc),
|
||||
),
|
||||
|
||||
// Choose color tile
|
||||
ChooseTile(title: loc.color, trailing: getRulesetDropdown(loc)),
|
||||
ChooseTile(title: loc.color, trailing: getColorDropdown(loc)),
|
||||
|
||||
// Description input field
|
||||
Container(
|
||||
|
||||
@@ -231,11 +231,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Determines whether the "Create Match" button should be enabled.
|
||||
///
|
||||
/// Returns `true` if:
|
||||
/// - A ruleset is selected AND
|
||||
/// - A game is selected AND
|
||||
/// - Either a group is selected OR at least 2 players are selected.
|
||||
bool _enableCreateGameButton() {
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers.length > 1) && selectedGame != null);
|
||||
return ((selectedGroup != null || selectedPlayers.length > 1) &&
|
||||
selectedGame != null);
|
||||
}
|
||||
|
||||
/// Handles navigation when the create or save button is pressed.
|
||||
|
||||
@@ -271,6 +271,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
Widget getResultWidget(AppLocalizations loc) {
|
||||
if (isSingleRowResult()) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: getSingleResultRow(loc),
|
||||
);
|
||||
@@ -282,47 +283,55 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
/// 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,
|
||||
if (match.mvp.isNotEmpty) {
|
||||
final ruleset = match.game.ruleset;
|
||||
|
||||
if (ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser) {
|
||||
return [
|
||||
Text(
|
||||
ruleset == Ruleset.singleWinner ? loc.winner : loc.loser,
|
||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||
),
|
||||
),
|
||||
];
|
||||
// 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,
|
||||
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),
|
||||
),
|
||||
];
|
||||
];
|
||||
} else if (match.game.ruleset == Ruleset.multipleWinners) {
|
||||
return [
|
||||
Text(
|
||||
loc.winners,
|
||||
style: const TextStyle(fontSize: 16, color: CustomTheme.textColor),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
match.mvp.map((player) => player.name).join(', '),
|
||||
textAlign: TextAlign.end,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// No results yet
|
||||
return [
|
||||
Text(
|
||||
loc.no_results_entered_yet,
|
||||
style: const TextStyle(fontSize: 14, color: CustomTheme.textColor),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// Returns the result widget for scores or placement
|
||||
@@ -401,7 +410,8 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
// Returns if the result can be displayed in a single row
|
||||
bool isSingleRowResult() {
|
||||
return match.game.ruleset == Ruleset.singleWinner ||
|
||||
match.game.ruleset == Ruleset.singleLoser;
|
||||
match.game.ruleset == Ruleset.singleLoser ||
|
||||
match.game.ruleset == Ruleset.multipleWinners;
|
||||
}
|
||||
|
||||
String getPlacementText(BuildContext context, int rank) {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/score_list_tile.dart';
|
||||
@@ -45,9 +46,12 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
/// Flag to indicate if the save button should be enabled
|
||||
late bool canSave;
|
||||
|
||||
/// Currently selected winner player
|
||||
/// Currently selected player (single winner / looser)
|
||||
Player? _selectedPlayer;
|
||||
|
||||
/// Currently selected players (multiple winners)
|
||||
final Set<Player> _selectedPlayers = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
@@ -64,17 +68,25 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
|
||||
// Prefill fields
|
||||
if (widget.match.mvp.isNotEmpty) {
|
||||
if (rulesetSupportsWinnerSelection()) {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.mvp.first.id,
|
||||
);
|
||||
if (rulesetSupportsPlayerSelection()) {
|
||||
if (ruleset == Ruleset.multipleWinners) {
|
||||
for (int i = 0; i < allPlayers.length; i++) {
|
||||
if (widget.match.scores[allPlayers[i].id]?.score == 1) {
|
||||
_selectedPlayers.add(allPlayers[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_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();
|
||||
}
|
||||
} else if (rulesetSupportsPlacement()) {
|
||||
} else if (rulesetSupportsDragBehaviour()) {
|
||||
allPlayers.sort((a, b) {
|
||||
final scoreA = widget.match.scores[a.id]?.score ?? 0;
|
||||
final scoreB = widget.match.scores[b.id]?.score ?? 0;
|
||||
@@ -158,38 +170,68 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Show player selection
|
||||
if (rulesetSupportsWinnerSelection())
|
||||
if (rulesetSupportsPlayerSelection())
|
||||
Expanded(
|
||||
child: RadioGroup<Player>(
|
||||
groupValue: _selectedPlayer,
|
||||
onChanged: (Player? value) async {
|
||||
setState(() {
|
||||
_selectedPlayer = value;
|
||||
});
|
||||
},
|
||||
child: ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: allPlayers.length,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomRadioListTile(
|
||||
text: allPlayers[index].name,
|
||||
value: allPlayers[index],
|
||||
onContainerTap: (value) async {
|
||||
child: ruleset == Ruleset.multipleWinners
|
||||
// Multiple winners
|
||||
? ListView.builder(
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
itemCount: allPlayers.length,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomCheckboxListTile(
|
||||
text: allPlayers[index].name,
|
||||
value: _selectedPlayers.contains(
|
||||
allPlayers[index],
|
||||
),
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
if (value) {
|
||||
_selectedPlayers.add(
|
||||
allPlayers[index],
|
||||
);
|
||||
} else {
|
||||
_selectedPlayers.remove(
|
||||
allPlayers[index],
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
// Single winner / looser
|
||||
: RadioGroup<Player>(
|
||||
groupValue: _selectedPlayer,
|
||||
onChanged: (Player? 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);
|
||||
}
|
||||
_selectedPlayer = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
child: ListView.builder(
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(),
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Show score entry
|
||||
@@ -216,7 +258,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
),
|
||||
|
||||
// Show draggable placement list
|
||||
if (rulesetSupportsPlacement())
|
||||
if (rulesetSupportsDragBehaviour())
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -382,12 +424,14 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
await _handleScores();
|
||||
} else if (ruleset == Ruleset.placement) {
|
||||
await _handlePlacement();
|
||||
} else if (ruleset == Ruleset.multipleWinners) {
|
||||
await _handleWinners();
|
||||
}
|
||||
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
/// Handles saving or removing the winner in the database.
|
||||
/// Handles saving or removing the (single) winner in the database.
|
||||
Future<bool> _handleWinner() async {
|
||||
if (_selectedPlayer == null) {
|
||||
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
||||
@@ -399,6 +443,18 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles saving the (multiple) winners to the database.
|
||||
Future<bool> _handleWinners() async {
|
||||
if (_selectedPlayers.isEmpty) {
|
||||
return await db.scoreEntryDao.removeWinner(matchId: widget.match.id);
|
||||
} else {
|
||||
return await db.scoreEntryDao.setWinners(
|
||||
matchId: widget.match.id,
|
||||
winners: allPlayers.where((p) => _selectedPlayers.contains(p)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles saving or removing the loser in the database.
|
||||
Future<bool> _handleLoser() async {
|
||||
if (_selectedPlayer == null) {
|
||||
@@ -443,20 +499,24 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
return loc.select_loser;
|
||||
case Ruleset.placement:
|
||||
return loc.drag_to_set_placement;
|
||||
case Ruleset.multipleWinners:
|
||||
return loc.select_winners;
|
||||
default:
|
||||
return loc.enter_points;
|
||||
}
|
||||
}
|
||||
|
||||
bool rulesetSupportsWinnerSelection() {
|
||||
return ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser;
|
||||
bool rulesetSupportsPlayerSelection() {
|
||||
return ruleset == Ruleset.singleWinner ||
|
||||
ruleset == Ruleset.singleLoser ||
|
||||
ruleset == Ruleset.multipleWinners;
|
||||
}
|
||||
|
||||
bool rulesetSupportsScoreEntry() {
|
||||
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
|
||||
}
|
||||
|
||||
bool rulesetSupportsPlacement() {
|
||||
bool rulesetSupportsDragBehaviour() {
|
||||
return ruleset == Ruleset.placement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
|
||||
class CustomCheckboxListTile extends StatelessWidget {
|
||||
const CustomCheckboxListTile({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorderColor),
|
||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: value,
|
||||
onChanged: (bool? v) {
|
||||
if (v == null) return;
|
||||
onChanged(v);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -264,6 +264,9 @@ class _MatchTileState extends State<MatchTile> {
|
||||
return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})';
|
||||
} else if (ruleset == Ruleset.placement) {
|
||||
return '${loc.winner}: ${widget.match.mvp.first.name}';
|
||||
} else if (ruleset == Ruleset.multipleWinners) {
|
||||
final mvpNames = widget.match.mvp.map((player) => player.name).join(', ');
|
||||
return '${loc.winners}: $mvpNames';
|
||||
}
|
||||
return '${loc.winner}: n.A.';
|
||||
}
|
||||
|
||||
@@ -883,14 +883,6 @@ void main() {
|
||||
'createdAt': testGroup.createdAt.toIso8601String(),
|
||||
},
|
||||
],
|
||||
'teams': [
|
||||
{
|
||||
'id': testTeam.id,
|
||||
'name': testTeam.name,
|
||||
'memberIds': [testPlayer1.id, testPlayer2.id],
|
||||
'createdAt': testTeam.createdAt.toIso8601String(),
|
||||
},
|
||||
],
|
||||
'matches': [
|
||||
{
|
||||
'id': testMatch.id,
|
||||
|
||||
Reference in New Issue
Block a user