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 a3904b7..94e392b 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 @@ -6,7 +6,9 @@ import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/dto/match.dart'; import 'package:tallee/data/dto/player.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/score_list_tile.dart'; class MatchResultView extends StatefulWidget { /// A view that allows selecting and saving the winner of a match @@ -22,6 +24,8 @@ class MatchResultView extends StatefulWidget { /// The match for which the winner is to be selected final Match match; + /// The ruleset of the match, determines how the winner is selected or how + /// scores are entered final Ruleset ruleset; /// Optional callback invoked when the winner is changed @@ -37,6 +41,9 @@ class _MatchResultViewState extends State { /// List of all players who participated in the match late final List allPlayers; + /// List of text controllers for score entry, one for each player + late final List controller; + /// Currently selected winner player Player? _selectedPlayer; @@ -47,10 +54,19 @@ class _MatchResultViewState extends State { allPlayers = widget.match.players; allPlayers.sort((a, b) => a.name.compareTo(b.name)); + controller = List.generate( + allPlayers.length, + (index) => TextEditingController(), + ); + if (widget.match.winner != null) { - _selectedPlayer = allPlayers.firstWhere( - (p) => p.id == widget.match.winner!.id, - ); + if (rulesetSupportsWinnerSelection()) { + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.match.winner!.id, + ); + } else if (rulesetSupportsScoreEntry()) { + /// TODO: Update when score logic is overhauled + } } super.initState(); } @@ -101,43 +117,70 @@ class _MatchResultViewState extends State { ), ), const SizedBox(height: 10), - Expanded( - child: RadioGroup( - groupValue: _selectedPlayer, - onChanged: (Player? value) async { - setState(() { - _selectedPlayer = value; - }); - await _handleSaving(); - }, - child: ListView.builder( + if (rulesetSupportsWinnerSelection()) + Expanded( + child: RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) async { + setState(() { + _selectedPlayer = value; + }); + }, + 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, itemBuilder: (context, index) { - return CustomRadioListTile( + print(allPlayers[index].name); + return ScoreListTile( 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); - } - }); - await _handleSaving(); - }, + controller: controller[index], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(indent: 20), ); }, ), ), - ), ], ), ), ), + CustomWidthButton( + text: loc.save_changes, + sizeRelativeToWidth: 0.95, + onPressed: () async { + await _handleSaving(); + if (!context.mounted) return; + Navigator.of(context).pop(_selectedPlayer); + }, + ), ], ), ), @@ -172,15 +215,28 @@ class _MatchResultViewState extends State { Future _handleLoser() async { if (_selectedPlayer == null) { - //TODO: removeLoser() method + /// TODO: Update when score logic is overhauled return false; } else { - //TODO: setLoser() method + /// TODO: Update when score logic is overhauled return false; } } + /// Handles saving the scores for each player in the database. Future _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.playerMatchDao.updatePlayerScore( + matchId: widget.match.id, + playerId: allPlayers[i].id, + newScore: score, + ); + } return false; } @@ -194,4 +250,14 @@ class _MatchResultViewState extends State { return loc.enter_points; } } + + bool rulesetSupportsWinnerSelection() { + return widget.ruleset == Ruleset.singleWinner || + widget.ruleset == Ruleset.singleLoser; + } + + bool rulesetSupportsScoreEntry() { + return widget.ruleset == Ruleset.lowestScore || + widget.ruleset == Ruleset.highestScore; + } } diff --git a/lib/presentation/widgets/tiles/score_list_tile.dart b/lib/presentation/widgets/tiles/score_list_tile.dart new file mode 100644 index 0000000..d6aafe3 --- /dev/null +++ b/lib/presentation/widgets/tiles/score_list_tile.dart @@ -0,0 +1,91 @@ +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, + /* + required this.onContainerTap, +*/ + }); + + /// The text to display next to the radio button. + final String text; + + final TextEditingController controller; + + /// The callback invoked when the container is tapped. + /* + final ValueChanged onContainerTap; +*/ + + @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, + ), + ), + ), + ), + ), + ], + ), + ); + } +}