CreateGameView erstellen #67

Merged
sneeex merged 35 commits from feature/3-creategameview-erstellen into development 2025-12-10 20:43:54 +00:00
5 changed files with 167 additions and 98 deletions
Showing only changes of commit 236a737fd1 - Show all commits

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);
});
},
flixcoo marked this conversation as resolved Outdated

Es ist kein OnChanged implementiert, die search functionality ist insgesamt nicht gegeben hier.

Note: Das onChanged sollte aber vllt. auch required sein in der CustomSearchBar (weiß gerade nicht ob ich das aus nem grund optional gelassen habe, macht aber ja glaube keinen sinn oder?)

Es ist kein OnChanged implementiert, die search functionality ist insgesamt nicht gegeben hier. Note: Das onChanged sollte aber vllt. auch required sein in der CustomSearchBar (weiß gerade nicht ob ich das aus nem grund optional gelassen habe, macht aber ja glaube keinen sinn oder?)

Also ich habs explizit weggelassen, weil ich der einfachhalt halber das aktuell ausgesuchte Spiel über einen Index markiere. Wenn ich das jetzt implementieren würde, müsste ich dass unnötig kompliziert implementieren, weil die Games am Ende eh eine ID haben über die ich das markierte Identifizieren kann, deswegen würd ich das an dieser Stelle weglassen

Also ich habs explizit weggelassen, weil ich der einfachhalt halber das aktuell ausgesuchte Spiel über einen Index markiere. Wenn ich das jetzt implementieren würde, müsste ich dass unnötig kompliziert implementieren, weil die Games am Ende eh eine ID haben über die ich das markierte Identifizieren kann, deswegen würd ich das an dieser Stelle weglassen

dann mach die searchbar weg, entweder searchbar und functionality oder nicht, aber nicht so halbe sachen.

dann mach die searchbar weg, entweder searchbar und functionality oder nicht, aber nicht so halbe sachen.

Ja aber das ist ja auch kacke, das layout an sich soll ja schon stehen

Ja aber das ist ja auch kacke, das layout an sich soll ja schon stehen

Ja aber das ist ja auch kacke, das layout an sich soll ja schon stehen

dann mach die functionality rein, entweder oder.
Du hast ja nur kein bock

> Ja aber das ist ja auch kacke, das layout an sich soll ja schon stehen dann mach die functionality rein, entweder oder. Du hast ja nur kein bock
);
},
),
);
}
}

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(() {
sneeex marked this conversation as resolved Outdated

warum so viele comments? ist doch klar wofür die settings jeweils sind, also label indicator, divider etc

warum so viele comments? ist doch klar wofür die settings jeweils sind, also label indicator, divider etc

Wollte das einfach bisschen klarer aufteilen

Wollte das einfach bisschen klarer aufteilen
// 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 = [
flixcoo marked this conversation as resolved Outdated

Du hast doch in enums die func translateRulesetToString für die Namen, warum nutzt du nicht die einfach für die Namen hier?

Du hast doch in enums die func translateRulesetToString für die Namen, warum nutzt du nicht die einfach für die Namen hier?

Hab ich danach erst implementiert, erledigt.

Hab ich danach erst implementiert, erledigt.
('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> {
}, },
), ),
), ),
flixcoo marked this conversation as resolved Outdated

bitte mit listener implementieren, außerdem hat das _gameNameController hier keinen Effekt, würde auch nur mit setState und leerem Inhalt gehen

bitte mit listener implementieren, außerdem hat das _gameNameController hier keinen Effekt, würde auch nur mit setState und leerem Inhalt gehen
ChooseTile(
title: 'Game',
flixcoo marked this conversation as resolved Outdated

die Function für onChanged wird nicht gebraucht, weil ja schon ein Listener auf den TextEditingController gesetzt ist

die Function für onChanged wird nicht gebraucht, weil ja schon ein Listener auf den TextEditingController gesetzt ist
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> {
); );
} }
sneeex marked this conversation as resolved Outdated
grafik.png game lässt sich nicht erstellen, obwohl spieler, game name und ruleset ausgewählt ist (funktioniert auch mit 2 spielern nicht, hier muss aber darauf geachtet werden, dass ein spiel nur mit min. 2 spielern erstellt werden kann)
<img width="318" alt="grafik.png" src="attachments/1bb19145-834c-4a72-9399-a43972f77e46"> game lässt sich nicht erstellen, obwohl spieler, game name und ruleset ausgewählt ist (funktioniert auch mit 2 spielern nicht, hier muss aber darauf geachtet werden, dass ein spiel nur mit min. 2 spielern erstellt werden kann)
132 KiB

Ja ups haha ich hab ganz vergessen irgendwas mit den ausgewählten Spielern zu machen, hab die einfach nur geprinted haha. Hab die jetzt auch included und sollte jetzt funktionieren

Ja ups haha ich hab ganz vergessen irgendwas mit den ausgewählten Spielern zu machen, hab die einfach nur geprinted haha. Hab die jetzt auch included und sollte jetzt funktionieren
/// 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,
),
), ),
flixcoo marked this conversation as resolved Outdated

title ist nicht overflow sicher und verdrängt das badge

grafik.png

title ist nicht overflow sicher und verdrängt das badge ![grafik.png](/attachments/bcfc7b02-e2a3-483f-ba90-d01bdf33a3b5)
), ),
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,
),
flixcoo marked this conversation as resolved
Review

badgetext nicht overflow sicher
grafik.png

badgetext nicht overflow sicher ![grafik.png](/attachments/44ff847b-617f-4a67-9a09-1547fe967e1c)
),
),
],
], ],
), ),
const SizedBox(height: 5), const SizedBox(height: 5),