diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index ea5ddd7..31d1b56 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -5,6 +6,7 @@ import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; @@ -21,7 +23,6 @@ class GameHistoryView extends StatefulWidget { class _GameHistoryViewState extends State { late Future> _gameListFuture; late final AppDatabase db; - late bool isLoading = true; late final List skeletonData = List.filled( 4, @@ -46,14 +47,10 @@ class _GameHistoryViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = db.gameDao.getAllGames(); - - Future.wait([_gameListFuture]).then((result) async { - await Future.delayed(const Duration(milliseconds: 250)); - setState(() { - isLoading = false; - }); - }); + _gameListFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.gameDao.getAllGames(), + ); } @override @@ -81,19 +78,19 @@ class _GameHistoryViewState extends State { return const Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Error', - message: 'No Games Available', + title: 'Info', + message: 'No games created yet', ), ); } - + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; final List games = (isLoading ? skeletonData : (snapshot.data ?? []) ..sort( (a, b) => b.createdAt.compareTo(a.createdAt), )) .toList(); - return AppSkeleton( enabled: isLoading, child: ListView.builder( @@ -106,8 +103,21 @@ class _GameHistoryViewState extends State { ); } return GameHistoryTile( + onTap: () async { + await Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => + GameResultView(game: games[index]), + ), + ); + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); + }, game: games[index], - ); // Placeholder + ); }, ), ); diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart new file mode 100644 index 0000000..f13553b --- /dev/null +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -0,0 +1,144 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; +import 'package:provider/provider.dart'; + +class GameResultView extends StatefulWidget { + final Game game; + + const GameResultView({super.key, required this.game}); + + @override + State createState() => _GameResultViewState(); +} + +class _GameResultViewState extends State { + late final List allPlayers; + late final AppDatabase db; + Player? _selectedPlayer; + + @override + void initState() { + db = Provider.of(context, listen: false); + allPlayers = getAllPlayers(widget.game); + if (widget.game.winner != null) { + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.game.winner!.id, + ); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: Text( + widget.game.name, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, + ), + ), + centerTitle: true, + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Select Winner:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Expanded( + child: RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) async { + setState(() { + _selectedPlayer = value; + }); + await _handleWinnerSaving(); + }, + 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); + } + }); + await _handleWinnerSaving(); + }, + ); + }, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Future _handleWinnerSaving() async { + if (_selectedPlayer == null) { + await db.gameDao.removeWinner(gameId: widget.game.id); + } else { + await db.gameDao.setWinner( + gameId: widget.game.id, + winnerId: _selectedPlayer!.id, + ); + } + } + + List getAllPlayers(Game game) { + if (game.group == null && game.players != null) { + return [...game.players!]; + } else if (game.group != null && game.players != null) { + return [...game.players!, ...game.group!.members]; + } + return [...game.group!.members]; + } +} diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 4601ef9..ce47f90 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -56,7 +56,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.report, title: 'Error', - message: 'Group data couldn\'t\nbe loaded.', + message: 'Group data couldn\'t\nbe loaded', ), ); } @@ -66,7 +66,7 @@ class _GroupsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: 'No groups created yet.', + message: 'No groups created yet', ), ); } diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart new file mode 100644 index 0000000..11e8b40 --- /dev/null +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomRadioListTile extends StatelessWidget { + final String text; + final T value; + final ValueChanged onContainerTap; + + const CustomRadioListTile({ + super.key, + required this.text, + required this.value, + required this.onContainerTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onContainerTap(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.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Radio( + value: value, + activeColor: CustomTheme.primaryColor, + toggleable: true, + ), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 3cdd1ad..83da859 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -6,142 +6,132 @@ import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { final Game game; + final VoidCallback onTap; - const GameHistoryTile({ - super.key, - required this.game, - }); + const GameHistoryTile({super.key, required this.game, required this.onTap}); @override State createState() => _GameHistoryTileState(); } class _GameHistoryTileState extends State { - @override Widget build(BuildContext context) { final group = widget.game.group; final winner = widget.game.winner; final allPlayers = _getAllPlayers(); - return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - widget.game.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, - ), - ), - Text( - _formatDate(widget.game.createdAt), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - - const SizedBox(height: 8), - - if (group != null) ...[ + return GestureDetector( + onTap: widget.onTap, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon( - Icons.group, - size: 16, - color: Colors.grey, - ), - const SizedBox(width: 6), Expanded( child: Text( - group.name, + widget.game.name, style: const TextStyle( - fontSize: 14, - color: Colors.grey, + fontSize: 18, + fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), ], ), - const SizedBox(height: 12), - ], - if (winner != null) ...[ - Container( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: BoxDecoration( - color: Colors.green.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Colors.green.withValues(alpha: 0.3), - width: 1, - ), - ), - child: Row( + const SizedBox(height: 8), + + if (group != null) ...[ + Row( children: [ - const Icon( - Icons.emoji_events, - size: 20, - color: Colors.amber, - ), - const SizedBox(width: 8), + const Icon(Icons.group, size: 16, color: Colors.grey), + const SizedBox(width: 6), Expanded( child: Text( - 'Winner: ${winner.name}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, - ), + group.name, + style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), ), ], ), - ), - const SizedBox(height: 12), - ], + const SizedBox(height: 12), + ], - if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', - style: TextStyle( - fontSize: 13, - color: Colors.grey, - fontWeight: FontWeight.w500, + if (winner != null) ...[ + Container( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), - ), - const SizedBox(height: 6), - Wrap( - spacing: 6, - runSpacing: 6, - children: allPlayers.map((player) { - return TextIconTile( - text: player.name, - iconEnabled: false, - ); - }).toList(), - ), + const SizedBox(height: 12), + ], + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + return TextIconTile(text: player.name, iconEnabled: false); + }).toList(), + ), + ], ], - ], + ), ), ); } @@ -187,5 +177,4 @@ class _GameHistoryTileState extends State { return allPlayers; } - -} \ No newline at end of file +}