From 694cac7f265063128e56d5c94b0299cfab646cd8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 21:16:13 +0100 Subject: [PATCH 01/18] =?UTF-8?q?Fix=20bug=20where=20the=20skeleton=20was?= =?UTF-8?q?=20edited=20while=20it=20was=20visible=20and=20match=20the=20?= =?UTF-8?q?=E2=80=9Cno=20games=20available=E2=80=9D=20container=20size=20t?= =?UTF-8?q?o=20the=20size=20used=20when=20games=20are=20available.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/views/main_menu/home_view.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index d9cd1ab..f75eb78 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -151,15 +151,6 @@ class _HomeViewState extends State { ), ); } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty)) { - return const Center( - heightFactor: 4, - child: Text('No recent games available.'), - ); - } final List games = (isLoading ? skeletonData @@ -214,7 +205,7 @@ class _HomeViewState extends State { ); } else { return const Center( - heightFactor: 4, + heightFactor: 12, child: Text('No recent games available.'), ); } From 974d6e9b56ce94764004d941954e14f973a84dfe Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 23 Nov 2025 22:14:13 +0100 Subject: [PATCH 02/18] refactor empty state logic in CreateGroupView The diff introduces boolean variables `doneLoading` and `snapshotDataEmpty` to simplify the conditional check for displaying the empty state message. It specifically fixes the logic to correctly show the "No players found" message when both the snapshot and the local `allPlayers` list are empty, removing the dependency on `selectedPlayers.isEmpty`. --- .../views/main_menu/create_group_view.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 59f72ed..c01250b 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -209,12 +209,13 @@ class _CreateGroupViewState extends State { ), ); } - if (snapshot.connectionState == - ConnectionState.done && - (!snapshot.hasData || - snapshot.data!.isEmpty || - (selectedPlayers.isEmpty && - allPlayers.isEmpty))) { + bool doneLoading = + snapshot.connectionState == + ConnectionState.done; + bool snapshotDataEmpty = + !snapshot.hasData || snapshot.data!.isEmpty; + if (doneLoading && + (snapshotDataEmpty && allPlayers.isEmpty)) { return const Center( child: TopCenteredMessage( icon: Icons.info, From 18f635e6ef0346ab689bd10f6953ece0f8ebab2c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:07 +0100 Subject: [PATCH 03/18] Implemented custom skeleton widget --- lib/presentation/widgets/app_skeleton.dart | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lib/presentation/widgets/app_skeleton.dart diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart new file mode 100644 index 0000000..209f1d8 --- /dev/null +++ b/lib/presentation/widgets/app_skeleton.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class AppSkeleton extends StatefulWidget { + final Widget child; + final bool enabled; + final bool fixLayoutBuilder; + + const AppSkeleton({ + super.key, + required this.child, + this.enabled = true, + this.fixLayoutBuilder = false, + }); + + @override + State createState() => _AppSkeletonState(); +} + +class _AppSkeletonState extends State { + @override + Widget build(BuildContext context) { + return Skeletonizer( + effect: PulseEffect( + from: Colors.grey[800]!, + to: Colors.grey[600]!, + duration: const Duration(milliseconds: 800), + ), + enabled: widget.enabled, + enableSwitchAnimation: true, + switchAnimationConfig: SwitchAnimationConfig( + duration: const Duration(milliseconds: 200), + switchInCurve: Curves.linear, + switchOutCurve: Curves.linear, + transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, + layoutBuilder: !widget.fixLayoutBuilder + ? AnimatedSwitcher.defaultLayoutBuilder + : (Widget? currentChild, List previousChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...previousChildren, + if (currentChild != null) currentChild, + ], + ); + }, + ), + child: widget.child, + ); + } +} From 2d9148788e55e99e9a6711fb7ff36837d257d2e4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 24 Nov 2025 20:05:18 +0100 Subject: [PATCH 04/18] Refactored skeleton widgets to own --- .../views/main_menu/create_group_view.dart | 20 ++------------ .../views/main_menu/groups_view.dart | 18 ++----------- .../views/main_menu/home_view.dart | 26 ++---------------- .../views/main_menu/statistics_view.dart | 27 +++---------------- 4 files changed, 9 insertions(+), 82 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c01250b..ef78169 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -4,6 +4,7 @@ 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'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; @@ -11,7 +12,6 @@ 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'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -228,24 +228,8 @@ class _CreateGroupViewState extends State { snapshot.connectionState == ConnectionState.waiting; return Expanded( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: - const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher - .defaultTransitionBuilder, - layoutBuilder: - AnimatedSwitcher.defaultLayoutBuilder, - ), child: Visibility( visible: (suggestedPlayers.isEmpty && diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/groups_view.dart index aaef1a5..29fbac8 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/groups_view.dart @@ -4,11 +4,11 @@ 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/app_skeleton.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'; -import 'package:skeletonizer/skeletonizer.dart'; class GroupsView extends StatefulWidget { const GroupsView({super.key}); @@ -75,22 +75,8 @@ class _GroupsViewState extends State { final List groups = isLoading ? skeletonData : (snapshot.data ?? []) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( 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( padding: const EdgeInsets.only(bottom: 85), itemCount: groups.length + 1, diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f75eb78..1667f2b 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -3,12 +3,12 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.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'; -import 'package:skeletonizer/skeletonizer.dart'; class HomeView extends StatefulWidget { const HomeView({super.key}); @@ -62,30 +62,8 @@ class _HomeViewState extends State { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + return AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6107586..564d0d5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; -import 'package:skeletonizer/skeletonizer.dart'; class StatisticsView extends StatefulWidget { const StatisticsView({super.key}); @@ -48,30 +48,9 @@ class _StatisticsViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( - child: Skeletonizer( - effect: PulseEffect( - from: Colors.grey[800]!, - to: Colors.grey[600]!, - duration: const Duration(milliseconds: 800), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: SwitchAnimationConfig( - duration: const Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: - (Widget? currentChild, List previousChildren) { - return Stack( - alignment: Alignment.topCenter, - children: [ - ...previousChildren, - if (currentChild != null) currentChild, - ], - ); - }, - ), + fixLayoutBuilder: true, child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: Column( From 744a402602f92c4912547ab2005f8db571fff796 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 20:58:56 +0100 Subject: [PATCH 05/18] put player selection from creategroupview into own widget --- .../widgets/select_player_widget.dart | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 lib/presentation/widgets/select_player_widget.dart diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/select_player_widget.dart new file mode 100644 index 0000000..14cea0b --- /dev/null +++ b/lib/presentation/widgets/select_player_widget.dart @@ -0,0 +1,287 @@ +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/custom_search_bar.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'; + +class SelectPlayerWidget extends StatefulWidget { + final TextEditingController groupNameController; + final TextEditingController searchBarController; + final List selectedPlayers; + final Function(List value) onChanged; + + const SelectPlayerWidget({ + super.key, + required this.groupNameController, + required this.searchBarController, + required this.selectedPlayers, + required this.onChanged, + }); + + @override + State createState() => _SelectPlayerWidgetState(); +} + +class _SelectPlayerWidgetState extends State { + List suggestedPlayers = []; + List allPlayers = []; + late final TextEditingController _searchBarController; + late final AppDatabase db; + late Future> _allPlayersFuture; + late final List skeletonData = List.filled( + 7, + Player(name: 'Player 0'), + ); + + @override + void initState() { + super.initState(); + db = Provider.of(context, listen: false); + _searchBarController = widget.searchBarController; + loadPlayerList(); + } + + void loadPlayerList() { + _allPlayersFuture = Future.delayed( + const Duration(milliseconds: 250), + () => db.playerDao.getAllPlayers(), + ); + suggestedPlayers = skeletonData; + _allPlayersFuture.then((loadedPlayers) { + setState(() { + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + }); + }); + } + + @override + Widget build(BuildContext context) { + return 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: [ + CustomSearchBar( + controller: _searchBarController, + constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), + hintText: 'Search for players', + trailingButtonShown: true, + trailingButtonicon: Icons.add_circle, + trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, + onTrailingButtonPressed: () async { + addNewPlayerFromSearch(context: context); + }, + onChanged: (value) { + setState(() { + if (value.isEmpty) { + suggestedPlayers = allPlayers.where((player) { + return !widget.selectedPlayers.contains(player); + }).toList(); + } else { + suggestedPlayers = allPlayers.where((player) { + final bool nameMatches = player.name.toLowerCase().contains( + value.toLowerCase(), + ); + final bool isNotSelected = !widget.selectedPlayers.contains( + player, + ); + return nameMatches && isNotSelected; + }).toList(); + } + }); + }, + ), + const SizedBox(height: 10), + Text( + 'Ausgewählte Spieler: (${widget.selectedPlayers.length})', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + spacing: 8.0, + runSpacing: 8.0, + children: [ + for (var player in widget.selectedPlayers) + TextIconTile( + text: player.name, + onIconTap: () { + setState(() { + final currentSearch = _searchBarController.text + .toLowerCase(); + //widget.selectedPlayers.remove(player); + widget.onChanged( + widget.selectedPlayers + .where((p) => p != player) + .toList(), + ); + if (currentSearch.isEmpty || + player.name.toLowerCase().contains(currentSearch)) { + suggestedPlayers.add(player); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); + } + }); + }, + ), + ], + ), + const SizedBox(height: 10), + const Text( + 'Alle Spieler:', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const 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.', + ), + ); + } + bool doneLoading = + snapshot.connectionState == ConnectionState.done; + bool snapshotDataEmpty = + !snapshot.hasData || snapshot.data!.isEmpty; + if (doneLoading && + (snapshotDataEmpty && 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: Visibility( + visible: + (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), + replacement: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!widget.selectedPlayers.contains( + suggestedPlayers[index], + )) { + /*widget.selectedPlayers.add( + suggestedPlayers[index], + );*/ + widget.onChanged([ + ...widget.selectedPlayers, + suggestedPlayers[index], + ]); + suggestedPlayers.remove( + suggestedPlayers[index], + ); + } + }); + }, + ); + }, + ), + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: + (widget.selectedPlayers.length == + allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + ), + ), + ); + }, + ), + ], + ), + ); + } + + /// Adds a new player to the database from the search bar input. + /// Shows a snackbar indicating success or failure. + /// [context] - BuildContext to show the snackbar. + void addNewPlayerFromSearch({required BuildContext context}) async { + String playerName = _searchBarController.text.trim(); + Player createdPlayer = Player(name: playerName); + bool success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { + //widget.selectedPlayers.add(createdPlayer); + widget.onChanged([...widget.selectedPlayers, createdPlayer]); + allPlayers.add(createdPlayer); + setState(() { + _searchBarController.clear(); + suggestedPlayers = allPlayers.where((player) { + return !widget.selectedPlayers.contains(player); + }).toList(); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Successfully added player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + 'Could not add player $playerName.', + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } + } +} From 6c9b742bdf47c54c5eb70b3835441ecb8395e83c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 20:59:23 +0100 Subject: [PATCH 06/18] Refactor CreateGroupView to use SelectPlayerWidget --- .../views/main_menu/create_group_view.dart | 290 +----------------- 1 file changed, 16 insertions(+), 274 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c01250b..1153fc0 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -5,13 +5,9 @@ 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/select_player_widget.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'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key}); @@ -21,29 +17,21 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - List selectedPlayers = []; - List suggestedPlayers = []; - List allPlayers = []; - late final AppDatabase db; - late Future> _allPlayersFuture; - late final List skeletonData = List.filled( - 7, - Player(name: 'Player 0'), - ); final _groupNameController = TextEditingController(); final _searchBarController = TextEditingController(); + late final AppDatabase db; + List selectedPlayers = []; @override void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController.addListener(() { - setState(() {}); - }); _groupNameController.addListener(() { setState(() {}); }); - loadPlayerList(); + _searchBarController.addListener(() { + setState(() {}); + }); } @override @@ -53,21 +41,6 @@ class _CreateGroupViewState extends State { super.dispose(); } - void loadPlayerList() { - _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.playerDao.getAllPlayers(), - ); - suggestedPlayers = skeletonData; - _allPlayersFuture.then((loadedPlayers) { - setState(() { - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - }); - }); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -96,204 +69,16 @@ class _CreateGroupViewState extends State { ), ), 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: [ - CustomSearchBar( - controller: _searchBarController, - constraints: const BoxConstraints( - maxHeight: 45, - minHeight: 45, - ), - hintText: 'Search for players', - trailingButtonShown: true, - trailingButtonicon: Icons.add_circle, - trailingButtonEnabled: _searchBarController.text - .trim() - .isNotEmpty, - onTrailingButtonPressed: () async { - addNewPlayerFromSearch(context: context); - }, - onChanged: (value) { - setState(() { - if (value.isEmpty) { - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - } else { - suggestedPlayers = allPlayers.where((player) { - final bool nameMatches = player.name - .toLowerCase() - .contains(value.toLowerCase()); - final bool isNotSelected = !selectedPlayers - .contains(player); - return nameMatches && isNotSelected; - }).toList(); - } - }); - }, - ), - const SizedBox(height: 10), - Text( - 'Ausgewählte Spieler: (${selectedPlayers.length})', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 8.0, - runSpacing: 8.0, - children: [ - for (var player in selectedPlayers) - TextIconTile( - text: player.name, - onIconTap: () { - setState(() { - final currentSearch = _searchBarController.text - .toLowerCase(); - selectedPlayers.remove(player); - if (currentSearch.isEmpty || - player.name.toLowerCase().contains( - currentSearch, - )) { - suggestedPlayers.add(player); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); - } - }); - }, - ), - ], - ), - const SizedBox(height: 10), - const Text( - 'Alle Spieler:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const 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.', - ), - ); - } - bool doneLoading = - snapshot.connectionState == - ConnectionState.done; - bool snapshotDataEmpty = - !snapshot.hasData || snapshot.data!.isEmpty; - if (doneLoading && - (snapshotDataEmpty && 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: Visibility( - visible: - (suggestedPlayers.isEmpty && - allPlayers.isNotEmpty), - replacement: ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: - (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index].name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - selectedPlayers.sort( - (a, b) => a.name.compareTo( - b.name, - ), - ); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); - }, - ), - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: - (selectedPlayers.length == - allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ), - ), - ), - ); - }, - ), - ], - ), + child: SelectPlayerWidget( + groupNameController: _groupNameController, + searchBarController: _searchBarController, + selectedPlayers: selectedPlayers, + onChanged: (value) { + setState(() { + selectedPlayers = [...value]; + }); + print(selectedPlayers); + }, ), ), CustomWidthButton( @@ -338,47 +123,4 @@ class _CreateGroupViewState extends State { ), ); } - - /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success or failure. - /// [context] - BuildContext to show the snackbar. - void addNewPlayerFromSearch({required BuildContext context}) async { - String playerName = _searchBarController.text.trim(); - Player createdPlayer = Player(name: playerName); - bool success = await db.playerDao.addPlayer(player: createdPlayer); - if (!context.mounted) return; - if (success) { - selectedPlayers.add(createdPlayer); - allPlayers.add(createdPlayer); - setState(() { - _searchBarController.clear(); - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Successfully added player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - 'Could not add player $playerName.', - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); - } - } } From f1bd9c18e0f047547706579ad787a12aa5ac0970 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:15:18 +0100 Subject: [PATCH 07/18] Made selectedPlayers local to SelectPlayerWidget because its not needed in CreateGroupView --- .../views/main_menu/create_group_view.dart | 6 +-- .../widgets/select_player_widget.dart | 40 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 1153fc0..5636c18 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -72,11 +72,8 @@ class _CreateGroupViewState extends State { child: SelectPlayerWidget( groupNameController: _groupNameController, searchBarController: _searchBarController, - selectedPlayers: selectedPlayers, onChanged: (value) { - setState(() { - selectedPlayers = [...value]; - }); + selectedPlayers = [...value]; print(selectedPlayers); }, ), @@ -99,7 +96,6 @@ class _CreateGroupViewState extends State { if (success) { _groupNameController.clear(); _searchBarController.clear(); - selectedPlayers.clear(); Navigator.pop(context); } else { ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/select_player_widget.dart index 14cea0b..1c4f87d 100644 --- a/lib/presentation/widgets/select_player_widget.dart +++ b/lib/presentation/widgets/select_player_widget.dart @@ -12,14 +12,12 @@ import 'package:skeletonizer/skeletonizer.dart'; class SelectPlayerWidget extends StatefulWidget { final TextEditingController groupNameController; final TextEditingController searchBarController; - final List selectedPlayers; final Function(List value) onChanged; const SelectPlayerWidget({ super.key, required this.groupNameController, required this.searchBarController, - required this.selectedPlayers, required this.onChanged, }); @@ -28,6 +26,7 @@ class SelectPlayerWidget extends StatefulWidget { } class _SelectPlayerWidgetState extends State { + List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; late final TextEditingController _searchBarController; @@ -88,14 +87,14 @@ class _SelectPlayerWidgetState extends State { setState(() { if (value.isEmpty) { suggestedPlayers = allPlayers.where((player) { - return !widget.selectedPlayers.contains(player); + return !selectedPlayers.contains(player); }).toList(); } else { suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name.toLowerCase().contains( value.toLowerCase(), ); - final bool isNotSelected = !widget.selectedPlayers.contains( + final bool isNotSelected = !selectedPlayers.contains( player, ); return nameMatches && isNotSelected; @@ -106,7 +105,7 @@ class _SelectPlayerWidgetState extends State { ), const SizedBox(height: 10), Text( - 'Ausgewählte Spieler: (${widget.selectedPlayers.length})', + 'Ausgewählte Spieler: (${selectedPlayers.length})', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -116,19 +115,15 @@ class _SelectPlayerWidgetState extends State { spacing: 8.0, runSpacing: 8.0, children: [ - for (var player in widget.selectedPlayers) + for (var player in selectedPlayers) TextIconTile( text: player.name, onIconTap: () { setState(() { final currentSearch = _searchBarController.text .toLowerCase(); - //widget.selectedPlayers.remove(player); - widget.onChanged( - widget.selectedPlayers - .where((p) => p != player) - .toList(), - ); + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); if (currentSearch.isEmpty || player.name.toLowerCase().contains(currentSearch)) { suggestedPlayers.add(player); @@ -203,16 +198,13 @@ class _SelectPlayerWidgetState extends State { text: suggestedPlayers[index].name, onPressed: () { setState(() { - if (!widget.selectedPlayers.contains( + if (!selectedPlayers.contains( suggestedPlayers[index], )) { - /*widget.selectedPlayers.add( + selectedPlayers.add( suggestedPlayers[index], - );*/ - widget.onChanged([ - ...widget.selectedPlayers, - suggestedPlayers[index], - ]); + ); + widget.onChanged([...selectedPlayers]); suggestedPlayers.remove( suggestedPlayers[index], ); @@ -225,9 +217,7 @@ class _SelectPlayerWidgetState extends State { child: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: - (widget.selectedPlayers.length == - allPlayers.length) + message: (selectedPlayers.length == allPlayers.length) ? 'No more players to add.' : 'No players found with that name.', ), @@ -250,13 +240,13 @@ class _SelectPlayerWidgetState extends State { bool success = await db.playerDao.addPlayer(player: createdPlayer); if (!context.mounted) return; if (success) { - //widget.selectedPlayers.add(createdPlayer); - widget.onChanged([...widget.selectedPlayers, createdPlayer]); + selectedPlayers.add(createdPlayer); + widget.onChanged([...selectedPlayers]); allPlayers.add(createdPlayer); setState(() { _searchBarController.clear(); suggestedPlayers = allPlayers.where((player) { - return !widget.selectedPlayers.contains(player); + return !selectedPlayers.contains(player); }).toList(); }); ScaffoldMessenger.of(context).showSnackBar( From 6a77028171eab4b20f394e7db2ca49caa96690ea Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:17:01 +0100 Subject: [PATCH 08/18] rename to PlayerSelection --- .../{select_player_widget.dart => player_selection.dart} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename lib/presentation/widgets/{select_player_widget.dart => player_selection.dart} (97%) diff --git a/lib/presentation/widgets/select_player_widget.dart b/lib/presentation/widgets/player_selection.dart similarity index 97% rename from lib/presentation/widgets/select_player_widget.dart rename to lib/presentation/widgets/player_selection.dart index 1c4f87d..3032c06 100644 --- a/lib/presentation/widgets/select_player_widget.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -9,12 +9,12 @@ import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class SelectPlayerWidget extends StatefulWidget { +class PlayerSelection extends StatefulWidget { final TextEditingController groupNameController; final TextEditingController searchBarController; final Function(List value) onChanged; - const SelectPlayerWidget({ + const PlayerSelection({ super.key, required this.groupNameController, required this.searchBarController, @@ -22,10 +22,10 @@ class SelectPlayerWidget extends StatefulWidget { }); @override - State createState() => _SelectPlayerWidgetState(); + State createState() => _PlayerSelectionState(); } -class _SelectPlayerWidgetState extends State { +class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; From 686463720ac89d28c2f31421678c7ff77678df8b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:17:27 +0100 Subject: [PATCH 09/18] refactor for new name and remove hide in material import --- lib/presentation/views/main_menu/create_group_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 5636c18..f8bbec0 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -1,11 +1,11 @@ -import 'package:flutter/material.dart' hide ButtonStyle; +import 'package:flutter/material.dart'; 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'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/select_player_widget.dart'; +import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; import 'package:provider/provider.dart'; @@ -69,7 +69,7 @@ class _CreateGroupViewState extends State { ), ), Expanded( - child: SelectPlayerWidget( + child: PlayerSelection( groupNameController: _groupNameController, searchBarController: _searchBarController, onChanged: (value) { From 54b54796e858a3cec3a330e312f840efb1735584 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:19:05 +0100 Subject: [PATCH 10/18] remove print --- lib/presentation/views/main_menu/create_group_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f8bbec0..c32986a 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -74,7 +74,6 @@ class _CreateGroupViewState extends State { searchBarController: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; - print(selectedPlayers); }, ), ), From 442e1d64a36d24809938143401b3321f3412ed47 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:24:51 +0100 Subject: [PATCH 11/18] remove uneccessary groupNameController --- lib/presentation/views/main_menu/create_group_view.dart | 1 - lib/presentation/widgets/player_selection.dart | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index c32986a..28566f6 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -70,7 +70,6 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - groupNameController: _groupNameController, searchBarController: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 3032c06..c6b2f1a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,13 +10,11 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController groupNameController; final TextEditingController searchBarController; final Function(List value) onChanged; const PlayerSelection({ super.key, - required this.groupNameController, required this.searchBarController, required this.onChanged, }); From a2522cef133f3df86283e2f8dab6e6083adfcb0d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:26:55 +0100 Subject: [PATCH 12/18] rename searchBarController to controller in PlayerSelection widget --- lib/presentation/views/main_menu/create_group_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 28566f6..f593efe 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -70,7 +70,7 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - searchBarController: _searchBarController, + controller: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; }, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index c6b2f1a..cf70072 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,12 +10,12 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController searchBarController; + final TextEditingController controller; final Function(List value) onChanged; const PlayerSelection({ super.key, - required this.searchBarController, + required this.controller, required this.onChanged, }); @@ -39,7 +39,7 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController = widget.searchBarController; + _searchBarController = widget.controller; loadPlayerList(); } From fc9779153d87b5e7850dbf9547c1e7e0d0147ae7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:19 +0100 Subject: [PATCH 13/18] Remove unused `_searchBarController` from CreateGroupView --- lib/presentation/views/main_menu/create_group_view.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index f593efe..2fe2fc5 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -18,7 +18,6 @@ class CreateGroupView extends StatefulWidget { class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); - final _searchBarController = TextEditingController(); late final AppDatabase db; List selectedPlayers = []; @@ -29,15 +28,11 @@ class _CreateGroupViewState extends State { _groupNameController.addListener(() { setState(() {}); }); - _searchBarController.addListener(() { - setState(() {}); - }); } @override void dispose() { _groupNameController.dispose(); - _searchBarController.dispose(); super.dispose(); } @@ -70,7 +65,6 @@ class _CreateGroupViewState extends State { ), Expanded( child: PlayerSelection( - controller: _searchBarController, onChanged: (value) { selectedPlayers = [...value]; }, @@ -92,8 +86,6 @@ class _CreateGroupViewState extends State { ); if (!context.mounted) return; if (success) { - _groupNameController.clear(); - _searchBarController.clear(); Navigator.pop(context); } else { ScaffoldMessenger.of(context).showSnackBar( From ebb531d825cc3bcc4dde2201cd0829022f8b26d0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:28 +0100 Subject: [PATCH 14/18] initialize `_searchBarController` internally instead of using widget controller --- lib/presentation/widgets/player_selection.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index cf70072..2e42d80 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -27,7 +27,8 @@ class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; - late final TextEditingController _searchBarController; + late final TextEditingController _searchBarController = + TextEditingController(); late final AppDatabase db; late Future> _allPlayersFuture; late final List skeletonData = List.filled( @@ -39,7 +40,6 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _searchBarController = widget.controller; loadPlayerList(); } From f8c0dbba5a8718e0cee4006893938fdc00743cf8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:33:53 +0100 Subject: [PATCH 15/18] Remove unused TextEditingController from PlayerSelection widget --- lib/presentation/widgets/player_selection.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 2e42d80..1c5120f 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -10,14 +10,9 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class PlayerSelection extends StatefulWidget { - final TextEditingController controller; final Function(List value) onChanged; - const PlayerSelection({ - super.key, - required this.controller, - required this.onChanged, - }); + const PlayerSelection({super.key, required this.onChanged}); @override State createState() => _PlayerSelectionState(); From 7be80e6f917a2a2e45e55115fb603d273c872df7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:44:41 +0100 Subject: [PATCH 16/18] Translate player selection UI text to English --- lib/presentation/widgets/player_selection.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 1c5120f..d1ea90e 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -98,7 +98,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - 'Ausgewählte Spieler: (${selectedPlayers.length})', + 'Selected players: (${selectedPlayers.length})', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -131,7 +131,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), const Text( - 'Alle Spieler:', + 'All players:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), From 0653700f9ce8f2547a971f44e1add59a1301c880 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 21:56:57 +0100 Subject: [PATCH 17/18] Added comments to explain player filtering and selection logic in PlayerSelection widget --- lib/presentation/widgets/player_selection.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d1ea90e..561f27c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -78,11 +78,15 @@ class _PlayerSelectionState extends State { }, onChanged: (value) { setState(() { + // Filters the list of suggested players based on the search input. if (value.isEmpty) { + // If the search is empty, it shows all unselected players. suggestedPlayers = allPlayers.where((player) { return !selectedPlayers.contains(player); }).toList(); } else { + // If there is input, it filters by name match (case-insensitive) and ensures + // that already selected players are excluded from the results. suggestedPlayers = allPlayers.where((player) { final bool nameMatches = player.name.toLowerCase().contains( value.toLowerCase(), @@ -108,21 +112,22 @@ class _PlayerSelectionState extends State { spacing: 8.0, runSpacing: 8.0, children: [ + // Generates a TextIconTile for each selected player. for (var player in selectedPlayers) TextIconTile( text: player.name, onIconTap: () { setState(() { + // Removes the player from the selection and notifies the parent. final currentSearch = _searchBarController.text .toLowerCase(); selectedPlayers.remove(player); widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), + // they are added back to the suggestions and the list is re-sorted. if (currentSearch.isEmpty || player.name.toLowerCase().contains(currentSearch)) { suggestedPlayers.add(player); - suggestedPlayers.sort( - (a, b) => a.name.compareTo(b.name), - ); } }); }, From 51e3c04e723e25f61a33b0bf025ccb2809f7e773 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Mon, 24 Nov 2025 22:08:42 +0100 Subject: [PATCH 18/18] Refactor PlayerSelection to use AppSkeleton widget --- .../views/main_menu/create_group_view.dart | 1 - lib/presentation/widgets/player_selection.dart | 18 ++---------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index 3055804..2fe2fc5 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -4,7 +4,6 @@ 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'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input_field.dart'; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 561f27c..ad15363 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -2,12 +2,12 @@ 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/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/custom_search_bar.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'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; @@ -170,22 +170,8 @@ class _PlayerSelectionState extends State { 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), - ), + child: AppSkeleton( enabled: isLoading, - enableSwitchAnimation: true, - switchAnimationConfig: const SwitchAnimationConfig( - duration: Duration(milliseconds: 200), - switchInCurve: Curves.linear, - switchOutCurve: Curves.linear, - transitionBuilder: - AnimatedSwitcher.defaultTransitionBuilder, - layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder, - ), child: Visibility( visible: (suggestedPlayers.isEmpty && allPlayers.isNotEmpty),