Merge pull request 'Spieler erstellen in CreateGroupView' (#43) from feature/42-spieler-erstellen-in-create-group-view-implementieren into development

Reviewed-on: #43
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
This commit was merged in pull request #43.
This commit is contained in:
2025-11-20 21:18:21 +00:00
2 changed files with 141 additions and 41 deletions

View File

@@ -37,6 +37,24 @@ class _CreateGroupViewState extends State<CreateGroupView> {
void initState() { void initState() {
super.initState(); super.initState();
db = Provider.of<AppDatabase>(context, listen: false); db = Provider.of<AppDatabase>(context, listen: false);
_searchBarController.addListener(() {
setState(() {});
});
_groupNameController.addListener(() {
setState(() {});
});
loadPlayerList();
}
@override
void dispose() {
_groupNameController.dispose();
_searchBarController
.dispose(); // Listener entfernen und Controller aufräumen
super.dispose();
}
void loadPlayerList() {
_allPlayersFuture = db.playerDao.getAllPlayers(); _allPlayersFuture = db.playerDao.getAllPlayers();
_allPlayersFuture.then((loadedPlayers) { _allPlayersFuture.then((loadedPlayers) {
setState(() { setState(() {
@@ -99,6 +117,19 @@ class _CreateGroupViewState extends State<CreateGroupView> {
minHeight: 45, minHeight: 45,
), ),
hintText: 'Search for players', hintText: 'Search for players',
trailingButtonShown: true,
trailingButtonicon: Icons.add_circle,
trailingButtonEnabled: _searchBarController.text
.trim()
.isNotEmpty,
onTrailingButtonPressed: () async {
addNewPlayerFromSearch(
context: context,
searchBarController: _searchBarController,
db: db,
loadPlayerList: loadPlayerList,
);
},
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
if (value.isEmpty) { if (value.isEmpty) {
@@ -216,46 +247,48 @@ class _CreateGroupViewState extends State<CreateGroupView> {
layoutBuilder: layoutBuilder:
AnimatedSwitcher.defaultLayoutBuilder, AnimatedSwitcher.defaultLayoutBuilder,
), ),
child: child: Visibility(
(suggestedPlayers.isEmpty && visible:
allPlayers.isNotEmpty) (suggestedPlayers.isEmpty &&
? TopCenteredMessage( allPlayers.isNotEmpty),
icon: Icons.info, replacement: ListView.builder(
title: 'Info', itemCount: suggestedPlayers.length,
message: itemBuilder:
(selectedPlayers.length == (BuildContext context, int index) {
allPlayers.length) return TextIconListTile(
? 'No more players to add.' text: suggestedPlayers[index].name,
: 'No players found with that name.', onPressed: () {
) setState(() {
: ListView.builder( if (!selectedPlayers.contains(
itemCount: suggestedPlayers.length, suggestedPlayers[index],
itemBuilder: )) {
(BuildContext context, int index) { selectedPlayers.add(
return TextIconListTile( suggestedPlayers[index],
text: suggestedPlayers[index] );
.name, selectedPlayers.sort(
onPressed: () { (a, b) => a.name.compareTo(
setState(() { b.name,
if (!selectedPlayers.contains( ),
suggestedPlayers[index], );
)) { suggestedPlayers.remove(
selectedPlayers.add( suggestedPlayers[index],
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.',
),
),
), ),
); );
}, },
@@ -274,7 +307,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
: () async { : () async {
bool success = await db.groupDao.addGroup( bool success = await db.groupDao.addGroup(
group: Group( group: Group(
name: _groupNameController.text, name: _groupNameController.text.trim(),
members: selectedPlayers, members: selectedPlayers,
), ),
); );
@@ -307,3 +340,47 @@ class _CreateGroupViewState extends State<CreateGroupView> {
); );
} }
} }
/// 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.
/// [searchBarController] - TextEditingController of the search bar.
/// [db] - AppDatabase instance to interact with the database.
/// [loadPlayerList] - Function to reload the player list after adding.
void addNewPlayerFromSearch({
required BuildContext context,
required TextEditingController searchBarController,
required AppDatabase db,
required Function loadPlayerList,
}) async {
String playerName = searchBarController.text.trim();
bool success = await db.playerDao.addPlayer(player: Player(name: playerName));
if (!context.mounted) return;
if (success) {
loadPlayerList();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
'Successfully added player $playerName.',
style: const TextStyle(color: Colors.white),
),
),
),
);
searchBarController.clear();
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
'Could not add player $playerName.',
style: const TextStyle(color: Colors.white),
),
),
),
);
}
}

View File

@@ -6,11 +6,19 @@ class CustomSearchBar extends StatelessWidget {
final String hintText; final String hintText;
final ValueChanged<String>? onChanged; final ValueChanged<String>? onChanged;
final BoxConstraints? constraints; final BoxConstraints? constraints;
final bool trailingButtonShown;
final bool trailingButtonEnabled;
final VoidCallback? onTrailingButtonPressed;
final IconData trailingButtonicon;
const CustomSearchBar({ const CustomSearchBar({
super.key, super.key,
required this.controller, required this.controller,
required this.hintText, required this.hintText,
this.trailingButtonShown = false,
this.trailingButtonicon = Icons.clear,
this.trailingButtonEnabled = true,
this.onTrailingButtonPressed,
this.onChanged, this.onChanged,
this.constraints, this.constraints,
}); });
@@ -22,9 +30,24 @@ class CustomSearchBar extends StatelessWidget {
constraints: constraints:
constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45),
hintText: hintText, hintText: hintText,
onChanged: onChanged, onChanged: trailingButtonEnabled ? onChanged : null,
hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)),
leading: const Icon(Icons.search), leading: const Icon(Icons.search),
trailing: [
Visibility(
visible: trailingButtonShown,
child: GestureDetector(
onTap: trailingButtonEnabled ? onTrailingButtonPressed : null,
child: Icon(
trailingButtonicon,
color: trailingButtonEnabled
? null
: Colors.grey.withValues(alpha: 0.2),
),
),
),
const SizedBox(width: 5),
],
backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor),
side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)),
shape: WidgetStateProperty.all( shape: WidgetStateProperty.all(