CreateGameView erstellen #67
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:game_tracker/core/custom_theme.dart';
|
import 'package:game_tracker/core/custom_theme.dart';
|
||||||
import 'package:game_tracker/data/dto/group.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/tiles/group_tile.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||||
|
|
||||||
class ChooseGroupView extends StatefulWidget {
|
class ChooseGroupView extends StatefulWidget {
|
||||||
final List<Group> groups;
|
final List<Group> groups;
|
||||||
final int initialGroupIndex;
|
final String initialGroupId;
|
||||||
|
|
||||||
const ChooseGroupView({
|
const ChooseGroupView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.groups,
|
required this.groups,
|
||||||
required this.initialGroupIndex,
|
required this.initialGroupId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -18,11 +20,15 @@ class ChooseGroupView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChooseGroupViewState extends State<ChooseGroupView> {
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
selectedGroupIndex = widget.initialGroupIndex;
|
selectedGroupId = widget.initialGroupId;
|
||||||
|
filteredGroups = [...widget.groups];
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,34 +39,90 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: CustomTheme.backgroundColor,
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
scrolledUnderElevation: 0,
|
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(
|
title: const Text(
|
||||||
'Choose Group',
|
'Choose Group',
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
flixcoo marked this conversation as resolved
|
|||||||
),
|
),
|
||||||
centerTitle: true,
|
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),
|
padding: const EdgeInsets.only(bottom: 85),
|
||||||
itemCount: widget.groups.length,
|
itemCount: filteredGroups.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedGroupIndex = index;
|
if (selectedGroupId != filteredGroups[index].id) {
|
||||||
});
|
selectedGroupId = filteredGroups[index].id;
|
||||||
|
} else {
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
selectedGroupId = '';
|
||||||
if (!context.mounted) return;
|
}
|
||||||
Navigator.of(context).pop(widget.groups[index]);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: GroupTile(
|
child: GroupTile(
|
||||||
group: widget.groups[index],
|
group: filteredGroups[index],
|
||||||
isHighlighted: selectedGroupIndex == 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:flutter/material.dart';
|
||||||
import 'package:game_tracker/core/custom_theme.dart';
|
import 'package:game_tracker/core/custom_theme.dart';
|
||||||
import 'package:game_tracker/core/enums.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 {
|
class ChooseRulesetView extends StatefulWidget {
|
||||||
final List<(Ruleset, String, String)> rulesets;
|
final List<(Ruleset, String)> rulesets;
|
||||||
final int initialRulesetIndex;
|
final int initialRulesetIndex;
|
||||||
|
|
||||||
const ChooseRulesetView({
|
const ChooseRulesetView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.rulesets,
|
required this.rulesets,
|
||||||
@@ -35,85 +36,42 @@ class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: CustomTheme.backgroundColor,
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
selectedRulesetIndex == -1
|
||||||
|
? null
|
||||||
|
: widget.rulesets[selectedRulesetIndex].$1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Choose Ruleset',
|
'Choose Ruleset',
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Column(
|
body: ListView.builder(
|
||||||
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),
|
padding: const EdgeInsets.only(bottom: 85),
|
||||||
itemCount: widget.rulesets.length,
|
itemCount: widget.rulesets.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return RulesetListTile(
|
return TitleDescriptionListTile(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
if (selectedRulesetIndex == index) {
|
||||||
|
selectedRulesetIndex = -1;
|
||||||
|
} else {
|
||||||
selectedRulesetIndex = index;
|
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,
|
title: translateRulesetToString(widget.rulesets[index].$1),
|
||||||
description: widget.rulesets[index].$3,
|
description: widget.rulesets[index].$2,
|
||||||
isHighlighted: selectedRulesetIndex == index,
|
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:flutter/material.dart';
|
||||||
import 'package:game_tracker/core/custom_theme.dart';
|
import 'package:game_tracker/core/custom_theme.dart';
|
||||||
import 'package:game_tracker/core/enums.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/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/views/main_menu/game_result_view.dart';
|
||||||
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.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/player_selection.dart';
|
||||||
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.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';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class CreateGameView extends StatefulWidget {
|
class CreateGameView extends StatefulWidget {
|
||||||
const CreateGameView({super.key});
|
final VoidCallback? onWinnerChanged;
|
||||||
|
const CreateGameView({super.key, this.onWinnerChanged});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateGameView> createState() => _CreateGameViewState();
|
State<CreateGameView> createState() => _CreateGameViewState();
|
||||||
@@ -39,12 +43,18 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
/// List of all players from the database
|
/// List of all players from the database
|
||||||
List<Player> playerList = [];
|
List<Player> playerList = [];
|
||||||
|
|
||||||
|
/// 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
|
/// The currently selected group
|
||||||
Group? selectedGroup;
|
Group? selectedGroup;
|
||||||
|
|
||||||
/// The index of the currently selected group in [groupsList] to mark it in
|
/// The index of the currently selected group in [groupsList] to mark it in
|
||||||
/// the [ChooseGroupView]
|
/// the [ChooseGroupView]
|
||||||
int selectedGroupIndex = -1;
|
String selectedGroupId = '';
|
||||||
|
|
||||||
/// The currently selected ruleset
|
/// The currently selected ruleset
|
||||||
Ruleset? selectedRuleset;
|
Ruleset? selectedRuleset;
|
||||||
@@ -53,37 +63,48 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
/// the [ChooseRulesetView]
|
/// the [ChooseRulesetView]
|
||||||
int selectedRulesetIndex = -1;
|
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
|
/// The currently selected players
|
||||||
List<Player>? selectedPlayers;
|
List<Player>? selectedPlayers;
|
||||||
|
|
||||||
/// List of available rulesets with their display names and descriptions
|
/// List of available rulesets with their descriptions
|
||||||
/// as tuples of (Ruleset, String, String)
|
/// as tuples of (Ruleset, String)
|
||||||
List<(Ruleset, String, String)> rulesets = [
|
/// TODO: Replace when rulesets are implemented
|
||||||
|
List<(Ruleset, String)> rulesets = [
|
||||||
(
|
(
|
||||||
Ruleset.singleWinner,
|
Ruleset.singleWinner,
|
||||||
'Single Winner',
|
|
||||||
'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.',
|
'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Ruleset.singleLoser,
|
Ruleset.singleLoser,
|
||||||
'Single Loser',
|
|
||||||
'Exactly one loser is determined; last place receives the penalty or consequence.',
|
'Exactly one loser is determined; last place receives the penalty or consequence.',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Ruleset.mostPoints,
|
Ruleset.mostPoints,
|
||||||
'Most Points',
|
|
||||||
'Traditional ruleset: the player with the most points wins.',
|
'Traditional ruleset: the player with the most points wins.',
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Ruleset.lastPoints,
|
Ruleset.leastPoints,
|
||||||
'Least Points',
|
|
||||||
'Inverse scoring: the player with the fewest points wins.',
|
'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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_gameNameController.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
db = Provider.of<AppDatabase>(context, listen: false);
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
|
||||||
_allGroupsFuture = db.groupDao.getAllGroups();
|
_allGroupsFuture = db.groupDao.getAllGroups();
|
||||||
@@ -93,6 +114,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
groupsList = result[0] as List<Group>;
|
groupsList = result[0] as List<Group>;
|
||||||
playerList = result[1] as List<Player>;
|
playerList = result[1] as List<Player>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
filteredPlayerList = List.from(playerList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -113,15 +136,38 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||||
child: TextInputField(
|
child: TextInputField(
|
||||||
controller: _gameNameController,
|
controller: _gameNameController,
|
||||||
hintText: 'Game name',
|
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(
|
ChooseTile(
|
||||||
title: 'Ruleset',
|
title: 'Ruleset',
|
||||||
trailingText: selectedRuleset == null
|
trailingText: selectedRuleset == null
|
||||||
@@ -139,6 +185,7 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
selectedRulesetIndex = rulesets.indexWhere(
|
selectedRulesetIndex = rulesets.indexWhere(
|
||||||
(r) => r.$1 == selectedRuleset,
|
(r) => r.$1 == selectedRuleset,
|
||||||
);
|
);
|
||||||
|
selectedGameIndex = -1;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -152,28 +199,28 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ChooseGroupView(
|
builder: (context) => ChooseGroupView(
|
||||||
groups: groupsList,
|
groups: groupsList,
|
||||||
initialGroupIndex: selectedGroupIndex,
|
initialGroupId: selectedGroupId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
selectedGroupIndex = groupsList.indexWhere(
|
selectedGroupId = selectedGroup?.id ?? '';
|
||||||
(g) => g.id == selectedGroup?.id,
|
if (selectedGroup != null) {
|
||||||
);
|
filteredPlayerList = playerList
|
||||||
|
.where(
|
||||||
|
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
filteredPlayerList = List.from(playerList);
|
||||||
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PlayerSelection(
|
child: PlayerSelection(
|
||||||
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||||
initialPlayers: selectedGroup == null
|
initialSelectedPlayers: selectedPlayers ?? [],
|
||||||
? playerList
|
availablePlayers: filteredPlayerList,
|
||||||
: playerList
|
|
||||||
.where(
|
|
||||||
(p) => !selectedGroup!.members.any(
|
|
||||||
(m) => m.id == p.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPlayers = value;
|
selectedPlayers = value;
|
||||||
@@ -181,7 +228,6 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
CustomWidthButton(
|
CustomWidthButton(
|
||||||
text: 'Create game',
|
text: 'Create game',
|
||||||
sizeRelativeToWidth: 0.95,
|
sizeRelativeToWidth: 0.95,
|
||||||
@@ -191,42 +237,37 @@ class _CreateGameViewState extends State<CreateGameView> {
|
|||||||
Game game = Game(
|
Game game = Game(
|
||||||
name: _gameNameController.text.trim(),
|
name: _gameNameController.text.trim(),
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
group: selectedGroup!,
|
group: selectedGroup,
|
||||||
players: selectedPlayers,
|
players: selectedPlayers,
|
||||||
);
|
);
|
||||||
// TODO: Replace with navigation to GameResultView()
|
await db.gameDao.addGame(game: game);
|
||||||
print('Created game: $game');
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (context) => GameResultView(
|
||||||
|
game: game,
|
||||||
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: 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
|
/// 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() {
|
||||||
return _gameNameController.text.isNotEmpty &&
|
return _gameNameController.text.isNotEmpty &&
|
||||||
(selectedGroup != null ||
|
(selectedGroup != null ||
|
||||||
(selectedPlayers != null && selectedPlayers!.isNotEmpty)) &&
|
(selectedPlayers != null && selectedPlayers!.length > 1)) &&
|
||||||
selectedRuleset != null;
|
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/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_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/views/main_menu/game_result_view.dart';
|
||||||
import 'package:game_tracker/presentation/widgets/app_skeleton.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/buttons/custom_width_button.dart';
|
||||||
@@ -98,17 +98,16 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
|||||||
}
|
}
|
||||||
return GameHistoryTile(
|
return GameHistoryTile(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
CupertinoPageRoute(
|
CupertinoPageRoute(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (context) =>
|
builder: (context) => GameResultView(
|
||||||
GameResultView(game: games[index]),
|
game: games[index],
|
||||||
|
onWinnerChanged: refreshGameList,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
setState(() {
|
|
||||||
_gameListFuture = db.gameDao.getAllGames();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
game: games[index],
|
game: games[index],
|
||||||
);
|
);
|
||||||
@@ -123,17 +122,13 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
|||||||
text: 'Create Game',
|
text: 'Create Game',
|
||||||
sizeRelativeToWidth: 0.90,
|
sizeRelativeToWidth: 0.90,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) =>
|
||||||
return const CreateGroupView();
|
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 {
|
class GameResultView extends StatefulWidget {
|
||||||
final Game game;
|
final Game game;
|
||||||
|
|
||||||
const GameResultView({super.key, required this.game});
|
final VoidCallback? onWinnerChanged;
|
||||||
|
|
||||||
|
const GameResultView({super.key, required this.game, this.onWinnerChanged});
|
||||||
@override
|
@override
|
||||||
State<GameResultView> createState() => _GameResultViewState();
|
State<GameResultView> createState() => _GameResultViewState();
|
||||||
}
|
}
|
||||||
@@ -131,6 +132,7 @@ class _GameResultViewState extends State<GameResultView> {
|
|||||||
winnerId: _selectedPlayer!.id,
|
winnerId: _selectedPlayer!.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
widget.onWinnerChanged?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Player> getAllPlayers(Game game) {
|
List<Player> getAllPlayers(Game game) {
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
class PlayerSelection extends StatefulWidget {
|
class PlayerSelection extends StatefulWidget {
|
||||||
final Function(List<Player> value) onChanged;
|
final Function(List<Player> value) onChanged;
|
||||||
final List<Player> initialPlayers;
|
final List<Player> availablePlayers;
|
||||||
|
final List<Player>? initialSelectedPlayers;
|
||||||
|
|
||||||
const PlayerSelection({
|
const PlayerSelection({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
this.initialPlayers = const [],
|
this.availablePlayers = const [],
|
||||||
|
this.initialSelectedPlayers,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -51,10 +53,24 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
suggestedPlayers = skeletonData;
|
suggestedPlayers = skeletonData;
|
||||||
_allPlayersFuture.then((loadedPlayers) {
|
_allPlayersFuture.then((loadedPlayers) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.initialPlayers.isNotEmpty) {
|
// If a list of available players is provided, use that list.
|
||||||
allPlayers = [...widget.initialPlayers];
|
if (widget.availablePlayers.isNotEmpty) {
|
||||||
suggestedPlayers = [...widget.initialPlayers];
|
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 {
|
} else {
|
||||||
|
// Otherwise, use the loaded players from the database.
|
||||||
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||||
allPlayers = [...loadedPlayers];
|
allPlayers = [...loadedPlayers];
|
||||||
suggestedPlayers = [...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,
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user
Warum lässt sich in Choose Game View suchen, im Choose Group View aber nicht? Können beides ja potenziell sehr lange Listen sein
Erledigt (So würde dass dann auch für die Games funktionieren)