Neuer Regelsatz: Platzierung #213
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttericon/rpg_awesome_icons.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/data/models/player.dart';
|
||||||
@@ -18,6 +19,8 @@ String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
|||||||
return loc.single_loser;
|
return loc.single_loser;
|
||||||
case Ruleset.multipleWinners:
|
case Ruleset.multipleWinners:
|
||||||
return loc.multiple_winners;
|
return loc.multiple_winners;
|
||||||
|
case Ruleset.placement:
|
||||||
|
return loc.placement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +82,8 @@ IconData getRulesetIcon(Ruleset ruleset) {
|
|||||||
return Icons.sentiment_dissatisfied;
|
return Icons.sentiment_dissatisfied;
|
||||||
case Ruleset.multipleWinners:
|
case Ruleset.multipleWinners:
|
||||||
return Icons.group;
|
return Icons.group;
|
||||||
|
case Ruleset.placement:
|
||||||
|
return RpgAwesome.podium;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,14 @@ enum ExportResult { success, canceled, unknownException }
|
|||||||
/// - [Ruleset.singleWinner]: The match is won by a single player.
|
/// - [Ruleset.singleWinner]: The match is won by a single player.
|
||||||
/// - [Ruleset.singleLoser]: The match has a single loser.
|
/// - [Ruleset.singleLoser]: The match has a single loser.
|
||||||
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
||||||
|
/// - [Ruleset.placement]: The player with the highest placement wins.
|
||||||
enum Ruleset {
|
enum Ruleset {
|
||||||
highestScore,
|
highestScore,
|
||||||
lowestScore,
|
lowestScore,
|
||||||
singleWinner,
|
singleWinner,
|
||||||
singleLoser,
|
singleLoser,
|
||||||
multipleWinners,
|
multipleWinners,
|
||||||
|
placement,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different colors available for games
|
/// Different colors available for games
|
||||||
|
|||||||
@@ -353,4 +353,19 @@ class ScoreEntryDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return await deleteAllScoresForMatch(matchId: matchId);
|
return await deleteAllScoresForMatch(matchId: matchId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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({
|
||||||
|
required String matchId,
|
||||||
|
required List<Player> players,
|
||||||
|
}) async {
|
||||||
|
for (int i = 0; i < players.length; i++) {
|
||||||
|
await db.scoreEntryDao.addScore(
|
||||||
|
matchId: matchId,
|
||||||
|
playerId: players[i].id,
|
||||||
|
entry: ScoreEntry(roundNumber: 0, score: players.length - i, change: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ class Match {
|
|||||||
|
|
||||||
case Ruleset.multipleWinners:
|
case Ruleset.multipleWinners:
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
|
case Ruleset.placement:
|
||||||
|
return _getPlayersWithHighestScore().take(1).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
},
|
},
|
||||||
"delete_group": "Gruppe löschen",
|
"delete_group": "Gruppe löschen",
|
||||||
"delete_match": "Spiel löschen",
|
"delete_match": "Spiel löschen",
|
||||||
|
"drag_to_set_placement": "Ziehen um Platzierung zu setzen",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"edit_game": "Spielvorlage bearbeiten",
|
"edit_game": "Spielvorlage bearbeiten",
|
||||||
"edit_group": "Gruppe bearbeiten",
|
"edit_group": "Gruppe bearbeiten",
|
||||||
@@ -97,6 +98,8 @@
|
|||||||
"none": "Kein",
|
"none": "Kein",
|
||||||
"none_group": "Keine",
|
"none_group": "Keine",
|
||||||
"not_available": "Nicht verfügbar",
|
"not_available": "Nicht verfügbar",
|
||||||
|
"placement": "Platzierung",
|
||||||
|
"place": "Platz",
|
||||||
"played_matches": "Gespielte Spiele",
|
"played_matches": "Gespielte Spiele",
|
||||||
"player_name": "Spieler:innenname",
|
"player_name": "Spieler:innenname",
|
||||||
"players": "Spieler:innen",
|
"players": "Spieler:innen",
|
||||||
@@ -110,6 +113,7 @@
|
|||||||
"ruleset": "Regelwerk",
|
"ruleset": "Regelwerk",
|
||||||
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.",
|
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.",
|
||||||
"ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",
|
"ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",
|
||||||
|
"ruleset_placement": "Spieler:innen können in einer Reihenfolge angeordnet werden, die ihre Platzierung reflektiert.",
|
||||||
"ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.",
|
"ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.",
|
||||||
"ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.",
|
"ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.",
|
||||||
"save_changes": "Änderungen speichern",
|
"save_changes": "Änderungen speichern",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
},
|
},
|
||||||
"delete_group": "Delete Group",
|
"delete_group": "Delete Group",
|
||||||
"delete_match": "Delete Match",
|
"delete_match": "Delete Match",
|
||||||
|
"drag_to_set_placement": "Drag to set placement",
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"edit_game": "Edit Game",
|
"edit_game": "Edit Game",
|
||||||
"edit_group": "Edit Group",
|
"edit_group": "Edit Group",
|
||||||
@@ -98,6 +99,8 @@
|
|||||||
"none": "None",
|
"none": "None",
|
||||||
"none_group": "None",
|
"none_group": "None",
|
||||||
"not_available": "Not available",
|
"not_available": "Not available",
|
||||||
|
"placement": "Placement",
|
||||||
|
"place": "place",
|
||||||
"played_matches": "Played Matches",
|
"played_matches": "Played Matches",
|
||||||
"player_name": "Player name",
|
"player_name": "Player name",
|
||||||
"players": "Players",
|
"players": "Players",
|
||||||
@@ -110,6 +113,7 @@
|
|||||||
"ruleset": "Ruleset",
|
"ruleset": "Ruleset",
|
||||||
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
|
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
|
||||||
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.",
|
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.",
|
||||||
|
"ruleset_placement": "Players can be arranged in an order, which reflects their placement.",
|
||||||
"ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.",
|
"ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.",
|
||||||
"ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.",
|
"ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.",
|
||||||
"save_changes": "Save Changes",
|
"save_changes": "Save Changes",
|
||||||
|
|||||||
@@ -320,6 +320,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Delete Match'**
|
/// **'Delete Match'**
|
||||||
String get delete_match;
|
String get delete_match;
|
||||||
|
|
||||||
|
/// No description provided for @drag_to_set_placement.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Drag to set placement'**
|
||||||
|
String get drag_to_set_placement;
|
||||||
|
|
||||||
/// No description provided for @description.
|
/// No description provided for @description.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -638,6 +644,18 @@ abstract class AppLocalizations {
|
|||||||
/// **'Not available'**
|
/// **'Not available'**
|
||||||
String get not_available;
|
String get not_available;
|
||||||
|
|
||||||
|
/// No description provided for @placement.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Placement'**
|
||||||
|
String get placement;
|
||||||
|
|
||||||
|
/// No description provided for @place.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'place'**
|
||||||
|
String get place;
|
||||||
|
|
||||||
/// No description provided for @played_matches.
|
/// No description provided for @played_matches.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -710,6 +728,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Traditional ruleset: the player with the most points wins.'**
|
/// **'Traditional ruleset: the player with the most points wins.'**
|
||||||
String get ruleset_most_points;
|
String get ruleset_most_points;
|
||||||
|
|
||||||
|
/// No description provided for @ruleset_placement.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Players can be arranged in an order, which reflects their placement.'**
|
||||||
|
String get ruleset_placement;
|
||||||
|
|
||||||
/// No description provided for @ruleset_single_loser.
|
/// No description provided for @ruleset_single_loser.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get delete_match => 'Spiel löschen';
|
String get delete_match => 'Spiel löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get drag_to_set_placement => 'Ziehen um Platzierung zu setzen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get description => 'Beschreibung';
|
String get description => 'Beschreibung';
|
||||||
|
|
||||||
@@ -295,6 +298,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get not_available => 'Nicht verfügbar';
|
String get not_available => 'Nicht verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get placement => 'Platzierung';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get place => 'Platz';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get played_matches => 'Gespielte Spiele';
|
String get played_matches => 'Gespielte Spiele';
|
||||||
|
|
||||||
@@ -333,6 +342,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get ruleset_most_points =>
|
String get ruleset_most_points =>
|
||||||
'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.';
|
'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_placement =>
|
||||||
|
'Spieler:innen können in einer Reihenfolge angeordnet werden, die ihre Platzierung reflektiert.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ruleset_single_loser =>
|
String get ruleset_single_loser =>
|
||||||
'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.';
|
'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.';
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get delete_match => 'Delete Match';
|
String get delete_match => 'Delete Match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get drag_to_set_placement => 'Drag to set placement';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get description => 'Description';
|
String get description => 'Description';
|
||||||
|
|
||||||
@@ -295,6 +298,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get not_available => 'Not available';
|
String get not_available => 'Not available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get placement => 'Placement';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get place => 'place';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get played_matches => 'Played Matches';
|
String get played_matches => 'Played Matches';
|
||||||
|
|
||||||
@@ -333,6 +342,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get ruleset_most_points =>
|
String get ruleset_most_points =>
|
||||||
'Traditional ruleset: the player with the most points wins.';
|
'Traditional ruleset: the player with the most points wins.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_placement =>
|
||||||
|
'Players can be arranged in an order, which reflects their placement.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ruleset_single_loser =>
|
String get ruleset_single_loser =>
|
||||||
'Exactly one loser is determined; last place receives the penalty or consequence.';
|
'Exactly one loser is determined; last place receives the penalty or consequence.';
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
children: getSingleResultRow(loc),
|
children: getSingleResultRow(loc),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return getScoreResultWidget(loc);
|
return getMultiResultRows(loc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,52 +325,113 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the result widget for scores
|
/// Returns the result widget for scores or placement
|
||||||
Widget getScoreResultWidget(AppLocalizations loc) {
|
Widget getMultiResultRows(AppLocalizations loc) {
|
||||||
List<(String, int)> playerScores = [];
|
List<(String, int)> playerScores = [];
|
||||||
for (var player in match.players) {
|
for (var player in match.players) {
|
||||||
int score = match.scores[player.id]?.score ?? 0;
|
int score = match.scores[player.id]?.score ?? 0;
|
||||||
playerScores.add((player.name, score));
|
playerScores.add((player.name, score));
|
||||||
}
|
}
|
||||||
if (widget.match.game.ruleset == Ruleset.highestScore) {
|
|
||||||
|
final ruleset = match.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.highestScore || ruleset == Ruleset.placement) {
|
||||||
playerScores.sort((a, b) => b.$2.compareTo(a.$2));
|
playerScores.sort((a, b) => b.$2.compareTo(a.$2));
|
||||||
} else if (widget.match.game.ruleset == Ruleset.lowestScore) {
|
} else if (ruleset == Ruleset.lowestScore) {
|
||||||
playerScores.sort((a, b) => a.$2.compareTo(b.$2));
|
playerScores.sort((a, b) => a.$2.compareTo(b.$2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
for (var score in playerScores)
|
for (var i = 0; i < playerScores.length; i++)
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
score.$1,
|
playerScores[i].$1,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: CustomTheme.textColor,
|
color: CustomTheme.textColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
getResultValueText(loc, i, playerScores[i].$2),
|
||||||
getPointLabel(loc, score.$2),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: CustomTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getResultValueText(AppLocalizations loc, int index, int score) {
|
||||||
|
final ruleset = match.game.ruleset;
|
||||||
|
|
||||||
|
if (ruleset == Ruleset.placement) {
|
||||||
|
return Text(
|
||||||
|
getPlacementText(context, index + 1),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: getPlacementTextcolor(index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text(
|
||||||
|
getPointLabel(loc, score),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getPlacementTextcolor(int placement) {
|
||||||
|
switch (placement) {
|
||||||
|
case 0:
|
||||||
|
return const Color(0xFFFFBF00);
|
||||||
|
case 1:
|
||||||
|
return const Color(0xBBFFFFFF);
|
||||||
|
case 2:
|
||||||
|
return const Color(0xFFCD7F32);
|
||||||
|
default:
|
||||||
|
return CustomTheme.textColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns if the result can be displayed in a single row
|
// Returns if the result can be displayed in a single row
|
||||||
bool isSingleRowResult() {
|
bool isSingleRowResult() {
|
||||||
return match.game.ruleset == Ruleset.singleWinner ||
|
return match.game.ruleset == Ruleset.singleWinner ||
|
||||||
match.game.ruleset == Ruleset.singleLoser;
|
match.game.ruleset == Ruleset.singleLoser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getPlacementText(BuildContext context, int rank) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
final locale = Localizations.localeOf(context).languageCode;
|
||||||
|
|
||||||
|
if (locale == 'de') {
|
||||||
|
return '$rank. ${loc.place}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '${_ordinalEn(rank)} ${loc.place}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _ordinalEn(int number) {
|
||||||
|
if (number % 100 >= 11 && number % 100 <= 13) {
|
||||||
|
return '${number}th';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (number % 10) {
|
||||||
|
case 1:
|
||||||
|
return '${number}st';
|
||||||
|
case 2:
|
||||||
|
return '${number}nd';
|
||||||
|
case 3:
|
||||||
|
return '${number}rd';
|
||||||
|
default:
|
||||||
|
return '${number}th';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void updateScoresForCurrentMatch() {
|
void updateScoresForCurrentMatch() {
|
||||||
db.scoreEntryDao
|
db.scoreEntryDao
|
||||||
.getAllMatchScores(matchId: match.id)
|
.getAllMatchScores(matchId: match.id)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
|||||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_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/live_edit_list_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/match_result_view/score_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/match_result_view/score_list_tile.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/tiles/text_icon_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
|
||||||
@@ -50,7 +51,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
ruleset = Ruleset.highestScore; //widget.match.game.ruleset;
|
ruleset = widget.match.game.ruleset;
|
||||||
canSave = !rulesetSupportsScoreEntry();
|
canSave = !rulesetSupportsScoreEntry();
|
||||||
|
|
||||||
allPlayers = widget.match.players;
|
allPlayers = widget.match.players;
|
||||||
@@ -73,6 +74,12 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
final score = scoreList?.score ?? 0;
|
final score = scoreList?.score ?? 0;
|
||||||
controller[i].text = score.toString();
|
controller[i].text = score.toString();
|
||||||
}
|
}
|
||||||
|
} else if (rulesetSupportsPlacement()) {
|
||||||
|
allPlayers.sort((a, b) {
|
||||||
|
final scoreA = widget.match.scores[a.id]?.score ?? 0;
|
||||||
|
final scoreB = widget.match.scores[b.id]?.score ?? 0;
|
||||||
|
return scoreB.compareTo(scoreA);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@@ -106,7 +113,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: isLiveEditMode && rulesetSupportsScoreEntry()
|
child: isLiveEditMode
|
||||||
// Live Edit Mode
|
// Live Edit Mode
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
itemCount: allPlayers.length,
|
itemCount: allPlayers.length,
|
||||||
@@ -122,7 +129,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// Normal Mode
|
// Normal Container
|
||||||
: Container(
|
: Container(
|
||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
horizontal: 12,
|
horizontal: 12,
|
||||||
@@ -161,6 +168,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
itemCount: allPlayers.length,
|
itemCount: allPlayers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return CustomRadioListTile(
|
return CustomRadioListTile(
|
||||||
@@ -183,6 +191,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Show score entry
|
// Show score entry
|
||||||
if (rulesetSupportsScoreEntry())
|
if (rulesetSupportsScoreEntry())
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -205,6 +214,111 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Show draggable placement list
|
||||||
|
if (rulesetSupportsPlacement())
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Placement indicators
|
||||||
|
Padding(
|
||||||
|
flixcoo marked this conversation as resolved
Outdated
|
|||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (
|
||||||
|
int i = 0;
|
||||||
|
i < allPlayers.length;
|
||||||
|
i++
|
||||||
|
)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 60,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
CustomTheme.boxBorderColor,
|
||||||
|
borderRadius: CustomTheme
|
||||||
|
.standardBorderRadiusAll,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
child: Text(
|
||||||
|
' #${i + 1} ',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Drag list
|
||||||
|
Expanded(
|
||||||
|
child: ReorderableListView.builder(
|
||||||
|
physics:
|
||||||
|
const NeverScrollableScrollPhysics(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
proxyDecorator: (child, index, animation) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: animation,
|
||||||
|
child: child,
|
||||||
|
builder: (context, child) {
|
||||||
|
final alpha =
|
||||||
|
(Curves.easeInOut.transform(
|
||||||
|
animation.value,
|
||||||
|
) *
|
||||||
|
40)
|
||||||
|
.toInt();
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
child!,
|
||||||
|
Positioned.fill(
|
||||||
|
left: 4,
|
||||||
|
top: 4,
|
||||||
|
right: 4,
|
||||||
|
bottom: 4,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white
|
||||||
|
.withAlpha(alpha),
|
||||||
|
borderRadius: CustomTheme
|
||||||
|
.standardBorderRadiusAll,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final Player item = allPlayers
|
||||||
|
.removeAt(oldIndex);
|
||||||
|
allPlayers.insert(newIndex, item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return TextIconListTile(
|
||||||
|
key: ValueKey(allPlayers[index].id),
|
||||||
|
text: allPlayers[index].name,
|
||||||
|
icon: Icons.drag_handle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -266,6 +380,8 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
} else if (ruleset == Ruleset.lowestScore ||
|
} else if (ruleset == Ruleset.lowestScore ||
|
||||||
ruleset == Ruleset.highestScore) {
|
ruleset == Ruleset.highestScore) {
|
||||||
await _handleScores();
|
await _handleScores();
|
||||||
|
} else if (ruleset == Ruleset.placement) {
|
||||||
|
await _handlePlacement();
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.onWinnerChanged?.call();
|
widget.onWinnerChanged?.call();
|
||||||
@@ -311,12 +427,22 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles saving the placement for each player in the database.
|
||||||
|
Future<void> _handlePlacement() async {
|
||||||
|
await db.scoreEntryDao.setPlacements(
|
||||||
|
matchId: widget.match.id,
|
||||||
|
players: allPlayers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String getTitleForRuleset(AppLocalizations loc) {
|
String getTitleForRuleset(AppLocalizations loc) {
|
||||||
switch (ruleset) {
|
switch (ruleset) {
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
return loc.select_winner;
|
return loc.select_winner;
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return loc.select_loser;
|
return loc.select_loser;
|
||||||
|
case Ruleset.placement:
|
||||||
|
return loc.drag_to_set_placement;
|
||||||
default:
|
default:
|
||||||
return loc.enter_points;
|
return loc.enter_points;
|
||||||
}
|
}
|
||||||
@@ -329,4 +455,8 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
bool rulesetSupportsScoreEntry() {
|
bool rulesetSupportsScoreEntry() {
|
||||||
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
|
return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rulesetSupportsPlacement() {
|
||||||
|
return ruleset == Ruleset.placement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
return TextIconListTile(
|
return TextIconListTile(
|
||||||
text: suggestedPlayers[index].name,
|
text: suggestedPlayers[index].name,
|
||||||
suffixText: getNameCountText(suggestedPlayers[index]),
|
suffixText: getNameCountText(suggestedPlayers[index]),
|
||||||
|
icon: Icons.add,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
// If the player is not already selected
|
// If the player is not already selected
|
||||||
|
|||||||
@@ -261,30 +261,29 @@ class _MatchTileState extends State<MatchTile> {
|
|||||||
final mvp = widget.match.mvp;
|
final mvp = widget.match.mvp;
|
||||||
final mvpScore = widget.match.scores[mvp.first.id]?.score ?? 0;
|
final mvpScore = widget.match.scores[mvp.first.id]?.score ?? 0;
|
||||||
final mvpNames = mvp.map((player) => player.name).join(', ');
|
final mvpNames = mvp.map((player) => player.name).join(', ');
|
||||||
|
|
||||||
return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})';
|
return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})';
|
||||||
|
} else if (ruleset == Ruleset.placement) {
|
||||||
|
return '${loc.winner}: ${widget.match.mvp.first.name}';
|
||||||
}
|
}
|
||||||
return '${loc.winner}: n.A.';
|
return '${loc.winner}: n.A.';
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon getMvpIcon() {
|
Icon getMvpIcon() {
|
||||||
const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
final icon = getRulesetIcon(widget.match.game.ruleset);
|
||||||
|
|
||||||
switch (widget.match.game.ruleset) {
|
switch (widget.match.game.ruleset) {
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
return const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
return Icon(icon, size: 20, color: Colors.amber);
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return const Icon(
|
return Icon(icon, size: 20, color: Colors.blue);
|
||||||
Icons.sentiment_dissatisfied_outlined,
|
|
||||||
size: 20,
|
|
||||||
color: Colors.blue,
|
|
||||||
);
|
|
||||||
case Ruleset.lowestScore:
|
case Ruleset.lowestScore:
|
||||||
return const Icon(Icons.arrow_downward, size: 20, color: Colors.orange);
|
return Icon(icon, size: 20, color: Colors.orange);
|
||||||
case Ruleset.highestScore:
|
case Ruleset.highestScore:
|
||||||
return const Icon(Icons.arrow_upward, size: 20, color: Colors.green);
|
return Icon(icon, size: 20, color: Colors.green);
|
||||||
default:
|
case Ruleset.multipleWinners:
|
||||||
return const Icon(Icons.emoji_events, size: 20, color: Colors.amber);
|
return Icon(icon, size: 20, color: Colors.amber);
|
||||||
|
case Ruleset.placement:
|
||||||
|
return Icon(icon, size: 20, color: Colors.deepOrangeAccent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.text,
|
required this.text,
|
||||||
this.suffixText = '',
|
this.suffixText = '',
|
||||||
this.iconEnabled = true,
|
this.icon,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
/// An optional suffix text to display after the main text.
|
/// An optional suffix text to display after the main text.
|
||||||
final String suffixText;
|
final String suffixText;
|
||||||
|
|
||||||
/// A boolean to determine if the icon should be displayed.
|
/// The icon to display in the tile.
|
||||||
final bool iconEnabled;
|
final IconData? icon;
|
||||||
|
|
||||||
/// The callback to be invoked when the icon is pressed.
|
/// The callback to be invoked when the icon is pressed.
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
@@ -64,11 +64,8 @@ class TextIconListTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (iconEnabled)
|
if (icon != null)
|
||||||
GestureDetector(
|
GestureDetector(onTap: onPressed, child: Icon(icon, size: 20)),
|
||||||
onTap: onPressed,
|
|
||||||
child: const Icon(Icons.add, size: 20),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user
Gerne hier noch ein kleines Highlighting implementieren, wenn ein Tile gedraggt wird
so nen highlighting?
Das mag ich garnicht. Lieber was simples, z.B. BG color etwas heller, ne weiße border oder so
dann schau mal selber ich habe wirklich keine ahnung