Implemented choose game view
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m3s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s

This commit is contained in:
2025-11-28 14:41:50 +01:00
parent 94b113eb95
commit 236a737fd1
5 changed files with 167 additions and 98 deletions

View File

@@ -27,5 +27,19 @@ enum ExportResult { success, canceled, unknownException }
/// - [Ruleset.singleWinner]: The game is won by a single player /// - [Ruleset.singleWinner]: The game is won by a single player
/// - [Ruleset.singleLoser]: The game is lost by a single player /// - [Ruleset.singleLoser]: The game is lost by a single player
/// - [Ruleset.mostPoints]: The player with the most points wins. /// - [Ruleset.mostPoints]: The player with the most points wins.
/// - [Ruleset.lastPoints]: The player with the fewest points wins. /// - [Ruleset.leastPoints]: The player with the fewest points wins.
enum Ruleset { singleWinner, singleLoser, mostPoints, lastPoints } 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';
}
}

View File

@@ -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<ChooseGameView> createState() => _ChooseGameViewState();
}
class _ChooseGameViewState extends State<ChooseGameView> {
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);
});
},
);
},
),
);
}
}

View File

@@ -6,6 +6,7 @@ import 'package:game_tracker/presentation/widgets/tiles/title_description_list_t
class ChooseRulesetView extends StatefulWidget { class ChooseRulesetView extends StatefulWidget {
final List<(Ruleset, String, String)> rulesets; final List<(Ruleset, String, String)> rulesets;
final int initialRulesetIndex; final int initialRulesetIndex;
const ChooseRulesetView({ const ChooseRulesetView({
super.key, super.key,
required this.rulesets, required this.rulesets,
@@ -41,78 +42,25 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
), ),
centerTitle: true, centerTitle: true,
), ),
body: Column( body: ListView.builder(
children: [ padding: const EdgeInsets.only(bottom: 85),
Container( itemCount: widget.rulesets.length,
color: CustomTheme.backgroundColor, itemBuilder: (BuildContext context, int index) {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), return TitleDescriptionListTile(
child: TabBar( onPressed: () async {
padding: const EdgeInsets.symmetric(horizontal: 5), setState(() {
// Label Settings selectedRulesetIndex = index;
labelStyle: const TextStyle( });
fontSize: 16, Future.delayed(const Duration(milliseconds: 500), () {
fontWeight: FontWeight.bold, if (!context.mounted) return;
), Navigator.of(context).pop(widget.rulesets[index].$1);
labelColor: Colors.white, });
unselectedLabelStyle: const TextStyle(fontSize: 14), },
unselectedLabelColor: Colors.white70, title: widget.rulesets[index].$2,
// Indicator Settings description: widget.rulesets[index].$3,
indicator: CustomTheme.standardBoxDecoration, isHighlighted: selectedRulesetIndex == index,
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),
),
),
],
),
),
],
), ),
), ),
); );

View File

@@ -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/game.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.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_group_view.dart';
import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_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'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
@@ -53,6 +54,8 @@ class _CreateGameViewState extends State<CreateGameView> {
/// the [ChooseRulesetView] /// the [ChooseRulesetView]
int selectedRulesetIndex = -1; int selectedRulesetIndex = -1;
int selectedGameIndex = -1;
/// The currently selected players /// The currently selected players
List<Player>? selectedPlayers; List<Player>? selectedPlayers;
@@ -75,12 +78,17 @@ class _CreateGameViewState extends State<CreateGameView> {
'Traditional ruleset: the player with the most points wins.', 'Traditional ruleset: the player with the most points wins.',
), ),
( (
Ruleset.lastPoints, Ruleset.leastPoints,
'Least Points', 'Least Points',
'Inverse scoring: the player with the fewest points wins.', '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 @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -122,6 +130,27 @@ class _CreateGameViewState extends State<CreateGameView> {
}, },
), ),
), ),
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( ChooseTile(
title: 'Ruleset', title: 'Ruleset',
trailingText: selectedRuleset == null trailingText: selectedRuleset == null
@@ -139,6 +168,7 @@ class _CreateGameViewState extends State<CreateGameView> {
selectedRulesetIndex = rulesets.indexWhere( selectedRulesetIndex = rulesets.indexWhere(
(r) => r.$1 == selectedRuleset, (r) => r.$1 == selectedRuleset,
); );
selectedGameIndex = -1;
setState(() {}); setState(() {});
}, },
), ),
@@ -207,20 +237,6 @@ class _CreateGameViewState extends State<CreateGameView> {
); );
} }
/// 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 /// Determines whether the "Create Game" button should be enabled based on
/// the current state of the input fields. /// the current state of the input fields.
bool _enableCreateGameButton() { bool _enableCreateGameButton() {

View File

@@ -6,6 +6,8 @@ class TitleDescriptionListTile extends StatelessWidget {
final String description; final String description;
final VoidCallback? onPressed; final VoidCallback? onPressed;
final bool isHighlighted; final bool isHighlighted;
final String? badgeText;
final Color? badgeColor;
const TitleDescriptionListTile({ const TitleDescriptionListTile({
super.key, super.key,
@@ -13,6 +15,8 @@ class TitleDescriptionListTile extends StatelessWidget {
required this.description, required this.description,
this.onPressed, this.onPressed,
this.isHighlighted = false, this.isHighlighted = false,
this.badgeText,
this.badgeColor,
}); });
@override @override
@@ -28,20 +32,42 @@ class TitleDescriptionListTile extends StatelessWidget {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Flexible( Text(
child: Text( title,
title, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, style: const TextStyle(
style: const TextStyle( fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, fontSize: 18,
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), const SizedBox(height: 5),