diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index b7c55fc..679fcc1 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -26,6 +26,7 @@ "create_new_group": "Neue Gruppe erstellen", "create_new_match": "Neues Spiel erstellen", "created_on": "Erstellt am", + "create_teams": "Teams erstellen", "data": "Daten", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", @@ -49,6 +50,7 @@ "edit_game": "Spielvorlage bearbeiten", "edit_group": "Gruppe bearbeiten", "edit_match": "Gruppe bearbeiten", + "edit_members": "Mitglieder bearbeiten", "enter_points": "Punkte eingeben", "enter_results": "Ergebnisse eintragen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", @@ -108,6 +110,7 @@ "privacy_policy": "Datenschutzerklärung", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", + "redistribute": "Neu verteilen", "result": "Ergebnis", "results": "Ergebnisse", "ruleset": "Regelwerk", @@ -133,6 +136,7 @@ "statistics": "Statistiken", "stats": "Statistiken", "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", + "team": "Team", "teams": "Teams", "there_are_no_games_matching_your_search": "Es gibt keine Spielvorlagen, die deiner Suche entspricht", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index be5efa6..b41a6f7 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -27,6 +27,7 @@ "create_new_group": "Create new group", "created_on": "Created on", "create_new_match": "Create new match", + "create_teams": "Create teams", "data": "Data", "data_successfully_deleted": "Data successfully deleted", "data_successfully_exported": "Data successfully exported", @@ -50,6 +51,7 @@ "edit_game": "Edit Game", "edit_group": "Edit Group", "edit_match": "Edit Match", + "edit_members": "Edit Members", "enter_points": "Enter points", "enter_results": "Enter Results", "error_creating_group": "Error while creating group, please try again", @@ -109,6 +111,7 @@ "privacy_policy": "Privacy Policy", "quick_create": "Quick Create", "recent_matches": "Recent Matches", + "redistribute": "Redistribute", "results": "Results", "ruleset": "Ruleset", "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", @@ -142,6 +145,7 @@ } } }, + "team": "Team", "teams": "Teams", "there_are_no_games_matching_your_search": "There are no games matching your search", "there_is_no_group_matching_your_search": "There is no group matching your search", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 3f7883d..4ce2c74 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -254,6 +254,12 @@ abstract class AppLocalizations { /// **'Create new match'** String get create_new_match; + /// No description provided for @create_teams. + /// + /// In en, this message translates to: + /// **'Create teams'** + String get create_teams; + /// No description provided for @data. /// /// In en, this message translates to: @@ -350,6 +356,12 @@ abstract class AppLocalizations { /// **'Edit Match'** String get edit_match; + /// No description provided for @edit_members. + /// + /// In en, this message translates to: + /// **'Edit Members'** + String get edit_members; + /// No description provided for @enter_points. /// /// In en, this message translates to: @@ -704,6 +716,12 @@ abstract class AppLocalizations { /// **'Recent Matches'** String get recent_matches; + /// No description provided for @redistribute. + /// + /// In en, this message translates to: + /// **'Redistribute'** + String get redistribute; + /// No description provided for @results. /// /// In en, this message translates to: @@ -848,6 +866,12 @@ abstract class AppLocalizations { /// **'Successfully added player {playerName}'** String successfully_added_player(String playerName); + /// No description provided for @team. + /// + /// In en, this message translates to: + /// **'Team'** + String get team; + /// No description provided for @teams. /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index dfbe9f1..c634f55 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -88,6 +88,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_new_match => 'Neues Spiel erstellen'; + @override + String get create_teams => 'Teams erstellen'; + @override String get data => 'Daten'; @@ -146,6 +149,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get edit_match => 'Gruppe bearbeiten'; + @override + String get edit_members => 'Mitglieder bearbeiten'; + @override String get enter_points => 'Punkte eingeben'; @@ -328,6 +334,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get recent_matches => 'Letzte Spiele'; + @override + String get redistribute => 'Neu verteilen'; + @override String get results => 'Ergebnisse'; @@ -407,6 +416,9 @@ class AppLocalizationsDe extends AppLocalizations { return 'Spieler:in $playerName erfolgreich hinzugefügt'; } + @override + String get team => 'Team'; + @override String get teams => 'Teams'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 93487b3..8e79c77 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -88,6 +88,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_new_match => 'Create new match'; + @override + String get create_teams => 'Create teams'; + @override String get data => 'Data'; @@ -146,6 +149,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get edit_match => 'Edit Match'; + @override + String get edit_members => 'Edit Members'; + @override String get enter_points => 'Enter points'; @@ -328,6 +334,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get recent_matches => 'Recent Matches'; + @override + String get redistribute => 'Redistribute'; + @override String get results => 'Results'; @@ -407,6 +416,9 @@ class AppLocalizationsEn extends AppLocalizations { return 'Successfully added player $playerName'; } + @override + String get team => 'Team'; + @override String get teams => 'Teams'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 3f4fe36..dbfba47 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -12,7 +12,7 @@ import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; -import 'package:tallee/presentation/views/main_menu/match_view/create_match/organize_teams_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/create_match/team_match/organize_teams_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; import 'package:tallee/presentation/widgets/player_selection.dart'; @@ -296,7 +296,7 @@ class _CreateMatchViewState extends State { if (isTeamMatch) { if (context.mounted) { - Navigator.pushReplacement( + Navigator.push( context, adaptivePageRoute( fullscreenDialog: !isTeamMatch, @@ -385,7 +385,9 @@ class _CreateMatchViewState extends State { isTeamMatch: isTeamMatch, game: selectedGame!, ); - await db.matchDao.addMatch(match: match); + + // Team matches are saved in OrganizeTeamsView + if (!isTeamMatch) await db.matchDao.addMatch(match: match); return match; } } diff --git a/lib/presentation/views/main_menu/match_view/create_match/organize_teams_view.dart b/lib/presentation/views/main_menu/match_view/create_match/organize_teams_view.dart deleted file mode 100644 index b9def83..0000000 --- a/lib/presentation/views/main_menu/match_view/create_match/organize_teams_view.dart +++ /dev/null @@ -1,263 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:provider/provider.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/enums.dart'; -import 'package:tallee/data/db/database.dart'; -import 'package:tallee/data/models/match.dart'; -import 'package:tallee/data/models/player.dart'; -import 'package:tallee/data/models/team.dart'; -import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; -import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; -import 'package:tallee/presentation/widgets/tiles/team_creation_tile.dart'; - -class OrganizeTeamsView extends StatefulWidget { - const OrganizeTeamsView({super.key, required this.match}); - - final Match match; - - @override - State createState() => _OrganizeTeamsViewState(); -} - -class _OrganizeTeamsViewState extends State { - final Random _random = Random(); - late final List<_TeamDraft> _teams; - - List get _players => widget.match.players; - - @override - void initState() { - super.initState(); - _teams = List.generate(2, _createTeamDraft); - _redistributePlayers(); - } - - @override - void dispose() { - for (final team in _teams) { - team.dispose(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar(title: const Text('Organize Teams')), - body: SafeArea( - child: Column( - children: [ - Expanded( - child: ListView.builder( - padding: const EdgeInsets.only(top: 12, bottom: 12), - itemCount: _teams.length, - itemBuilder: (context, index) { - return TeamCreationTile( - color: _teams[index].color, - controller: _teams[index].nameController, - players: _teams[index].members, - hintText: 'Team ${index + 1}', - onDelete: () => _removeTeam(index), - onColorSelection: (color) { - setState(() { - _teams[index].color = color; - }); - }, - onPlayerTap: (player) => - _showMovePlayerSheet(player, index), - ); - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - MainMenuButton( - icon: Icons.cached, - onPressed: () => setState(() { - _redistributePlayers(); - }), - ), - const SizedBox(width: 15), - MainMenuButton( - text: 'Add team', - icon: Icons.emoji_events, - onPressed: _teams.length >= widget.match.players.length - ? null - : _addTeam, - ), - const SizedBox(width: 15), - MainMenuButton( - icon: Icons.check, - onPressed: () async { - final match = await createMatchWithTeams(); - if (context.mounted) { - Navigator.pushReplacement( - context, - adaptivePageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView(match: match), - ), - ); - } - }, - ), - ], - ), - ], - ), - ), - ); - } - - Future createMatchWithTeams() async { - final teams = _teams - .map( - (team) => Team( - name: team.nameController.text.trim().isNotEmpty - ? team.nameController.text.trim() - : 'Team ${_teams.indexOf(team) + 1}', - color: team.color, - members: team.members, - ), - ) - .toList(); - final db = Provider.of(context, listen: false); - await db.teamDao.addTeamsAsList(teams: teams, matchId: widget.match.id); - return widget.match.copyWith(teams: teams); - } - - _TeamDraft _createTeamDraft(int index) { - return _TeamDraft( - nameController: TextEditingController(text: 'Team ${index + 1}'), - color: getTeamColor(index), - ); - } - - void _addTeam() { - setState(() { - _teams.add(_createTeamDraft(_teams.length)); - _redistributePlayers(); - }); - } - - void _removeTeam(int index) { - setState(() { - final removedTeam = _teams.removeAt(index); - removedTeam.dispose(); - - if (_teams.isEmpty) { - _teams.add(_createTeamDraft(0)); - } - - _redistributePlayers(); - }); - } - - void _movePlayer(Player player, int fromTeamIndex, int toTeamIndex) { - setState(() { - _teams[fromTeamIndex].members.remove(player); - _teams[toTeamIndex].members.add(player); - }); - } - - void _showMovePlayerSheet(Player player, int fromTeamIndex) { - final otherTeams = [ - for (int i = 0; i < _teams.length; i++) - if (i != fromTeamIndex) (index: i, team: _teams[i]), - ]; - - if (otherTeams.isEmpty) return; - - showModalBottomSheet( - context: context, - backgroundColor: CustomTheme.backgroundColor, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), - builder: (context) { - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Text( - '${player.name} verschieben in …', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CustomTheme.textColor, - ), - ), - ), - const Divider(), - ...otherTeams.map((entry) { - final teamName = - entry.team.nameController.text.trim().isNotEmpty - ? entry.team.nameController.text.trim() - : 'Team ${entry.index + 1}'; - final teamColor = getColorFromGameColor(entry.team.color); - return ListTile( - leading: CircleAvatar( - radius: 12, - backgroundColor: teamColor, - ), - title: Text( - teamName, - style: const TextStyle(color: CustomTheme.textColor), - ), - onTap: () { - Navigator.pop(context); - _movePlayer(player, fromTeamIndex, entry.index); - }, - ); - }), - ], - ), - ), - ); - }, - ); - } - - void _redistributePlayers() { - for (final team in _teams) { - team.members.clear(); - } - - if (_players.isEmpty || _teams.isEmpty) { - return; - } - - final shuffledPlayers = [..._players]..shuffle(_random); - - for (int i = 0; i < shuffledPlayers.length; i++) { - final teamIndex = i % _teams.length; - _teams[teamIndex].members.add(shuffledPlayers[i]); - } - } -} - -class _TeamDraft { - _TeamDraft({required this.nameController, required this.color}); - - final TextEditingController nameController; - GameColor color; - final List members = []; - - void dispose() { - nameController.dispose(); - } -} diff --git a/lib/presentation/views/main_menu/match_view/create_match/team_match/edit_members_view.dart b/lib/presentation/views/main_menu/match_view/create_match/team_match/edit_members_view.dart new file mode 100644 index 0000000..12071be --- /dev/null +++ b/lib/presentation/views/main_menu/match_view/create_match/team_match/edit_members_view.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; +import 'package:tallee/presentation/widgets/player_selection.dart'; + +class EditMembersView extends StatefulWidget { + const EditMembersView({ + super.key, + required this.matchPlayer, + required this.teamMember, + }); + + final List matchPlayer; + + final List teamMember; + + @override + State createState() => _EditMembersViewState(); +} + +class _EditMembersViewState extends State { + List selectedPlayers = []; + List matchPlayer = []; + + @override + void initState() { + selectedPlayers = [...widget.teamMember]; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + appBar: AppBar( + title: Text(loc.edit_members), + leading: HapticIconButton( + onPressed: selectedPlayers.isNotEmpty + ? () => Navigator.pop(context, selectedPlayers) + : null, + icon: const Icon(Icons.arrow_back_ios_new_outlined), + ), + ), + body: PlayerSelection( + initialSelectedPlayers: widget.teamMember, + availablePlayers: widget.matchPlayer, + onChanged: (List newSelectedPlayers) { + setState(() { + selectedPlayers = newSelectedPlayers; + }); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/match_view/create_match/team_match/organize_teams_view.dart b/lib/presentation/views/main_menu/match_view/create_match/team_match/organize_teams_view.dart new file mode 100644 index 0000000..47f794c --- /dev/null +++ b/lib/presentation/views/main_menu/match_view/create_match/team_match/organize_teams_view.dart @@ -0,0 +1,272 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.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/data/db/database.dart'; +import 'package:tallee/data/models/match.dart'; +import 'package:tallee/data/models/player.dart'; +import 'package:tallee/data/models/team.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/create_match/team_match/edit_members_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/tiles/team_creation_tile.dart'; + +class OrganizeTeamsView extends StatefulWidget { + const OrganizeTeamsView({super.key, required this.match}); + + final Match match; + + @override + State createState() => _OrganizeTeamsViewState(); +} + +class _OrganizeTeamsViewState extends State { + final Random random = Random(); + late List teams; + late List nameController; + + final int initialTeamCount = 2; + List get matchPlayers => widget.match.players; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final loc = AppLocalizations.of(context); + + // Init the teams + teams = List.generate( + initialTeamCount, + (index) => Team( + name: '${loc.team} ${index + 1}', + color: getTeamColor(index), + members: [], + ), + ); + + // Init the controllers + nameController = teams.map(getNewController).toList(); + redistributePlayers(); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar(title: Text(loc.create_teams)), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(top: 12, bottom: 12), + itemCount: teams.length, + itemBuilder: (context, index) { + return TeamCreationTile( + color: teams[index].color, + controller: nameController[index], + players: teams[index].members, + hintText: '${loc.team} ${index + 1}', + onEdit: () async { + final newPlayers = await Navigator.push( + context, + adaptivePageRoute( + fullscreenDialog: true, + builder: (context) => EditMembersView( + matchPlayer: widget.match.players, + teamMember: teams[index].members, + ), + ), + ); + + setState(() { + // Remove the selected players from every team + for (final player in newPlayers) { + for (final team in teams) { + if (team.members.contains(player)) { + team.members.remove(player); + } + } + } + + // Add the selected players to the current team + teams[index] = teams[index].copyWith( + members: newPlayers, + ); + }); + }, + onDelete: teams.length >= 3 + ? () => _removeTeam(index) + : null, + onColorSelection: (color) { + setState(() { + teams[index] = teams[index].copyWith(color: color); + }); + }, + ); + }, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MainMenuButton( + icon: Icons.cached, + text: loc.redistribute, + onPressed: () => setState(() { + redistributePlayers(); + }), + ), + const SizedBox(width: 15), + MainMenuButton( + icon: Icons.add, + onPressed: teams.length >= widget.match.players.length + ? null + : addTeam, + ), + const SizedBox(width: 15), + MainMenuButton( + icon: Icons.check, + onPressed: teams.every((team) => team.members.isNotEmpty) + ? () async { + final match = await createMatchWithTeams(); + if (context.mounted) { + Navigator.pushAndRemoveUntil( + context, + adaptivePageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: match), + ), + (route) => route.isFirst, + ); + } + } + : null, + ), + ], + ), + ], + ), + ), + ); + } + + /// Adds a new team to the list of teams, creates a corresponding controller, + /// and redistributes the players among all teams. + void addTeam() { + setState(() { + final newTeam = getNewTeam(); + teams.add(newTeam); + nameController.add(getNewController(newTeam)); + redistributePlayers(); + }); + } + + /// Creates a new team with a default name and color based on the current number + Team getNewTeam() { + final loc = AppLocalizations.of(context); + return Team( + name: '${loc.team} ${teams.length + 1}', + color: getTeamColor(teams.length), + members: [], + ); + } + + /// Builds a [TextEditingController] for the given team and sets up a listener + /// to update the team's name whenever the text changes. + TextEditingController getNewController(Team team) { + final textController = TextEditingController(text: team.name); + textController.addListener(() { + final index = teams.indexWhere((t) => t.id == team.id); + if (index == -1) return; + teams[index] = teams[index].copyWith(name: textController.text); + }); + return textController; + } + + /// Removes the team with the given index and redistributes its players to the + /// remaining teams. If there are less than 2 teams the removed team gets + /// replaced with a new one + void _removeTeam(int index) { + final loc = AppLocalizations.of(context); + + setState(() { + final removedTeam = teams.removeAt(index); + final removedController = nameController.removeAt(index); + removedController.dispose(); + if (teams.length < 2) { + final fallbackTeam = getNewTeam(); + teams.add(fallbackTeam); + nameController.add(getNewController(fallbackTeam)); + } + + // Update index-based team names + for (int i = 0; i < nameController.length; i++) { + if (nameController[i].text.contains( + RegExp('^${RegExp.escape(loc.team)} \\d+\$'), + )) { + nameController[i].text = '${loc.team} ${i + 1}'; + } + } + + addToSmallestTeams(removedTeam.members); + }); + } + + /// Adds the given players to the teams with the least amount of members + /// [orphanedPlayers] The players to be added to the teams. + void addToSmallestTeams(List orphanedPlayers) { + if (teams.isEmpty || orphanedPlayers.isEmpty) return; + + for (final player in orphanedPlayers) { + var targetIndex = 0; + for (var i = 1; i < teams.length; i++) { + if (teams[i].members.length < teams[targetIndex].members.length) { + targetIndex = i; + } + } + teams[targetIndex].members.add(player); + } + } + + // Iterates through all teams and redistributes players randomly and + // as evenly as possible. + void redistributePlayers() { + for (final team in teams) { + team.members.clear(); + } + + if (matchPlayers.isEmpty || teams.isEmpty) { + return; + } + + final shuffledPlayers = [...matchPlayers]..shuffle(random); + + for (int i = 0; i < shuffledPlayers.length; i++) { + final teamIndex = i % teams.length; + teams[teamIndex].members.add(shuffledPlayers[i]); + } + } + + /// Saves the teams to the database and returns the updated match with the teams. + Future createMatchWithTeams() async { + final db = Provider.of(context, listen: false); + final match = widget.match.copyWith(teams: teams); + await db.matchDao.addMatch(match: match); + return match; + } + + @override + void dispose() { + for (final c in nameController) { + c.dispose(); + } + super.dispose(); + } +} diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 4f9ab34..39cfef8 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -333,6 +333,7 @@ class _MatchResultViewState extends State { } else { return await db.teamDao.setWinnerTeams( matchId: widget.match.id, + winners: _selectedTeams.toList(), ); } diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index a7f60c6..69b7df7 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -142,6 +142,7 @@ class _MatchViewState extends State { /// Loads the matches from the database and sorts them by creation date. void loadMatches() { + print('Loading matches from database'); isLoading = true; Future.wait([ db.matchDao.getAllMatches(), diff --git a/lib/presentation/widgets/tiles/team_creation_tile.dart b/lib/presentation/widgets/tiles/team_creation_tile.dart index fb3e9c5..8a10150 100644 --- a/lib/presentation/widgets/tiles/team_creation_tile.dart +++ b/lib/presentation/widgets/tiles/team_creation_tile.dart @@ -4,6 +4,8 @@ import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/models/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart'; import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; @@ -14,9 +16,9 @@ class TeamCreationTile extends StatefulWidget { required this.controller, required this.players, required this.hintText, + this.onEdit, this.onDelete, this.onColorSelection, - this.onPlayerTap, }); final GameColor color; @@ -27,28 +29,34 @@ class TeamCreationTile extends StatefulWidget { final String hintText; + final VoidCallback? onEdit; + final VoidCallback? onDelete; final ValueChanged? onColorSelection; - final void Function(Player player)? onPlayerTap; - @override State createState() => _TeamCreationTileState(); } class _TeamCreationTileState extends State { + final teamColors = List.generate( + GameColor.values.length, + (index) => getTeamColor(index), + ); @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + return Container( margin: CustomTheme.standardMargin, - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: CustomTheme.standardBoxDecoration, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: TextInputField( @@ -57,17 +65,19 @@ class _TeamCreationTileState extends State { maxLength: Constants.MAX_TEAM_NAME_LENGTH, ), ), - const SizedBox(width: 8), - IconButton( - onPressed: () => widget.onDelete?.call(), - icon: const Icon(Icons.delete, size: 24), + const SizedBox(width: 12), + AnimatedDialogButton( + content: const Icon(Icons.delete), + isDescructive: true, + onPressed: widget.onDelete, + buttonText: '', ), ], ), const SizedBox(height: 8), - const Text( - 'Color', - style: TextStyle( + Text( + loc.color, + style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: CustomTheme.textColor, @@ -77,7 +87,7 @@ class _TeamCreationTileState extends State { Wrap( spacing: 8, runSpacing: 8, - children: GameColor.values.map((color) { + children: teamColors.map((color) { final isSelected = widget.color == color; return GestureDetector( onTap: () { @@ -102,9 +112,9 @@ class _TeamCreationTileState extends State { }).toList(), ), const SizedBox(height: 12), - const Text( - 'Players', - style: TextStyle( + Text( + loc.players, + style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: CustomTheme.textColor, @@ -112,9 +122,9 @@ class _TeamCreationTileState extends State { ), const SizedBox(height: 8), if (widget.players.isEmpty) - const Text( - 'Keine Spieler:innen zugewiesen', - style: TextStyle(color: CustomTheme.hintColor), + Text( + loc.no_players_selected, + style: const TextStyle(color: CustomTheme.hintColor), ) else Wrap( @@ -122,18 +132,25 @@ class _TeamCreationTileState extends State { runSpacing: 8, children: widget.players .map( - (player) => GestureDetector( - onTap: () => widget.onPlayerTap?.call(player), - child: TextIconTile( - text: player.name, - suffixText: getNameCountText(player), - iconEnabled: widget.onPlayerTap != null, - onIconTap: () => widget.onPlayerTap?.call(player), - ), + (player) => TextIconTile( + text: player.name, + suffixText: getNameCountText(player), + iconEnabled: false, ), ) .toList(), ), + if (widget.onEdit != null) + Padding( + padding: const EdgeInsets.only(top: 12), + child: AnimatedDialogButton( + buttonConstraints: const BoxConstraints( + minWidth: double.infinity, + ), + buttonText: loc.edit_members, + onPressed: widget.onEdit!, + ), + ), ], ), );