changed to use standardized tiles and fixed search bug

This commit is contained in:
2025-11-18 20:10:48 +01:00
parent 8f9289617f
commit 7781284289

View File

@@ -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/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/presentation/widgets/custom_search_bar.dart';
import 'package:game_tracker/presentation/widgets/full_width_button.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:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:skeletonizer/skeletonizer.dart'; import 'package:skeletonizer/skeletonizer.dart';
import 'package:uuid/uuid.dart';
class CreateGroupView extends StatefulWidget { class CreateGroupView extends StatefulWidget {
const CreateGroupView({super.key}); const CreateGroupView({super.key});
@@ -61,29 +66,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
children: [ children: [
Container( Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: TextField( child: TextInputField(
controller: _groupNameController, controller: _groupNameController,
hintText: 'Group name',
onChanged: (value) { onChanged: (value) {
setState(() {}); 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( Expanded(
@@ -104,29 +92,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SearchBar( CustomSearchBar(
controller: _searchBarController, controller: _searchBarController,
constraints: BoxConstraints(maxHeight: 45, minHeight: 45), constraints: BoxConstraints(maxHeight: 45, minHeight: 45),
hintText: "Search for players", 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) { onChanged: (value) {
setState(() { setState(() {
if (value.isEmpty) { if (value.isEmpty) {
suggestedPlayers = [...allPlayers]; suggestedPlayers = allPlayers.where((player) {
return !selectedPlayers.contains(player);
}).toList();
} else { } else {
suggestedPlayers = allPlayers.where((player) { suggestedPlayers = allPlayers.where((player) {
final bool nameMatches = player.name final bool nameMatches = player.name
@@ -156,50 +131,25 @@ class _CreateGroupViewState extends State<CreateGroupView> {
runSpacing: 8.0, runSpacing: 8.0,
children: <Widget>[ children: <Widget>[
for (var selectedPlayer in selectedPlayers) for (var selectedPlayer in selectedPlayers)
Container( TextIconTile(
padding: EdgeInsets.all(5), text: selectedPlayer.name,
decoration: BoxDecoration( icon: Icons.close,
color: CustomTheme.onBoxColor, onIconTap: () {
borderRadius: BorderRadius.circular(12), setState(() {
), final currentSearch = _searchBarController.text
child: Row( .toLowerCase();
mainAxisAlignment: MainAxisAlignment.spaceBetween, selectedPlayers.remove(selectedPlayer);
mainAxisSize: MainAxisSize.min, if (currentSearch.isEmpty ||
children: [ selectedPlayer.name.toLowerCase().contains(
SizedBox(width: 12), currentSearch,
Flexible( )) {
child: Text( suggestedPlayers.add(selectedPlayer);
selectedPlayer.name, suggestedPlayers.sort(
overflow: TextOverflow.ellipsis, (a, b) => a.name.compareTo(b.name),
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),
);
}
});
},
),
],
),
), ),
], ],
), ),
@@ -214,125 +164,100 @@ class _CreateGroupViewState extends State<CreateGroupView> {
SizedBox(height: 10), SizedBox(height: 10),
FutureBuilder( FutureBuilder(
future: _allPlayersFuture, future: _allPlayersFuture,
builder: (BuildContext context, AsyncSnapshot<List<Player>> snapshot) { builder:
if (snapshot.hasError) { (
return const Center( BuildContext context,
child: TopCenteredMessage( AsyncSnapshot<List<Player>> snapshot,
icon: Icons.report, ) {
title: 'Error', if (snapshot.hasError) {
message: 'Player data couldn\'t\nbe loaded.', return const Center(
), child: TopCenteredMessage(
); icon: Icons.report,
} title: 'Error',
if (snapshot.connectionState == ConnectionState.done && message: 'Player data couldn\'t\nbe loaded.',
(!snapshot.hasData || ),
snapshot.data!.isEmpty || );
(selectedPlayers.isEmpty && }
allPlayers.isEmpty))) { if (snapshot.connectionState ==
return const Center( ConnectionState.done &&
child: TopCenteredMessage( (!snapshot.hasData ||
icon: Icons.info, snapshot.data!.isEmpty ||
title: 'Info', (selectedPlayers.isEmpty &&
message: 'No players created yet.', allPlayers.isEmpty))) {
), return const Center(
); child: TopCenteredMessage(
} icon: Icons.info,
final bool isLoading = title: 'Info',
snapshot.connectionState == ConnectionState.waiting; message: 'No players created yet.',
return Expanded( ),
child: Skeletonizer( );
effect: PulseEffect( }
from: Colors.grey[800]!, final bool isLoading =
to: Colors.grey[600]!, snapshot.connectionState ==
duration: const Duration(milliseconds: 800), ConnectionState.waiting;
), return Expanded(
enabled: isLoading, child: Skeletonizer(
enableSwitchAnimation: true, effect: PulseEffect(
switchAnimationConfig: const SwitchAnimationConfig( from: Colors.grey[800]!,
duration: Duration(milliseconds: 200), to: Colors.grey[600]!,
switchInCurve: Curves.linear, duration: const Duration(milliseconds: 800),
switchOutCurve: Curves.linear, ),
transitionBuilder: enabled: isLoading,
AnimatedSwitcher.defaultTransitionBuilder, enableSwitchAnimation: true,
layoutBuilder: switchAnimationConfig:
AnimatedSwitcher.defaultLayoutBuilder, const SwitchAnimationConfig(
), duration: Duration(milliseconds: 200),
child: switchInCurve: Curves.linear,
(suggestedPlayers.isEmpty && switchOutCurve: Curves.linear,
!allPlayers.isEmpty) transitionBuilder: AnimatedSwitcher
? TopCenteredMessage( .defaultTransitionBuilder,
icon: Icons.info, layoutBuilder:
title: 'Info', AnimatedSwitcher.defaultLayoutBuilder,
message: ),
(selectedPlayers.length == child:
allPlayers.length) (suggestedPlayers.isEmpty &&
? 'No more players to add.' !allPlayers.isEmpty)
: 'No players found with that name.', ? TopCenteredMessage(
) icon: Icons.info,
: ListView.builder( title: 'Info',
itemCount: suggestedPlayers.length, message:
itemBuilder: (BuildContext context, int index) { (selectedPlayers.length ==
return Container( allPlayers.length)
margin: const EdgeInsets.symmetric( ? 'No more players to add.'
horizontal: 5, : 'No players found with that name.',
vertical: 5, )
), : ListView.builder(
padding: const EdgeInsets.symmetric( itemCount: suggestedPlayers.length,
horizontal: 10, itemBuilder:
), (BuildContext context, int index) {
decoration: BoxDecoration( return IconListTile(
color: CustomTheme.boxColor, text: suggestedPlayers[index]
border: Border.all( .name,
color: CustomTheme.boxBorder, icon: Icons.add,
), onPressed: () {
borderRadius: BorderRadius.circular( setState(() {
12, if (!selectedPlayers.contains(
),
),
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(
suggestedPlayers[index], suggestedPlayers[index],
); )) {
selectedPlayers.sort( selectedPlayers.add(
(a, b) => a.name suggestedPlayers[index],
.compareTo(b.name), );
); selectedPlayers.sort(
suggestedPlayers.remove( (a, b) => a.name
suggestedPlayers[index], .compareTo(b.name),
); );
} suggestedPlayers.remove(
}); suggestedPlayers[index],
}, );
), }
], });
), },
); );
}, },
), ),
), ),
); );
}, },
), ),
], ],
), ),
@@ -348,11 +273,12 @@ class _CreateGroupViewState extends State<CreateGroupView> {
(_groupNameController.text.isEmpty || selectedPlayers.isEmpty) (_groupNameController.text.isEmpty || selectedPlayers.isEmpty)
? null ? null
: () async { : () async {
String id = "ID_" + _groupNameController.text;
String name = _groupNameController.text;
List<Player> members = selectedPlayers;
bool success = await db.groupDao.addGroup( 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) { if (success) {
_groupNameController.clear(); _groupNameController.clear();