diff --git a/lib/core/enums.dart b/lib/core/enums.dart index af1f4a6..68752fb 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -27,5 +27,19 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.singleWinner]: The game is won by a single player /// - [Ruleset.singleLoser]: The game is lost by a single player /// - [Ruleset.mostPoints]: The player with the most points wins. -/// - [Ruleset.lastPoints]: The player with the fewest points wins. -enum Ruleset { singleWinner, singleLoser, mostPoints, lastPoints } +/// - [Ruleset.leastPoints]: The player with the fewest points wins. +enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } + +/// Translates a [Ruleset] enum value to its corresponding string representation. +String translateRulesetToString(Ruleset ruleset) { + switch (ruleset) { + case Ruleset.singleWinner: + return 'Single Winner'; + case Ruleset.singleLoser: + return 'Single Loser'; + case Ruleset.mostPoints: + return 'Most Points'; + case Ruleset.leastPoints: + return 'Least Points'; + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart new file mode 100644 index 0000000..1c75b32 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; + +class ChooseGameView extends StatefulWidget { + final List<(String, String, Ruleset)> games; + final int initialGameIndex; + + const ChooseGameView({ + super.key, + required this.games, + required this.initialGameIndex, + }); + + @override + State createState() => _ChooseGameViewState(); +} + +class _ChooseGameViewState extends State { + late int selectedGameIndex; + + @override + void initState() { + selectedGameIndex = widget.initialGameIndex; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Game', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(selectedGameIndex); + }); + }, + ); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 647cdc4..2e45a7c 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/presentation/widgets/tiles/title_description_list_t class ChooseRulesetView extends StatefulWidget { final List<(Ruleset, String, String)> rulesets; final int initialRulesetIndex; + const ChooseRulesetView({ super.key, required this.rulesets, @@ -41,78 +42,25 @@ class _ChooseRulesetViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Container( - color: CustomTheme.backgroundColor, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: TabBar( - padding: const EdgeInsets.symmetric(horizontal: 5), - // Label Settings - labelStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - labelColor: Colors.white, - unselectedLabelStyle: const TextStyle(fontSize: 14), - unselectedLabelColor: Colors.white70, - // Indicator Settings - indicator: CustomTheme.standardBoxDecoration, - indicatorSize: TabBarIndicatorSize.tab, - indicatorWeight: 1, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 0, - ), - // Divider Settings - dividerHeight: 0, - tabs: const [ - Tab(text: 'Rulesets'), - Tab(text: 'Gametypes'), - ], - ), - ), - const Divider( - indent: 30, - endIndent: 30, - thickness: 3, - radius: BorderRadius.all(Radius.circular(12)), - ), - Expanded( - child: TabBarView( - children: [ - ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - selectedRulesetIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of( - context, - ).pop(widget.rulesets[index].$1); - }); - }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, - isHighlighted: selectedRulesetIndex == index, - ); - }, - ), - const Center( - child: Text( - 'No gametypes available', - style: TextStyle(color: Colors.white70), - ), - ), - ], - ), - ), - ], + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + onPressed: () async { + setState(() { + selectedRulesetIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(widget.rulesets[index].$1); + }); + }, + title: widget.rulesets[index].$2, + description: widget.rulesets[index].$3, + isHighlighted: selectedRulesetIndex == index, + ); + }, ), ), ); diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 485118b..68883d0 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/data/db/database.dart'; 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_game/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -53,6 +54,8 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + int selectedGameIndex = -1; + /// The currently selected players List? selectedPlayers; @@ -75,12 +78,17 @@ class _CreateGameViewState extends State { 'Traditional ruleset: the player with the most points wins.', ), ( - Ruleset.lastPoints, + Ruleset.leastPoints, 'Least Points', 'Inverse scoring: the player with the fewest points wins.', ), ]; + List<(String, String, Ruleset)> games = [ + ('Cabo', 'A memory card game', Ruleset.leastPoints), + ('Uno', 'The Classic', Ruleset.singleWinner), + ]; + @override void initState() { super.initState(); @@ -122,6 +130,27 @@ class _CreateGameViewState extends State { }, ), ), + ChooseTile( + title: 'Game', + trailingText: selectedGameIndex == -1 + ? 'None' + : games[selectedGameIndex].$1, + onPressed: () async { + selectedGameIndex = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGameView( + games: games, + initialGameIndex: selectedGameIndex, + ), + ), + ); + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + setState(() {}); + }, + ), ChooseTile( title: 'Ruleset', trailingText: selectedRuleset == null @@ -139,6 +168,7 @@ class _CreateGameViewState extends State { selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); + selectedGameIndex = -1; setState(() {}); }, ), @@ -207,20 +237,6 @@ class _CreateGameViewState extends State { ); } - /// Translates a [Ruleset] enum value to its corresponding string representation. - String translateRulesetToString(Ruleset ruleset) { - switch (ruleset) { - case Ruleset.singleWinner: - return 'Single Winner'; - case Ruleset.singleLoser: - return 'Single Loser'; - case Ruleset.mostPoints: - return 'Most Points'; - case Ruleset.lastPoints: - return 'Least Points'; - } - } - /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 5b370d4..ac20b3f 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -6,6 +6,8 @@ class TitleDescriptionListTile extends StatelessWidget { final String description; final VoidCallback? onPressed; final bool isHighlighted; + final String? badgeText; + final Color? badgeColor; const TitleDescriptionListTile({ super.key, @@ -13,6 +15,8 @@ class TitleDescriptionListTile extends StatelessWidget { required this.description, this.onPressed, this.isHighlighted = false, + this.badgeText, + this.badgeColor, }); @override @@ -28,20 +32,42 @@ class TitleDescriptionListTile extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Flexible( - child: Text( - title, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + Text( + title, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, ), ), + if (badgeText != null) ...[ + const Spacer(), + Container( + margin: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 6, + ), + decoration: BoxDecoration( + color: badgeColor ?? CustomTheme.primaryColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + badgeText!, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ], ), const SizedBox(height: 5),