From b5e7fe23ab335388eb8ee28fe781dc77fab3b899 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:10:08 +0100 Subject: [PATCH 01/34] changed fullwidthbutton to include size, borderColor and infillColor attributes --- .../widgets/full_width_button.dart | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index bd18c64..fc2ca78 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -1,10 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; class FullWidthButton extends StatelessWidget { - const FullWidthButton({super.key, required this.text, this.onPressed}); + const FullWidthButton({ + super.key, + required this.text, + required this.borderColor, + required this.infillColor, + required this.sizeRelativeToWidth, + required this.onPressed, + }); final String text; + final Color borderColor; + final Color infillColor; + final double sizeRelativeToWidth; final VoidCallback? onPressed; @override @@ -12,8 +21,12 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - minimumSize: Size(MediaQuery.sizeOf(context).width * 0.9, 60), - backgroundColor: CustomTheme.primaryColor, + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + backgroundColor: infillColor, + side: BorderSide(color: borderColor, width: 2), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( From a54495f915447d0b0b42cfa959b751dc77eb87da Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:10:33 +0100 Subject: [PATCH 02/34] implemented basic CreateGroupView without functionality --- .../create_group/create_group_view.dart | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 lib/presentation/views/main_menu/create_group/create_group_view.dart diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart new file mode 100644 index 0000000..63daf62 --- /dev/null +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -0,0 +1,309 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:provider/provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class CreateGroupView extends StatefulWidget { + const CreateGroupView({super.key}); + + @override + State createState() => _CreateGroupViewState(); +} + +class _CreateGroupViewState extends State { + List selectedPlayers = [ + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + Player(id: '0', name: 'Player 0'), + ]; + late Future> _allPlayersFuture; + late final List skeletonData = List.filled( + 7, + Player(id: '0', name: 'Player 0'), + ); + + @override + @override + void initState() { + super.initState(); + final db = Provider.of(context, listen: false); + _allPlayersFuture = db.playerDao.getAllPlayers(); + } + + @override + Widget build(BuildContext context) { + addSamplePlayers(context); + return SafeArea( + child: Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + title: const Text( + "Create new group", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + child: TextField( + decoration: InputDecoration( + filled: true, + fillColor: CustomTheme.boxColor, + hint: Text("Group name", style: TextStyle(fontSize: 18)), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ), + ), + Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SearchBar( + constraints: BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: "Search for players", + hintStyle: WidgetStateProperty.all( + TextStyle(fontSize: 16), + ), + leading: Icon(Icons.search), + backgroundColor: WidgetStateProperty.all( + CustomTheme.boxColor, + ), + side: WidgetStateProperty.all( + BorderSide(color: CustomTheme.boxBorder), + ), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + SizedBox(height: 10), + Text( + "Ausgewählte Spieler: (X)", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var player in selectedPlayers) + Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 12), + Text( + player.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(width: 3), + GestureDetector( + child: const Icon(Icons.close, size: 20), + onTap: () { + setState(() { + selectedPlayers.remove(player); + }); + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Text( + "Alle Spieler:", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10), + FutureBuilder( + future: _allPlayersFuture, + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == + ConnectionState.waiting; + final List players = isLoading + ? skeletonData + : (snapshot.data ?? []); + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: + const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher + .defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: ListView.builder( + itemCount: players.length, + itemBuilder: + (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + players[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + IconButton( + icon: Icon(Icons.add, size: 20), + onPressed: () {}, + ), + ], + ), + ); //GroupTile(group: groups[index]); + }, + ), + ), + ); + }, + ), + ], + ), + ), + ), + FullWidthButton( + text: "Create group", + infillColor: CustomTheme.primaryColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.95, + onPressed: () {}, + ), + SizedBox(height: 10), + FullWidthButton( + text: "Cancel", + infillColor: CustomTheme.boxColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.95, + onPressed: () { + Navigator.pop(context); + }, + ), + SizedBox(height: 20), + ], + ), + ), + ); + } + + Future addSamplePlayers(BuildContext context) async { + final db = Provider.of(context, listen: false); + final playerCount = await db.playerDao.getPlayerCount(); + if (playerCount == 0) { + for (int i = 1; i <= 10; i++) { + final player = Player(id: '$i', name: 'Spieler $i'); + await db.playerDao.addPlayer(player: player); + } + print("10 Beispiel-Spieler wurden zur Datenbank hinzugefügt."); + final players = await db.playerDao.getAllPlayers(); + for (int i = 0; i < players.length; i++) { + print(players[i]); + } + } + } +} From e4de8fdb253906e14723e955ec01aa9fd7df358c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 13:11:47 +0100 Subject: [PATCH 03/34] added missing attributes for FullWidthButton in groups view --- .../views/main_menu/groups_view.dart | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7f1f32d..7c5e3d5 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.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/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -103,7 +104,22 @@ class _GroupsViewState extends State { Positioned( bottom: 80, - child: FullWidthButton(text: 'Create Group', onPressed: () {}), + child: FullWidthButton( + text: 'Create Group', + infillColor: CustomTheme.primaryColor, + borderColor: CustomTheme.primaryColor, + sizeRelativeToWidth: 0.90, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); + }, + ), ), ], ), From 47bb090e725be017e0a8e252c35e25b26b6e9ef2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 18:23:07 +0100 Subject: [PATCH 04/34] added option to choose disabledBackgroundColor in FullWidthButton --- lib/presentation/widgets/full_width_button.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/full_width_button.dart index fc2ca78..fe9913c 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/full_width_button.dart @@ -6,6 +6,7 @@ class FullWidthButton extends StatelessWidget { required this.text, required this.borderColor, required this.infillColor, + this.disabledInfillColor, required this.sizeRelativeToWidth, required this.onPressed, }); @@ -13,6 +14,7 @@ class FullWidthButton extends StatelessWidget { final String text; final Color borderColor; final Color infillColor; + final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; @@ -21,6 +23,7 @@ class FullWidthButton extends StatelessWidget { return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( + disabledBackgroundColor: disabledInfillColor, minimumSize: Size( MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, From 35f2f8754ae7d2b4f6644e8cf0e34035fd8663e1 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 18:24:30 +0100 Subject: [PATCH 05/34] added functionality to create group --- .../create_group/create_group_view.dart | 197 +++++++++++++----- 1 file changed, 142 insertions(+), 55 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 63daf62..13f1b7c 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -15,24 +16,30 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - List selectedPlayers = [ - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - Player(id: '0', name: 'Player 0'), - ]; + List selectedPlayers = []; + List suggestedPlayers = []; + List allPlayers = []; + late final AppDatabase db; late Future> _allPlayersFuture; late final List skeletonData = List.filled( 7, Player(id: '0', name: 'Player 0'), ); + final _groupNameController = TextEditingController(); + final _searchBarController = TextEditingController(); @override @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); _allPlayersFuture = db.playerDao.getAllPlayers(); + _allPlayersFuture.then((loadedPlayers) { + setState(() { + allPlayers = loadedPlayers; + suggestedPlayers = loadedPlayers; + }); + }); } @override @@ -55,6 +62,10 @@ class _CreateGroupViewState extends State { Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextField( + controller: _groupNameController, + onChanged: (value) { + setState(() {}); + }, decoration: InputDecoration( filled: true, fillColor: CustomTheme.boxColor, @@ -90,6 +101,7 @@ class _CreateGroupViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SearchBar( + controller: _searchBarController, constraints: BoxConstraints(maxHeight: 45, minHeight: 45), hintText: "Search for players", hintStyle: WidgetStateProperty.all( @@ -107,10 +119,23 @@ class _CreateGroupViewState extends State { borderRadius: BorderRadius.circular(12), ), ), + onChanged: (value) { + setState(() { + if (value.isEmpty) { + suggestedPlayers = allPlayers; + } else { + suggestedPlayers = allPlayers.where((player) { + return player.name.toLowerCase().contains( + value.toLowerCase(), + ); + }).toList(); + } + }); + }, ), SizedBox(height: 10), Text( - "Ausgewählte Spieler: (X)", + "Ausgewählte Spieler: (${selectedPlayers.length})", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -123,7 +148,7 @@ class _CreateGroupViewState extends State { spacing: 8.0, runSpacing: 8.0, children: [ - for (var player in selectedPlayers) + for (var selectedPlayer in selectedPlayers) Container( padding: EdgeInsets.all(5), decoration: BoxDecoration( @@ -136,7 +161,7 @@ class _CreateGroupViewState extends State { children: [ SizedBox(width: 12), Text( - player.name, + selectedPlayer.name, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, @@ -147,7 +172,7 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { - selectedPlayers.remove(player); + selectedPlayers.remove(selectedPlayer); }); }, ), @@ -183,7 +208,10 @@ class _CreateGroupViewState extends State { } if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { + (!snapshot.hasData || + snapshot.data!.isEmpty || + (suggestedPlayers.isEmpty && + allPlayers.isEmpty))) { return const Center( child: TopCenteredMessage( icon: Icons.info, @@ -195,9 +223,6 @@ class _CreateGroupViewState extends State { final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List players = isLoading - ? skeletonData - : (snapshot.data ?? []); return Expanded( child: Skeletonizer( effect: PulseEffect( @@ -217,48 +242,69 @@ class _CreateGroupViewState extends State { layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, ), - child: ListView.builder( - itemCount: players.length, - itemBuilder: - (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, ), - borderRadius: BorderRadius.circular( - 12, + padding: const EdgeInsets.symmetric( + horizontal: 10, ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - players[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: + BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + suggestedPlayers[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), - ), - IconButton( - icon: Icon(Icons.add, size: 20), - onPressed: () {}, - ), - ], - ), - ); //GroupTile(group: groups[index]); - }, - ), + IconButton( + icon: Icon( + Icons.add, + size: 20, + ), + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + } + }); + }, + ), + ], + ), + ); + }, + ), ), ); }, @@ -271,14 +317,39 @@ class _CreateGroupViewState extends State { text: "Create group", infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, + disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, - onPressed: () {}, + onPressed: + (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) + ? null + : () { + String id = "ID_" + _groupNameController.text; + String name = _groupNameController.text; + List members = selectedPlayers; + db.groupDao.addGroup( + group: Group(id: id, name: name, members: members), + ); + print(name); + print(id); + for (int i = 0; i < members.length; i++) { + print(members[i].name); + print(members[i].id); + } + if (true) { + //eigentlich wenn create group erfolgreich + _groupNameController.clear(); + _searchBarController.clear(); + selectedPlayers.clear(); + } + setState(() {}); + }, ), SizedBox(height: 10), FullWidthButton( text: "Cancel", infillColor: CustomTheme.boxColor, borderColor: CustomTheme.primaryColor, + disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, onPressed: () { Navigator.pop(context); @@ -293,6 +364,22 @@ class _CreateGroupViewState extends State { Future addSamplePlayers(BuildContext context) async { final db = Provider.of(context, listen: false); + /*await db.groupDao.addGroup( + group: Group( + id: "dg1", + name: "Debug Gruppe 1", + members: [ + Player(id: '1', name: 'Spieler 1'), + Player(id: '2', name: 'Spieler 2'), + Player(id: '3', name: 'Spieler 3'), + ], + ), + ); + final group = await db.groupDao.getGroupById(groupId: "dg1"); + print(group.name); + print(group.id); + print(group.members.length); + */ final playerCount = await db.playerDao.getPlayerCount(); if (playerCount == 0) { for (int i = 1; i <= 10; i++) { From a7f6a53b9ccc20a21b64414b3dd7ef707b085671 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 19:00:38 +0100 Subject: [PATCH 06/34] removed double @override --- .../views/main_menu/create_group/create_group_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 13f1b7c..631f3ae 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -28,7 +28,6 @@ class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); - @override @override void initState() { super.initState(); From f8b6c00d5d8f1ceaa67f3862f1d379e2241a327b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 17 Nov 2025 19:26:36 +0100 Subject: [PATCH 07/34] Fixed return --- lib/data/dao/group_dao.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 8eb3a1a..cc680a3 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -60,8 +60,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { await Future.wait( group.members.map((player) => db.playerDao.addPlayer(player: player)), ); - return true; }); + return true; } return false; } From 6b2fb18ec095b3feeffe344f7ecdd9caabf4faa1 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 20:20:56 +0100 Subject: [PATCH 08/34] fixed groups not getting added & added feature to remove player from all players when selected --- .../create_group/create_group_view.dart | 67 +++++++++---------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 631f3ae..b27fb6a 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -35,6 +35,7 @@ class _CreateGroupViewState extends State { _allPlayersFuture = db.playerDao.getAllPlayers(); _allPlayersFuture.then((loadedPlayers) { setState(() { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = loadedPlayers; suggestedPlayers = loadedPlayers; }); @@ -171,7 +172,11 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { + suggestedPlayers.add(selectedPlayer); selectedPlayers.remove(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); }); }, ), @@ -295,6 +300,15 @@ class _CreateGroupViewState extends State { selectedPlayers.add( suggestedPlayers[index], ); + selectedPlayers.sort( + (a, b) => + a.name.compareTo( + b.name, + ), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); } }); }, @@ -321,39 +335,34 @@ class _CreateGroupViewState extends State { onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null - : () { + : () async { String id = "ID_" + _groupNameController.text; String name = _groupNameController.text; List members = selectedPlayers; - db.groupDao.addGroup( + bool success = await db.groupDao.addGroup( group: Group(id: id, name: name, members: members), ); - print(name); - print(id); - for (int i = 0; i < members.length; i++) { - print(members[i].name); - print(members[i].id); - } - if (true) { - //eigentlich wenn create group erfolgreich + if (success) { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + "Error while creating group, please try again", + style: TextStyle(color: Colors.white), + ), + ), + ), + ); } setState(() {}); }, ), - SizedBox(height: 10), - FullWidthButton( - text: "Cancel", - infillColor: CustomTheme.boxColor, - borderColor: CustomTheme.primaryColor, - disabledInfillColor: CustomTheme.boxColor, - sizeRelativeToWidth: 0.95, - onPressed: () { - Navigator.pop(context); - }, - ), SizedBox(height: 20), ], ), @@ -363,22 +372,6 @@ class _CreateGroupViewState extends State { Future addSamplePlayers(BuildContext context) async { final db = Provider.of(context, listen: false); - /*await db.groupDao.addGroup( - group: Group( - id: "dg1", - name: "Debug Gruppe 1", - members: [ - Player(id: '1', name: 'Spieler 1'), - Player(id: '2', name: 'Spieler 2'), - Player(id: '3', name: 'Spieler 3'), - ], - ), - ); - final group = await db.groupDao.getGroupById(groupId: "dg1"); - print(group.name); - print(group.id); - print(group.members.length); - */ final playerCount = await db.playerDao.getPlayerCount(); if (playerCount == 0) { for (int i = 1; i <= 10; i++) { From c3a2ac77b0de0918a3d38c6cc72ded946de6d08d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 21:19:17 +0100 Subject: [PATCH 09/34] fixed color change in appbar when scrolling --- .../views/main_menu/create_group/create_group_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index b27fb6a..316f2d3 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -50,6 +50,7 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, title: const Text( "Create new group", style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), From 3e89bfd641e5245264a5250fa7b320cb5072b127 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 17 Nov 2025 21:34:51 +0100 Subject: [PATCH 10/34] added info message for when all players are selected --- .../create_group/create_group_view.dart | 241 +++++++++--------- 1 file changed, 116 insertions(+), 125 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 316f2d3..907671b 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -36,8 +36,8 @@ class _CreateGroupViewState extends State { _allPlayersFuture.then((loadedPlayers) { setState(() { loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = loadedPlayers; - suggestedPlayers = loadedPlayers; + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; }); }); } @@ -123,7 +123,7 @@ class _CreateGroupViewState extends State { onChanged: (value) { setState(() { if (value.isEmpty) { - suggestedPlayers = allPlayers; + suggestedPlayers = [...allPlayers]; } else { suggestedPlayers = allPlayers.where((player) { return player.name.toLowerCase().contains( @@ -197,131 +197,122 @@ class _CreateGroupViewState extends State { SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), - ); - } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (suggestedPlayers.isEmpty && - allPlayers.isEmpty))) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == - ConnectionState.waiting; - return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), - child: - (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) - ? TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - 'No players found with that name.', - ) - : ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty || + (selectedPlayers.isEmpty && + allPlayers.isEmpty))) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: + AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return Container( + margin: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 5, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all( + color: CustomTheme.boxBorder, + ), + borderRadius: BorderRadius.circular( + 12, + ), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + suggestedPlayers[index].name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, ), - borderRadius: - BorderRadius.circular(12), ), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - suggestedPlayers[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - IconButton( - icon: Icon( - Icons.add, - size: 20, - ), - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => - a.name.compareTo( - b.name, - ), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ), - ], + IconButton( + icon: Icon(Icons.add, size: 20), + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name + .compareTo(b.name), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, ), - ); - }, - ), - ), - ); - }, + ], + ), + ); + }, + ), + ), + ); + }, ), ], ), From c31d757615547a89344387c8eca2324898970b15 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:00:09 +0100 Subject: [PATCH 11/34] fixed renderoverflow for long player & group names in create group view and group view --- .../create_group/create_group_view.dart | 32 +++++++++++------ .../widgets/tiles/group_tile.dart | 36 +++++++++++-------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 907671b..823b6ff 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -70,7 +70,11 @@ class _CreateGroupViewState extends State { decoration: InputDecoration( filled: true, fillColor: CustomTheme.boxColor, - hint: Text("Group name", style: TextStyle(fontSize: 18)), + hint: Text( + "Group name", + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 18), + ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(12)), borderSide: BorderSide(color: CustomTheme.boxBorder), @@ -161,11 +165,14 @@ class _CreateGroupViewState extends State { mainAxisSize: MainAxisSize.min, children: [ SizedBox(width: 12), - Text( - selectedPlayer.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, + Flexible( + child: Text( + selectedPlayer.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), ), SizedBox(width: 3), @@ -277,11 +284,14 @@ class _CreateGroupViewState extends State { MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ - Text( - suggestedPlayers[index].name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, + Flexible( + child: Text( + suggestedPlayers[index].name, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), ), ), IconButton( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 448c68c..12d016e 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -24,24 +24,29 @@ class GroupTile extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - group.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + Flexible( + child: Text( + group.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), ), - const Spacer(), - Text( - '${group.members.length}', - style: const TextStyle( - fontWeight: FontWeight.w900, - fontSize: 18, - ), + Row( + children: [ + Text( + '${group.members.length}', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + const SizedBox(width: 3), + const Icon(Icons.group, size: 22), + ], ), - const SizedBox(width: 3), - const Icon(Icons.group, size: 22), ], ), const SizedBox(height: 5), @@ -64,6 +69,7 @@ class GroupTile extends StatelessWidget { child: Skeleton.ignore( child: Text( member.name, + overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, From 412d1fd334b5475f5f1fd4f02c9ab5c7c6d639f8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:08:07 +0100 Subject: [PATCH 12/34] fixed search bugs where duplicates where created or search results were wrong --- .../create_group/create_group_view.dart | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 823b6ff..87ef5bc 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -130,9 +130,12 @@ class _CreateGroupViewState extends State { suggestedPlayers = [...allPlayers]; } else { suggestedPlayers = allPlayers.where((player) { - return player.name.toLowerCase().contains( - value.toLowerCase(), - ); + final bool nameMatches = player.name + .toLowerCase() + .contains(value.toLowerCase()); + final bool isNotSelected = !selectedPlayers + .contains(player); + return nameMatches && isNotSelected; }).toList(); } }); @@ -180,11 +183,19 @@ class _CreateGroupViewState extends State { child: const Icon(Icons.close, size: 20), onTap: () { setState(() { - suggestedPlayers.add(selectedPlayer); + final currentSearch = _searchBarController + .text + .toLowerCase(); selectedPlayers.remove(selectedPlayer); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); + if (currentSearch.isEmpty || + selectedPlayer.name + .toLowerCase() + .contains(currentSearch)) { + suggestedPlayers.add(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } }); }, ), From a5e508dbdaedb2e22039f0015de786d2e85b5a84 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:29:24 +0100 Subject: [PATCH 13/34] Refresh group list after adding a new group --- lib/presentation/views/main_menu/groups_view.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7c5e3d5..bdb3d4a 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -19,6 +19,7 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late Future> _allGroupsFuture; + late final AppDatabase db; final player = Player(id: 'p1', name: 'Sample'); late final List skeletonData = List.filled( @@ -33,7 +34,7 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); + db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); } @@ -109,8 +110,8 @@ class _GroupsViewState extends State { infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, sizeRelativeToWidth: 0.90, - onPressed: () { - Navigator.push( + onPressed: () async { + await Navigator.push( context, MaterialPageRoute( builder: (context) { @@ -118,6 +119,9 @@ class _GroupsViewState extends State { }, ), ); + setState(() { + _allGroupsFuture = db.groupDao.getAllGroups(); + }); }, ), ), From 05c41707ca96f997f3608fc1206fc8e22913abda Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 17:34:09 +0100 Subject: [PATCH 14/34] Refactor: Remove sample player generation code --- .../create_group/create_group_view.dart | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 87ef5bc..1f616a2 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -44,7 +44,6 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { - addSamplePlayers(context); return SafeArea( child: Scaffold( backgroundColor: CustomTheme.backgroundColor, @@ -382,20 +381,4 @@ class _CreateGroupViewState extends State { ), ); } - - Future addSamplePlayers(BuildContext context) async { - final db = Provider.of(context, listen: false); - final playerCount = await db.playerDao.getPlayerCount(); - if (playerCount == 0) { - for (int i = 1; i <= 10; i++) { - final player = Player(id: '$i', name: 'Spieler $i'); - await db.playerDao.addPlayer(player: player); - } - print("10 Beispiel-Spieler wurden zur Datenbank hinzugefügt."); - final players = await db.playerDao.getAllPlayers(); - for (int i = 0; i < players.length; i++) { - print(players[i]); - } - } - } } From 1882d0007bd42daadec4f95bd749640203dcdf88 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:09:57 +0100 Subject: [PATCH 15/34] created widgets for search bar list tile, selected tile and text input field in create groups view --- .../widgets/custom_search_bar.dart | 36 +++++++++++++++ .../widgets/text_input_field.dart | 38 ++++++++++++++++ .../widgets/tiles/text_icon_list_tile.dart | 42 ++++++++++++++++++ .../widgets/tiles/text_icon_tile.dart | 44 +++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 lib/presentation/widgets/custom_search_bar.dart create mode 100644 lib/presentation/widgets/text_input_field.dart create mode 100644 lib/presentation/widgets/tiles/text_icon_list_tile.dart create mode 100644 lib/presentation/widgets/tiles/text_icon_tile.dart diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart new file mode 100644 index 0000000..d0f66e8 --- /dev/null +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class CustomSearchBar extends StatelessWidget { + final TextEditingController controller; + final String hintText; + final ValueChanged? onChanged; + final BoxConstraints? constraints; + + const CustomSearchBar({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + this.constraints, + }); + + @override + Widget build(BuildContext context) { + return SearchBar( + controller: controller, + constraints: + constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: hintText, + onChanged: onChanged, + hintStyle: MaterialStateProperty.all(const TextStyle(fontSize: 16)), + leading: const Icon(Icons.search), + backgroundColor: MaterialStateProperty.all(CustomTheme.boxColor), + side: MaterialStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + elevation: MaterialStateProperty.all(0), + ); + } +} diff --git a/lib/presentation/widgets/text_input_field.dart b/lib/presentation/widgets/text_input_field.dart new file mode 100644 index 0000000..6cd9d75 --- /dev/null +++ b/lib/presentation/widgets/text_input_field.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextInputField extends StatelessWidget { + final TextEditingController controller; + final ValueChanged? onChanged; + final String hintText; + + const TextInputField({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + onChanged: onChanged, + decoration: InputDecoration( + filled: true, + fillColor: CustomTheme.boxColor, + hintText: hintText, + hintStyle: const TextStyle(fontSize: 18), + enabledBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorder), + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart new file mode 100644 index 0000000..b32504f --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class IconListTile extends StatelessWidget { + final String text; + final IconData icon; + final VoidCallback onPressed; + + const IconListTile({ + super.key, + required this.text, + required this.icon, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + ), + IconButton(icon: Icon(icon, size: 20), onPressed: onPressed), + ], + ), + ); + } +} diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart new file mode 100644 index 0000000..52174bd --- /dev/null +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +class TextIconTile extends StatelessWidget { + final String text; + final IconData? icon; + final VoidCallback? onIconTap; + + const TextIconTile({ + super.key, + required this.text, + this.icon, + this.onIconTap, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) const SizedBox(width: 3), + Flexible( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + ), + if (icon != null) ...[ + const SizedBox(width: 3), + GestureDetector(onTap: onIconTap, child: Icon(icon, size: 20)), + ], + ], + ), + ); + } +} From 8f9289617f63b4f66d5131a4f9e8e61df8f2c0ac Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:10:26 +0100 Subject: [PATCH 16/34] changed group tile to use standardized text icon tile --- .../widgets/tiles/group_tile.dart | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 12d016e..d87cc12 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; -import 'package:skeletonizer/skeletonizer.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group}); @@ -56,27 +56,7 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) - Container( - padding: const EdgeInsets.symmetric( - vertical: 5, - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.onBoxColor, - borderRadius: BorderRadius.circular(12), - ), - child: Skeleton.ignore( - child: Text( - member.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - ), + for (var member in group.members) TextIconTile(text: member.name), ], ), const SizedBox(height: 2.5), From 77812842893db5d4c122761d5a74580331dc1639 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:10:48 +0100 Subject: [PATCH 17/34] changed to use standardized tiles and fixed search bug --- .../create_group/create_group_view.dart | 330 +++++++----------- 1 file changed, 128 insertions(+), 202 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 1f616a2..1a84956 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -3,10 +3,15 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/text_input_field.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; +import 'package:uuid/uuid.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -61,29 +66,12 @@ class _CreateGroupViewState extends State { children: [ Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: TextField( + child: TextInputField( controller: _groupNameController, + hintText: 'Group name', onChanged: (value) { setState(() {}); }, - decoration: InputDecoration( - filled: true, - fillColor: CustomTheme.boxColor, - hint: Text( - "Group name", - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 18), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - ), ), ), Expanded( @@ -104,29 +92,16 @@ class _CreateGroupViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SearchBar( + CustomSearchBar( controller: _searchBarController, constraints: BoxConstraints(maxHeight: 45, minHeight: 45), hintText: "Search for players", - hintStyle: WidgetStateProperty.all( - TextStyle(fontSize: 16), - ), - leading: Icon(Icons.search), - backgroundColor: WidgetStateProperty.all( - CustomTheme.boxColor, - ), - side: WidgetStateProperty.all( - BorderSide(color: CustomTheme.boxBorder), - ), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), onChanged: (value) { setState(() { if (value.isEmpty) { - suggestedPlayers = [...allPlayers]; + suggestedPlayers = allPlayers.where((player) { + return !selectedPlayers.contains(player); + }).toList(); } else { suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name @@ -156,50 +131,25 @@ class _CreateGroupViewState extends State { runSpacing: 8.0, children: [ for (var selectedPlayer in selectedPlayers) - Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - color: CustomTheme.onBoxColor, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(width: 12), - Flexible( - child: Text( - selectedPlayer.name, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - SizedBox(width: 3), - GestureDetector( - child: const Icon(Icons.close, size: 20), - onTap: () { - setState(() { - final currentSearch = _searchBarController - .text - .toLowerCase(); - selectedPlayers.remove(selectedPlayer); - if (currentSearch.isEmpty || - selectedPlayer.name - .toLowerCase() - .contains(currentSearch)) { - suggestedPlayers.add(selectedPlayer); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); - } - }); - }, - ), - ], - ), + TextIconTile( + text: selectedPlayer.name, + icon: Icons.close, + onIconTap: () { + setState(() { + final currentSearch = _searchBarController.text + .toLowerCase(); + selectedPlayers.remove(selectedPlayer); + if (currentSearch.isEmpty || + selectedPlayer.name.toLowerCase().contains( + currentSearch, + )) { + suggestedPlayers.add(selectedPlayer); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } + }); + }, ), ], ), @@ -214,125 +164,100 @@ class _CreateGroupViewState extends State { SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), - ); - } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (selectedPlayers.isEmpty && - allPlayers.isEmpty))) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), - enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), - child: - (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) - ? TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - (selectedPlayers.length == - allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ) - : ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 5, - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all( - color: CustomTheme.boxBorder, - ), - borderRadius: BorderRadius.circular( - 12, - ), - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - Flexible( - child: Text( - suggestedPlayers[index].name, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ), - IconButton( - icon: Icon(Icons.add, size: 20), - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( + builder: + ( + BuildContext context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Player data couldn\'t\nbe loaded.', + ), + ); + } + if (snapshot.connectionState == + ConnectionState.done && + (!snapshot.hasData || + snapshot.data!.isEmpty || + (selectedPlayers.isEmpty && + allPlayers.isEmpty))) { + return const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No players created yet.', + ), + ); + } + final bool isLoading = + snapshot.connectionState == + ConnectionState.waiting; + return Expanded( + child: Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: isLoading, + enableSwitchAnimation: true, + switchAnimationConfig: + const SwitchAnimationConfig( + duration: Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher + .defaultTransitionBuilder, + layoutBuilder: + AnimatedSwitcher.defaultLayoutBuilder, + ), + child: + (suggestedPlayers.isEmpty && + !allPlayers.isEmpty) + ? TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ) + : ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: + (BuildContext context, int index) { + return IconListTile( + text: suggestedPlayers[index] + .name, + icon: Icons.add, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => a.name - .compareTo(b.name), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ), - ], - ), - ); - }, - ), - ), - ); - }, + )) { + selectedPlayers.add( + suggestedPlayers[index], + ); + selectedPlayers.sort( + (a, b) => a.name + .compareTo(b.name), + ); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, + ); + }, + ), + ), + ); + }, ), ], ), @@ -348,11 +273,12 @@ class _CreateGroupViewState extends State { (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null : () async { - String id = "ID_" + _groupNameController.text; - String name = _groupNameController.text; - List members = selectedPlayers; bool success = await db.groupDao.addGroup( - group: Group(id: id, name: name, members: members), + group: Group( + id: Uuid().v4(), + name: _groupNameController.text, + members: selectedPlayers, + ), ); if (success) { _groupNameController.clear(); From 2f260d7cbcfd01be9ec595f3427088e120970c58 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:14:18 +0100 Subject: [PATCH 18/34] Add uuid dependency --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index ab6e30b..fbbc01a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: path_provider: ^2.1.5 provider: ^6.1.5 skeletonizer: ^2.1.0+1 + uuid: ^4.5.2 dev_dependencies: flutter_test: From 80290efa0b1d0046b0c18bea101dab4a82c05b83 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 20:37:41 +0100 Subject: [PATCH 19/34] rename FullWidthButton to CustomWidthButton --- .../views/main_menu/create_group/create_group_view.dart | 4 ++-- lib/presentation/views/main_menu/groups_view.dart | 4 ++-- .../{full_width_button.dart => custom_width_button.dart} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/presentation/widgets/{full_width_button.dart => custom_width_button.dart} (93%) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 1a84956..8a89501 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; @@ -263,7 +263,7 @@ class _CreateGroupViewState extends State { ), ), ), - FullWidthButton( + CustomWidthButton( text: "Create group", infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index bdb3d4a..200f1d0 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.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/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/full_width_button.dart'; +import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; @@ -105,7 +105,7 @@ class _GroupsViewState extends State { Positioned( bottom: 80, - child: FullWidthButton( + child: CustomWidthButton( text: 'Create Group', infillColor: CustomTheme.primaryColor, borderColor: CustomTheme.primaryColor, diff --git a/lib/presentation/widgets/full_width_button.dart b/lib/presentation/widgets/custom_width_button.dart similarity index 93% rename from lib/presentation/widgets/full_width_button.dart rename to lib/presentation/widgets/custom_width_button.dart index fe9913c..b336a79 100644 --- a/lib/presentation/widgets/full_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class FullWidthButton extends StatelessWidget { - const FullWidthButton({ +class CustomWidthButton extends StatelessWidget { + const CustomWidthButton({ super.key, required this.text, required this.borderColor, From c67f688a7723c82c7026759472439f29bf65e98c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:03 +0100 Subject: [PATCH 20/34] Refactor CreateGroupView: remove UUID generation, update tiles & fix async gaps --- .../create_group/create_group_view.dart | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group/create_group_view.dart index 8a89501..81d5e36 100644 --- a/lib/presentation/views/main_menu/create_group/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group/create_group_view.dart @@ -11,7 +11,6 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:uuid/uuid.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -28,7 +27,7 @@ class _CreateGroupViewState extends State { late Future> _allPlayersFuture; late final List skeletonData = List.filled( 7, - Player(id: '0', name: 'Player 0'), + Player(name: 'Player 0'), ); final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); @@ -56,8 +55,8 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: const Text( - "Create new group", - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + 'Create new group', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), @@ -94,8 +93,11 @@ class _CreateGroupViewState extends State { children: [ CustomSearchBar( controller: _searchBarController, - constraints: BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: "Search for players", + constraints: const BoxConstraints( + maxHeight: 45, + minHeight: 45, + ), + hintText: 'Search for players', onChanged: (value) { setState(() { if (value.isEmpty) { @@ -115,35 +117,34 @@ class _CreateGroupViewState extends State { }); }, ), - SizedBox(height: 10), + const SizedBox(height: 10), Text( - "Ausgewählte Spieler: (${selectedPlayers.length})", - style: TextStyle( + 'Ausgewählte Spieler: (${selectedPlayers.length})', + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), + const SizedBox(height: 10), Wrap( alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, spacing: 8.0, runSpacing: 8.0, children: [ - for (var selectedPlayer in selectedPlayers) + for (var player in selectedPlayers) TextIconTile( - text: selectedPlayer.name, - icon: Icons.close, + text: player.name, onIconTap: () { setState(() { final currentSearch = _searchBarController.text .toLowerCase(); - selectedPlayers.remove(selectedPlayer); + selectedPlayers.remove(player); if (currentSearch.isEmpty || - selectedPlayer.name.toLowerCase().contains( + player.name.toLowerCase().contains( currentSearch, )) { - suggestedPlayers.add(selectedPlayer); + suggestedPlayers.add(player); suggestedPlayers.sort( (a, b) => a.name.compareTo(b.name), ); @@ -153,15 +154,15 @@ class _CreateGroupViewState extends State { ), ], ), - SizedBox(height: 10), - Text( - "Alle Spieler:", + const SizedBox(height: 10), + const Text( + 'Alle Spieler:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 10), + const SizedBox(height: 10), FutureBuilder( future: _allPlayersFuture, builder: @@ -216,7 +217,7 @@ class _CreateGroupViewState extends State { ), child: (suggestedPlayers.isEmpty && - !allPlayers.isEmpty) + allPlayers.isNotEmpty) ? TopCenteredMessage( icon: Icons.info, title: 'Info', @@ -230,10 +231,9 @@ class _CreateGroupViewState extends State { itemCount: suggestedPlayers.length, itemBuilder: (BuildContext context, int index) { - return IconListTile( + return TextIconListTile( text: suggestedPlayers[index] .name, - icon: Icons.add, onPressed: () { setState(() { if (!selectedPlayers.contains( @@ -264,9 +264,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: "Create group", - infillColor: CustomTheme.primaryColor, - borderColor: CustomTheme.primaryColor, + text: 'Create group', disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, onPressed: @@ -275,7 +273,6 @@ class _CreateGroupViewState extends State { : () async { bool success = await db.groupDao.addGroup( group: Group( - id: Uuid().v4(), name: _groupNameController.text, members: selectedPlayers, ), @@ -284,14 +281,16 @@ class _CreateGroupViewState extends State { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); + if (!mounted) return; Navigator.pop(context); } else { + if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, - content: Center( + content: const Center( child: Text( - "Error while creating group, please try again", + 'Error while creating group, please try again', style: TextStyle(color: Colors.white), ), ), @@ -301,7 +300,7 @@ class _CreateGroupViewState extends State { setState(() {}); }, ), - SizedBox(height: 20), + const SizedBox(height: 20), ], ), ), From 51a8c4ea58ab95ff732949868502acd66553537c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:17 +0100 Subject: [PATCH 21/34] Replace `MaterialStateProperty` with `WidgetStateProperty` in `CustomSearchBar` --- lib/presentation/widgets/custom_search_bar.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/custom_search_bar.dart b/lib/presentation/widgets/custom_search_bar.dart index d0f66e8..b482efb 100644 --- a/lib/presentation/widgets/custom_search_bar.dart +++ b/lib/presentation/widgets/custom_search_bar.dart @@ -23,14 +23,14 @@ class CustomSearchBar extends StatelessWidget { constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: hintText, onChanged: onChanged, - hintStyle: MaterialStateProperty.all(const TextStyle(fontSize: 16)), + hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), - backgroundColor: MaterialStateProperty.all(CustomTheme.boxColor), - side: MaterialStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), - shape: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), + side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + shape: WidgetStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), - elevation: MaterialStateProperty.all(0), + elevation: WidgetStateProperty.all(0), ); } } From d65dd3d9838a9fa1075bcfd62460ec52ecb7f87e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:42:40 +0100 Subject: [PATCH 22/34] Refactor CustomWidthButton to use ButtonStyle enum and CustomTheme - Replaced `borderColor` and `infillColor` parameters with a `buttonStyle` parameter. - Introduced `ButtonStyle` enum (primary/secondary) to control styling. - Updated `CustomWidthButton` to derive colors from `CustomTheme` based on the selected `ButtonStyle`. --- .../widgets/custom_width_button.dart | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/custom_width_button.dart index b336a79..b0b9bd3 100644 --- a/lib/presentation/widgets/custom_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -1,22 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; + +enum ButtonStyle { primary, secondary } class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - required this.borderColor, - required this.infillColor, this.disabledInfillColor, + this.buttonStyle = ButtonStyle.primary, required this.sizeRelativeToWidth, required this.onPressed, }); final String text; - final Color borderColor; - final Color infillColor; final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; + final ButtonStyle buttonStyle; @override Widget build(BuildContext context) { @@ -28,8 +29,15 @@ class CustomWidthButton extends StatelessWidget { MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, ), - backgroundColor: infillColor, - side: BorderSide(color: borderColor, width: 2), + backgroundColor: buttonStyle == ButtonStyle.primary + ? CustomTheme.primaryColor + : CustomTheme.secondaryColor, + side: BorderSide( + color: buttonStyle == ButtonStyle.primary + ? CustomTheme.primaryColor + : CustomTheme.secondaryColor, + width: 2, + ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( From e0c83988730735e444bd87ad0e631be605dc5799 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:43:38 +0100 Subject: [PATCH 23/34] remove custom colors from Create Group button in GroupsView --- lib/presentation/views/main_menu/groups_view.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 73f3509..7e6f59d 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -106,8 +106,6 @@ class _GroupsViewState extends State { bottom: 80, child: CustomWidthButton( text: 'Create Group', - infillColor: CustomTheme.primaryColor, - borderColor: CustomTheme.primaryColor, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( From d3a63bd299e23cdfdf6470ff3793dce71076ad70 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:56:20 +0100 Subject: [PATCH 24/34] renamed IconListTile to TextIconListTile and replaced the icon parameter with iconEnabled in both TextIconListTile and TextIconTile --- .../widgets/tiles/text_icon_list_tile.dart | 28 +++++++++++++------ .../widgets/tiles/text_icon_tile.dart | 13 +++++---- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index b32504f..92d0251 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -class IconListTile extends StatelessWidget { +class TextIconListTile extends StatelessWidget { final String text; - final IconData icon; final VoidCallback onPressed; + final bool iconEnabled; - const IconListTile({ + const TextIconListTile({ super.key, required this.text, - required this.icon, required this.onPressed, + this.iconEnabled = true, }); @override @@ -28,13 +28,23 @@ class IconListTile extends StatelessWidget { mainAxisSize: MainAxisSize.max, children: [ Flexible( - child: Text( - text, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.5), + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), ), ), - IconButton(icon: Icon(icon, size: 20), onPressed: onPressed), + if (iconEnabled) + IconButton( + icon: const Icon(Icons.add, size: 20), + onPressed: onPressed, + ), ], ), ); diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 52174bd..2544837 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -3,14 +3,14 @@ import 'package:game_tracker/core/custom_theme.dart'; class TextIconTile extends StatelessWidget { final String text; - final IconData? icon; + final bool iconEnabled; final VoidCallback? onIconTap; const TextIconTile({ super.key, required this.text, - this.icon, this.onIconTap, + this.iconEnabled = true, }); @override @@ -25,7 +25,7 @@ class TextIconTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ - if (icon != null) const SizedBox(width: 3), + if (iconEnabled) const SizedBox(width: 3), Flexible( child: Text( text, @@ -33,9 +33,12 @@ class TextIconTile extends StatelessWidget { style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), ), - if (icon != null) ...[ + if (iconEnabled) ...[ const SizedBox(width: 3), - GestureDetector(onTap: onIconTap, child: Icon(icon, size: 20)), + GestureDetector( + onTap: onIconTap, + child: const Icon(Icons.close, size: 20), + ), ], ], ), From d34163488531a2cc37ac0f5c1577dca3c8df0f7b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 18 Nov 2025 21:56:31 +0100 Subject: [PATCH 25/34] Disable icon for members in group tile --- lib/presentation/widgets/tiles/group_tile.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index d87cc12..fa91477 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -56,7 +56,8 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) TextIconTile(text: member.name), + for (var member in group.members) + TextIconTile(text: member.name, iconEnabled: false), ], ), const SizedBox(height: 2.5), From b82261317c87c14b0001c8f3fd457373bc5cbd4f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 15:09:40 +0100 Subject: [PATCH 26/34] move CreateGroupView to main_menu directory --- .../views/main_menu/{create_group => }/create_group_view.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/presentation/views/main_menu/{create_group => }/create_group_view.dart (100%) diff --git a/lib/presentation/views/main_menu/create_group/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_group/create_group_view.dart rename to lib/presentation/views/main_menu/create_group_view.dart From 54e1756e79326324205acba4874fd0e12b14e88a Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:39:05 +0100 Subject: [PATCH 27/34] moved create_group_view from subfolder to root --- lib/presentation/views/main_menu/groups_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index 7e6f59d..f74d20c 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.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/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; From 9365313c9215a5ad77f2a16b8c2c05f7dad03ad9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:39:32 +0100 Subject: [PATCH 28/34] button not working --- .../views/main_menu/create_group_view.dart | 7 ++- .../widgets/custom_width_button.dart | 46 ++++++++++++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 81d5e36..f43fc8d 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ButtonStyle; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -265,8 +265,8 @@ class _CreateGroupViewState extends State { ), CustomWidthButton( text: 'Create group', - disabledInfillColor: CustomTheme.boxColor, sizeRelativeToWidth: 0.95, + buttonStyle: ButtonStyle.secondary, onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null @@ -277,14 +277,13 @@ class _CreateGroupViewState extends State { members: selectedPlayers, ), ); + if (!context.mounted) return; if (success) { _groupNameController.clear(); _searchBarController.clear(); selectedPlayers.clear(); - if (!mounted) return; Navigator.pop(context); } else { - if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/custom_width_button.dart index b0b9bd3..2a79d1b 100644 --- a/lib/presentation/widgets/custom_width_button.dart +++ b/lib/presentation/widgets/custom_width_button.dart @@ -7,45 +7,67 @@ class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - this.disabledInfillColor, this.buttonStyle = ButtonStyle.primary, required this.sizeRelativeToWidth, - required this.onPressed, + this.onPressed, }); final String text; - final Color? disabledInfillColor; final double sizeRelativeToWidth; final VoidCallback? onPressed; final ButtonStyle buttonStyle; @override Widget build(BuildContext context) { + + final Color buttonBackgroundColor; + final Color disabledBackgroundColor; + final Color borderSideColor; + final Color disabledBorderSideColor; + final Color textcolor; + final Color disabledTextColor; + + + if(buttonStyle == ButtonStyle.primary){ + buttonBackgroundColor = CustomTheme.primaryColor; + disabledBackgroundColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + borderSideColor = Colors.transparent; + disabledBorderSideColor = Colors.transparent; + textcolor = Colors.white; + disabledTextColor = Colors.white.withValues(alpha: 0.24); + } else{ + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + borderSideColor = CustomTheme.primaryColor.withValues(alpha: 0.6 ); + disabledBorderSideColor = Colors.transparent; + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + } + + return ElevatedButton( onPressed: onPressed, style: ElevatedButton.styleFrom( - disabledBackgroundColor: disabledInfillColor, + disabledBackgroundColor: disabledBackgroundColor, minimumSize: Size( MediaQuery.sizeOf(context).width * sizeRelativeToWidth, 60, ), - backgroundColor: buttonStyle == ButtonStyle.primary - ? CustomTheme.primaryColor - : CustomTheme.secondaryColor, + backgroundColor: buttonBackgroundColor, side: BorderSide( - color: buttonStyle == ButtonStyle.primary - ? CustomTheme.primaryColor - : CustomTheme.secondaryColor, + color: borderSideColor, width: 2, ), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: Text( text, - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.w500, fontSize: 22, - color: Colors.white, + color: (onPressed == null) + ? disabledTextColor + : textcolor, ), ), ); From 201fd70685c2d7040db9c9d0533254db2bef0dd6 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:44:46 +0100 Subject: [PATCH 29/34] Update `TextIconListTile` padding and replace `IconButton` with `GestureDetector` --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index 92d0251..1907928 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -17,7 +17,7 @@ class TextIconListTile extends StatelessWidget { Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 15), decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), @@ -41,9 +41,9 @@ class TextIconListTile extends StatelessWidget { ), ), if (iconEnabled) - IconButton( - icon: const Icon(Icons.add, size: 20), - onPressed: onPressed, + GestureDetector( + child: const Icon(Icons.add, size: 20), + onTap: onPressed, ), ], ), From 018332d8e603cabe5ab6ee6e46770fd1e16a595c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:48:43 +0100 Subject: [PATCH 30/34] Refactor widget directory structure by organizing tiles and buttons - Move `GameTile` and `DoubleRowInfoTile` to `presentation/widgets/tiles/` - Move `CustomWidthButton` and `QuickCreateButton` to `presentation/widgets/buttons/` - Update import paths in `HomeView`, `GroupsView`, `GameHistoryView`, and `CreateGroupView` --- lib/presentation/views/main_menu/create_group_view.dart | 2 +- lib/presentation/views/main_menu/game_history_view.dart | 2 +- lib/presentation/views/main_menu/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 4 ++-- .../widgets/{ => buttons}/custom_width_button.dart | 0 .../widgets/{ => buttons}/quick_create_button.dart | 0 .../widgets/{ => tiles}/double_row_info_tile.dart | 0 lib/presentation/widgets/{ => tiles}/game_tile.dart | 0 8 files changed, 5 insertions(+), 5 deletions(-) rename lib/presentation/widgets/{ => buttons}/custom_width_button.dart (100%) rename lib/presentation/widgets/{ => buttons}/quick_create_button.dart (100%) rename lib/presentation/widgets/{ => tiles}/double_row_info_tile.dart (100%) rename lib/presentation/widgets/{ => tiles}/game_tile.dart (100%) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f43fc8d..365cf27 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -3,8 +3,8 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 3642a88..7c19bbf 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/double_row_info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/double_row_info_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class GameHistoryView extends StatefulWidget { diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index f74d20c..c45cf21 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/data/db/database.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/widgets/custom_width_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index cf6288a..34e4be3 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/presentation/widgets/game_tile.dart'; -import 'package:game_tracker/presentation/widgets/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/widgets/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart similarity index 100% rename from lib/presentation/widgets/custom_width_button.dart rename to lib/presentation/widgets/buttons/custom_width_button.dart diff --git a/lib/presentation/widgets/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart similarity index 100% rename from lib/presentation/widgets/quick_create_button.dart rename to lib/presentation/widgets/buttons/quick_create_button.dart diff --git a/lib/presentation/widgets/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart similarity index 100% rename from lib/presentation/widgets/double_row_info_tile.dart rename to lib/presentation/widgets/tiles/double_row_info_tile.dart diff --git a/lib/presentation/widgets/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart similarity index 100% rename from lib/presentation/widgets/game_tile.dart rename to lib/presentation/widgets/tiles/game_tile.dart From 1232cb8f0dc259c4c4105030fe25255c14683a06 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 19 Nov 2025 16:50:02 +0100 Subject: [PATCH 31/34] Fix `GestureDetector` child ordering in `TextIconListTile` --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index 1907928..c0fe673 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -42,8 +42,8 @@ class TextIconListTile extends StatelessWidget { ), if (iconEnabled) GestureDetector( - child: const Icon(Icons.add, size: 20), onTap: onPressed, + child: const Icon(Icons.add, size: 20), ), ], ), From 3f79a7b89826b60fd33712cb016418507da1b292 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:26:51 +0100 Subject: [PATCH 32/34] sourcing enums out to enums.dart --- lib/core/enums.dart | 2 ++ lib/presentation/views/main_menu/create_group_view.dart | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 lib/core/enums.dart diff --git a/lib/core/enums.dart b/lib/core/enums.dart new file mode 100644 index 0000000..320eaf7 --- /dev/null +++ b/lib/core/enums.dart @@ -0,0 +1,2 @@ +/// Button types used for styling the [CustomWidthButton] +enum ButtonType { primary, secondary, tertiary } diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 365cf27..db8890f 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' hide ButtonStyle; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -266,7 +267,7 @@ class _CreateGroupViewState extends State { CustomWidthButton( text: 'Create group', sizeRelativeToWidth: 0.95, - buttonStyle: ButtonStyle.secondary, + buttonType: ButtonType.primary, onPressed: (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) ? null From e71e65b197f1fa05bb82d977d8012e5a92dd73f2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:27:27 +0100 Subject: [PATCH 33/34] Corrected button color behaviour and added tertiary button --- .../widgets/buttons/custom_width_button.dart | 127 ++++++++++++------ 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 2a79d1b..bce78ed 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; - -enum ButtonStyle { primary, secondary } +import 'package:game_tracker/core/enums.dart'; class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, required this.text, - this.buttonStyle = ButtonStyle.primary, + this.buttonType = ButtonType.primary, required this.sizeRelativeToWidth, this.onPressed, }); @@ -15,61 +14,101 @@ class CustomWidthButton extends StatelessWidget { final String text; final double sizeRelativeToWidth; final VoidCallback? onPressed; - final ButtonStyle buttonStyle; + final ButtonType buttonType; @override Widget build(BuildContext context) { - final Color buttonBackgroundColor; final Color disabledBackgroundColor; final Color borderSideColor; - final Color disabledBorderSideColor; final Color textcolor; final Color disabledTextColor; - - if(buttonStyle == ButtonStyle.primary){ - buttonBackgroundColor = CustomTheme.primaryColor; - disabledBackgroundColor = CustomTheme.primaryColor.withValues(alpha: 0.24); - borderSideColor = Colors.transparent; - disabledBorderSideColor = Colors.transparent; + if (buttonType == ButtonType.primary) { textcolor = Colors.white; disabledTextColor = Colors.white.withValues(alpha: 0.24); - } else{ + buttonBackgroundColor = CustomTheme.primaryColor; + disabledBackgroundColor = CustomTheme.primaryColor.withValues( + alpha: 0.24, + ); + + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else if (buttonType == ButtonType.secondary) { + textcolor = CustomTheme.primaryColor; + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.5); buttonBackgroundColor = Colors.transparent; disabledBackgroundColor = Colors.transparent; - borderSideColor = CustomTheme.primaryColor.withValues(alpha: 0.6 ); - disabledBorderSideColor = Colors.transparent; + borderSideColor = onPressed != null + ? CustomTheme.primaryColor + : CustomTheme.primaryColor.withValues(alpha: 0.5); + + return OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: BorderSide(color: borderSideColor, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); + } else { textcolor = CustomTheme.primaryColor; - disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.24); + disabledTextColor = CustomTheme.primaryColor.withValues(alpha: 0.3); + buttonBackgroundColor = Colors.transparent; + disabledBackgroundColor = Colors.transparent; + + return TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + foregroundColor: textcolor, + disabledForegroundColor: disabledTextColor, + backgroundColor: buttonBackgroundColor, + disabledBackgroundColor: disabledBackgroundColor, + animationDuration: const Duration(), + minimumSize: Size( + MediaQuery.sizeOf(context).width * sizeRelativeToWidth, + 60, + ), + side: const BorderSide(style: BorderStyle.none), + ), + child: Text( + text, + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 22), + ), + ); } - - - return ElevatedButton( - onPressed: onPressed, - style: ElevatedButton.styleFrom( - disabledBackgroundColor: disabledBackgroundColor, - minimumSize: Size( - MediaQuery.sizeOf(context).width * sizeRelativeToWidth, - 60, - ), - backgroundColor: buttonBackgroundColor, - side: BorderSide( - color: borderSideColor, - width: 2, - ), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - child: Text( - text, - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 22, - color: (onPressed == null) - ? disabledTextColor - : textcolor, - ), - ), - ); } } From 248d652e0641cba2a45447a486b97b6b52c5a175 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 19 Nov 2025 18:32:25 +0100 Subject: [PATCH 34/34] Made onPressed not required --- lib/presentation/widgets/tiles/text_icon_list_tile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index c0fe673..5e272c9 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -3,13 +3,13 @@ import 'package:game_tracker/core/custom_theme.dart'; class TextIconListTile extends StatelessWidget { final String text; - final VoidCallback onPressed; + final VoidCallback? onPressed; final bool iconEnabled; const TextIconListTile({ super.key, required this.text, - required this.onPressed, + this.onPressed, this.iconEnabled = true, });