CreateGameView erstellen #67
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
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/text_input/custom_search_bar.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;
|
||||
final TextEditingController searchBarController = TextEditingController();
|
||||
|
||||
@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,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(selectedGameIndex);
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Choose Game',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: CustomSearchBar(
|
||||
controller: searchBarController,
|
||||
hintText: 'Game Name',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
|
flixcoo marked this conversation as resolved
Outdated
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
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(() {
|
||||
if (selectedGameIndex == index) {
|
||||
selectedGameIndex = -1;
|
||||
} else {
|
||||
selectedGameIndex = index;
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
warum ist unselecten hier nicht implementiert, beim choose group view aber schon? warum ist unselecten hier nicht implementiert, beim choose group view aber schon?
flixcoo
commented
ups, vergessen ups, vergessen
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||
|
||||
class ChooseGroupView extends StatefulWidget {
|
||||
final List<Group> groups;
|
||||
final int initialGroupIndex;
|
||||
final String initialGroupId;
|
||||
|
||||
const ChooseGroupView({
|
||||
super.key,
|
||||
required this.groups,
|
||||
required this.initialGroupIndex,
|
||||
required this.initialGroupId,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -18,11 +20,15 @@ class ChooseGroupView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
late int selectedGroupIndex;
|
||||
late String selectedGroupId;
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final String hintText = 'Group Name';
|
||||
late final List<Group> filteredGroups;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
selectedGroupIndex = widget.initialGroupIndex;
|
||||
selectedGroupId = widget.initialGroupId;
|
||||
filteredGroups = [...widget.groups];
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -33,34 +39,90 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
selectedGroupId == ''
|
||||
? null
|
||||
: widget.groups.firstWhere(
|
||||
(group) => group.id == selectedGroupId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Choose Group',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
Warum lässt sich in Choose Game View suchen, im Choose Group View aber nicht? Können beides ja potenziell sehr lange Listen sein Warum lässt sich in Choose Game View suchen, im Choose Group View aber nicht? Können beides ja potenziell sehr lange Listen sein
flixcoo
commented
Erledigt (So würde dass dann auch für die Games funktionieren) Erledigt (So würde dass dann auch für die Games funktionieren)
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: ListView.builder(
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: CustomSearchBar(
|
||||
controller: controller,
|
||||
hintText: hintText,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
filterGroups(value);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: filteredGroups.isNotEmpty,
|
||||
replacement: const TopCenteredMessage(
|
||||
icon: Icons.info,
|
||||
title: 'Info',
|
||||
message: 'There is no group matching your search',
|
||||
),
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
Hier bitte ohne Punkt, das hatten wir ja gesagt, und wir müssen uns darauf einigen ob der Titel infos erhält, oder nur keywords wie info/error, weil ich das bis jetzt so gemacht habe. Hier bitte ohne Punkt, das hatten wir ja gesagt, und wir müssen uns darauf einigen ob der Titel infos erhält, oder nur keywords wie info/error, weil ich das bis jetzt so gemacht habe.
Sonst passt so
flixcoo
commented
Wenn der Titel keine Infos enthalten soll, würd ich daraus n enum Attribut machen damit der je nach enum gesetzt wird (Icon und Titel) Wenn der Titel keine Infos enthalten soll, würd ich daraus n enum Attribut machen damit der je nach enum gesetzt wird (Icon und Titel)
sneeex
commented
ja findste mit oder ohne richtigen Title besser? ja findste mit oder ohne richtigen Title besser?
sneeex
commented
ist mir auch egal, lass es so oder nicht. Wenn du es mit Info/Error im Titel willst, mach am besten das einmal temporär in den Titel und dann nen Issue für Änderung der anderen Titel in eine enum Version oder halt eine Version mit aussagekräftigem Titel. Aber auf jeden Fall den Punkt weg machen am Ende von Message ist mir auch egal, lass es so oder nicht. Wenn du es mit Info/Error im Titel willst, mach am besten das einmal temporär in den Titel und dann nen Issue für Änderung der anderen Titel in eine enum Version oder halt eine Version mit aussagekräftigem Titel.
**Aber auf jeden Fall den Punkt weg machen am Ende von Message**
dann approve ich
flixcoo
commented
erledigt erledigt
|
||||
child: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: widget.groups.length,
|
||||
itemCount: filteredGroups.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedGroupIndex = index;
|
||||
});
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop(widget.groups[index]);
|
||||
if (selectedGroupId != filteredGroups[index].id) {
|
||||
selectedGroupId = filteredGroups[index].id;
|
||||
} else {
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
hier noch etwas einblenden wenn kein suchergebnis da ist, also filteredGroups leer ist (bspw. topcenteredmessage) hier noch etwas einblenden wenn kein suchergebnis da ist, also filteredGroups leer ist (bspw. topcenteredmessage)
|
||||
selectedGroupId = '';
|
||||
}
|
||||
});
|
||||
},
|
||||
child: GroupTile(
|
||||
group: widget.groups[index],
|
||||
isHighlighted: selectedGroupIndex == index,
|
||||
group: filteredGroups[index],
|
||||
isHighlighted:
|
||||
selectedGroupId == filteredGroups[index].id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Filters the groups based on the search query.
|
||||
/// TODO: Maybe implement also targetting player names?
|
||||
void filterGroups(String query) {
|
||||
setState(() {
|
||||
if (query.isEmpty) {
|
||||
filteredGroups.clear();
|
||||
filteredGroups.addAll(widget.groups);
|
||||
} else {
|
||||
filteredGroups.clear();
|
||||
filteredGroups.addAll(
|
||||
widget.groups.where(
|
||||
(group) => group.name.toLowerCase().contains(query.toLowerCase()),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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/ruleset_list_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
|
||||
class ChooseRulesetView extends StatefulWidget {
|
||||
final List<(Ruleset, String, String)> rulesets;
|
||||
final List<(Ruleset, String)> rulesets;
|
||||
final int initialRulesetIndex;
|
||||
|
||||
const ChooseRulesetView({
|
||||
super.key,
|
||||
required this.rulesets,
|
||||
@@ -35,85 +36,42 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
|
sneeex marked this conversation as resolved
Outdated
sneeex
commented
Macht keinen Sinn, da gametype und rulesets ausgewählt werden können Macht keinen Sinn, da gametype und rulesets ausgewählt werden können
flixcoo
commented
Habs jetzt zu Habs jetzt zu `Choose Ruleset` geändert, da man mit den Gametypes ja auch im Prinzip nur n Ruleset festlegt
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(
|
||||
selectedRulesetIndex == -1
|
||||
? null
|
||||
: widget.rulesets[selectedRulesetIndex].$1,
|
||||
);
|
||||
},
|
||||
),
|
||||
title: const Text(
|
||||
'Choose Ruleset',
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||
|
sneeex marked this conversation as resolved
Outdated
sneeex
commented
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
flixcoo
commented
Wollte das einfach bisschen klarer aufteilen Wollte das einfach bisschen klarer aufteilen
|
||||
),
|
||||
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(
|
||||
body: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: widget.rulesets.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return RulesetListTile(
|
||||
return TitleDescriptionListTile(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
if (selectedRulesetIndex == index) {
|
||||
selectedRulesetIndex = -1;
|
||||
} else {
|
||||
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,
|
||||
title: translateRulesetToString(widget.rulesets[index].$1),
|
||||
description: widget.rulesets[index].$2,
|
||||
isHighlighted: selectedRulesetIndex == index,
|
||||
);
|
||||
},
|
||||
),
|
||||
const Center(
|
||||
child: Text(
|
||||
'No gametypes available',
|
||||
style: TextStyle(color: Colors.white70),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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/core/enums.dart';
|
||||
@@ -5,8 +6,10 @@ 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/views/main_menu/game_result_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/player_selection.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart';
|
||||
@@ -14,7 +17,8 @@ import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CreateGameView extends StatefulWidget {
|
||||
const CreateGameView({super.key});
|
||||
final VoidCallback? onWinnerChanged;
|
||||
const CreateGameView({super.key, this.onWinnerChanged});
|
||||
|
||||
@override
|
||||
State<CreateGameView> createState() => _CreateGameViewState();
|
||||
@@ -39,12 +43,18 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
/// List of all players from the database
|
||||
List<Player> playerList = [];
|
||||
|
||||
|
sneeex marked this conversation as resolved
Outdated
sneeex
commented
warum auch hier bei allen vars comments?? warum auch hier bei allen vars comments??
flixcoo
commented
Damit halt jede Variable klar ist und man auch entsprechende Docs hat, wenn man darüber hovert Damit halt jede Variable klar ist und man auch entsprechende Docs hat, wenn man darüber hovert
|
||||
/// List of players filtered based on the selected group
|
||||
/// If a group is selected, this list contains all players from [playerList]
|
||||
/// who are not members of the selected group. If no group is selected,
|
||||
/// this list is identical to [playerList].
|
||||
List<Player> filteredPlayerList = [];
|
||||
|
||||
/// The currently selected group
|
||||
Group? selectedGroup;
|
||||
|
||||
/// The index of the currently selected group in [groupsList] to mark it in
|
||||
/// the [ChooseGroupView]
|
||||
int selectedGroupIndex = -1;
|
||||
String selectedGroupId = '';
|
||||
|
||||
/// The currently selected ruleset
|
||||
Ruleset? selectedRuleset;
|
||||
@@ -53,37 +63,48 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
/// the [ChooseRulesetView]
|
||||
int selectedRulesetIndex = -1;
|
||||
|
||||
/// The index of the currently selected game in [games] to mark it in
|
||||
/// the [ChooseGameView]
|
||||
int selectedGameIndex = -1;
|
||||
|
||||
/// The currently selected players
|
||||
List<Player>? selectedPlayers;
|
||||
|
||||
/// List of available rulesets with their display names and descriptions
|
||||
/// as tuples of (Ruleset, String, String)
|
||||
List<(Ruleset, String, String)> rulesets = [
|
||||
/// List of available rulesets with their descriptions
|
||||
/// as tuples of (Ruleset, String)
|
||||
/// TODO: Replace when rulesets are implemented
|
||||
List<(Ruleset, String)> rulesets = [
|
||||
(
|
||||
Ruleset.singleWinner,
|
||||
'Single Winner',
|
||||
'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.',
|
||||
),
|
||||
(
|
||||
Ruleset.singleLoser,
|
||||
'Single Loser',
|
||||
'Exactly one loser is determined; last place receives the penalty or consequence.',
|
||||
),
|
||||
(
|
||||
Ruleset.mostPoints,
|
||||
'Most Points',
|
||||
'Traditional ruleset: the player with the most points wins.',
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
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?
flixcoo
commented
Hab ich danach erst implementiert, erledigt. Hab ich danach erst implementiert, erledigt.
|
||||
),
|
||||
(
|
||||
Ruleset.lastPoints,
|
||||
'Least Points',
|
||||
Ruleset.leastPoints,
|
||||
'Inverse scoring: the player with the fewest points wins.',
|
||||
),
|
||||
];
|
||||
|
||||
// TODO: Replace when games are implemented
|
||||
List<(String, String, Ruleset)> games = [
|
||||
('Example Game 1', 'This is a discription', Ruleset.leastPoints),
|
||||
('Example Game 2', '', Ruleset.singleWinner),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_gameNameController.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
_allGroupsFuture = db.groupDao.getAllGroups();
|
||||
@@ -93,6 +114,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
groupsList = result[0] as List<Group>;
|
||||
playerList = result[1] as List<Player>;
|
||||
});
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
macht das nicht mehr sinn, rein logisch, die ausserhalb der future wait zu haben? weil die hat ja nichts in der future wait zu suchen oder? macht das nicht mehr sinn, rein logisch, die ausserhalb der future wait zu haben? weil die hat ja nichts in der future wait zu suchen oder?
flixcoo
commented
theoreitisch ist das egal wo das ist hauptsache nach der future theoreitisch ist das egal wo das ist hauptsache nach der future
sneeex
commented
ja ich weiß aber macht es nicht mehr sinn das ausserhalb zu machen ja ich weiß aber macht es nicht mehr sinn das ausserhalb zu machen
sneeex
commented
so vom logischen so vom logischen
|
||||
|
||||
filteredPlayerList = List.from(playerList);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -113,15 +136,38 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
child: TextInputField(
|
||||
controller: _gameNameController,
|
||||
hintText: 'Game name',
|
||||
onChanged: (value) {
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
if (selectedGameIndex != -1) {
|
||||
selectedRuleset = games[selectedGameIndex].$3;
|
||||
selectedRulesetIndex = rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
} else {
|
||||
selectedRuleset = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
ChooseTile(
|
||||
title: 'Ruleset',
|
||||
trailingText: selectedRuleset == null
|
||||
@@ -139,6 +185,7 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
selectedRulesetIndex = rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
selectedGameIndex = -1;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
@@ -152,28 +199,28 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChooseGroupView(
|
||||
groups: groupsList,
|
||||
initialGroupIndex: selectedGroupIndex,
|
||||
initialGroupId: selectedGroupId,
|
||||
),
|
||||
),
|
||||
);
|
||||
selectedGroupIndex = groupsList.indexWhere(
|
||||
(g) => g.id == selectedGroup?.id,
|
||||
);
|
||||
selectedGroupId = selectedGroup?.id ?? '';
|
||||
if (selectedGroup != null) {
|
||||
filteredPlayerList = playerList
|
||||
.where(
|
||||
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
|
||||
)
|
||||
.toList();
|
||||
} else {
|
||||
filteredPlayerList = List.from(playerList);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: PlayerSelection(
|
||||
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||
initialPlayers: selectedGroup == null
|
||||
? playerList
|
||||
: playerList
|
||||
.where(
|
||||
(p) => !selectedGroup!.members.any(
|
||||
(m) => m.id == p.id,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
initialSelectedPlayers: selectedPlayers ?? [],
|
||||
availablePlayers: filteredPlayerList,
|
||||
onChanged: (value) {
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
auch hier print auch hier print
|
||||
setState(() {
|
||||
selectedPlayers = value;
|
||||
@@ -181,7 +228,6 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
CustomWidthButton(
|
||||
text: 'Create game',
|
||||
sizeRelativeToWidth: 0.95,
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
db ist doch schon assignt? warum nochmal? siehe zeile 99 db ist doch schon assignt? warum nochmal? siehe zeile 99
|
||||
@@ -191,42 +237,37 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
Game game = Game(
|
||||
name: _gameNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
|
sneeex marked this conversation as resolved
Outdated
sneeex
commented
<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)
flixcoo
commented
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
|
||||
group: selectedGroup!,
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
);
|
||||
// TODO: Replace with navigation to GameResultView()
|
||||
print('Created game: $game');
|
||||
Navigator.pop(context);
|
||||
await db.gameDao.addGame(game: game);
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => GameResultView(
|
||||
game: game,
|
||||
onWinnerChanged: widget.onWinnerChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
hier sicherstellen, dass mindestens 2 spieler in nem game sind oder nicht? hier sicherstellen, dass mindestens 2 spieler in nem game sind oder nicht?
kann leider nicht an der richtigen stelle kommentieren, aber in der function halt
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
return _gameNameController.text.isNotEmpty &&
|
||||
(selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.isNotEmpty)) &&
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1)) &&
|
||||
selectedRuleset != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +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_group_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_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';
|
||||
@@ -98,17 +98,16 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
}
|
||||
return GameHistoryTile(
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
Navigator.push(
|
||||
context,
|
||||
CupertinoPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) =>
|
||||
GameResultView(game: games[index]),
|
||||
builder: (context) => GameResultView(
|
||||
game: games[index],
|
||||
onWinnerChanged: refreshGameList,
|
||||
),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_gameListFuture = db.gameDao.getAllGames();
|
||||
});
|
||||
},
|
||||
game: games[index],
|
||||
);
|
||||
@@ -123,17 +122,13 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
text: 'Create Game',
|
||||
sizeRelativeToWidth: 0.90,
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return const CreateGroupView();
|
||||
},
|
||||
builder: (context) =>
|
||||
CreateGameView(onWinnerChanged: refreshGameList),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_gameListFuture = db.gameDao.getAllGames();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -141,4 +136,10 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void refreshGameList() {
|
||||
setState(() {
|
||||
_gameListFuture = db.gameDao.getAllGames();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import 'package:provider/provider.dart';
|
||||
class GameResultView extends StatefulWidget {
|
||||
final Game game;
|
||||
|
||||
const GameResultView({super.key, required this.game});
|
||||
final VoidCallback? onWinnerChanged;
|
||||
|
||||
const GameResultView({super.key, required this.game, this.onWinnerChanged});
|
||||
@override
|
||||
State<GameResultView> createState() => _GameResultViewState();
|
||||
}
|
||||
@@ -131,6 +132,7 @@ class _GameResultViewState extends State<GameResultView> {
|
||||
winnerId: _selectedPlayer!.id,
|
||||
);
|
||||
}
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
List<Player> getAllPlayers(Game game) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import 'package:provider/provider.dart';
|
||||
|
||||
class PlayerSelection extends StatefulWidget {
|
||||
final Function(List<Player> value) onChanged;
|
||||
final List<Player> initialPlayers;
|
||||
final List<Player> availablePlayers;
|
||||
final List<Player>? initialSelectedPlayers;
|
||||
|
||||
const PlayerSelection({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.initialPlayers = const [],
|
||||
this.availablePlayers = const [],
|
||||
this.initialSelectedPlayers,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -51,10 +53,24 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
suggestedPlayers = skeletonData;
|
||||
_allPlayersFuture.then((loadedPlayers) {
|
||||
setState(() {
|
||||
if (widget.initialPlayers.isNotEmpty) {
|
||||
allPlayers = [...widget.initialPlayers];
|
||||
suggestedPlayers = [...widget.initialPlayers];
|
||||
// If a list of available players is provided, use that list.
|
||||
if (widget.availablePlayers.isNotEmpty) {
|
||||
widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...widget.availablePlayers];
|
||||
suggestedPlayers = [...allPlayers];
|
||||
|
||||
if (widget.initialSelectedPlayers != null) {
|
||||
// Ensures that only players available for selection are pre-selected.
|
||||
selectedPlayers = widget.initialSelectedPlayers!
|
||||
.where(
|
||||
(p) => widget.availablePlayers.any(
|
||||
(available) => available.id == p.id,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
} else {
|
||||
// Otherwise, use the loaded players from the database.
|
||||
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...loadedPlayers];
|
||||
suggestedPlayers = [...loadedPlayers];
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
class RulesetListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isHighlighted;
|
||||
|
||||
const RulesetListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
this.onPressed,
|
||||
this.isHighlighted = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: AnimatedContainer(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: isHighlighted
|
||||
? CustomTheme.highlightedBoxDecoration
|
||||
: CustomTheme.standardBoxDecoration,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(description, style: const TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 2.5),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
class TitleDescriptionListTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isHighlighted;
|
||||
final String? badgeText;
|
||||
final Color? badgeColor;
|
||||
|
||||
const TitleDescriptionListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
this.onPressed,
|
||||
this.isHighlighted = false,
|
||||
this.badgeText,
|
||||
this.badgeColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: AnimatedContainer(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
|
||||
decoration: isHighlighted
|
||||
? CustomTheme.highlightedBoxDecoration
|
||||
: CustomTheme.standardBoxDecoration,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 230,
|
||||
child: Text(
|
||||
title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
|
flixcoo marked this conversation as resolved
Outdated
sneeex
commented
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (badgeText != null) ...[
|
||||
const Spacer(),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 100),
|
||||
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(
|
||||
|
flixcoo marked this conversation as resolved
sneeex
commented
|
||||
badgeText!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (description.isNotEmpty) ...[
|
||||
const SizedBox(height: 5),
|
||||
Text(description, style: const TextStyle(fontSize: 14)),
|
||||
const SizedBox(height: 2.5),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
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
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
dann mach die functionality rein, entweder oder.
Du hast ja nur kein bock