Merge branch 'bug/37-getallgames-returnt-nicht-alle-attribute-von-der-game-klasse' into feature/31-json-import-fuer-testdaten
This commit is contained in:
@@ -15,11 +15,23 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
Future<List<Game>> getAllGames() async {
|
||||
final query = select(gameTable);
|
||||
final result = await query.get();
|
||||
return result
|
||||
.map(
|
||||
(row) => Game(id: row.id, name: row.name, createdAt: row.createdAt),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return Future.wait(
|
||||
result.map((row) async {
|
||||
final group = await db.groupGameDao.getGroupByGameId(gameId: row.id);
|
||||
final player = await db.playerGameDao.getPlayersByGameId(
|
||||
gameId: row.id,
|
||||
);
|
||||
return Game(
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
group: group,
|
||||
players: player,
|
||||
createdAt: row.createdAt,
|
||||
winner: row.winnerId,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieves a [Game] by its [gameId].
|
||||
|
||||
@@ -23,10 +23,15 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
|
||||
}
|
||||
|
||||
/// Retrieves the [Group] associated with the given [gameId].
|
||||
Future<Group> getGroupByGameId({required String gameId}) async {
|
||||
/// Returns `null` if no group is found.
|
||||
Future<Group?> getGroupByGameId({required String gameId}) async {
|
||||
final result = await (select(
|
||||
groupGameTable,
|
||||
)..where((g) => g.gameId.equals(gameId))).getSingle();
|
||||
)..where((g) => g.gameId.equals(gameId))).getSingleOrNull();
|
||||
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
||||
return group;
|
||||
|
||||
@@ -23,19 +23,19 @@ class PlayerGameDao extends DatabaseAccessor<AppDatabase>
|
||||
}
|
||||
|
||||
/// Retrieves a list of [Player]s associated with the given [gameId].
|
||||
/// Returns an empty list if no players are found.
|
||||
Future<List<Player>> getPlayersByGameId({required String gameId}) async {
|
||||
/// Returns null if no players are found.
|
||||
Future<List<Player>?> getPlayersByGameId({required String gameId}) async {
|
||||
final result = await (select(
|
||||
playerGameTable,
|
||||
)..where((p) => p.gameId.equals(gameId))).get();
|
||||
|
||||
if (result.isEmpty) return <Player>[];
|
||||
if (result.isEmpty) return null;
|
||||
|
||||
final futures = result.map(
|
||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||
);
|
||||
final players = await Future.wait(futures);
|
||||
return players.whereType<Player>().toList();
|
||||
return players;
|
||||
}
|
||||
|
||||
/// Associates a player with a game by inserting a record into the
|
||||
|
||||
@@ -37,6 +37,24 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
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.then((loadedPlayers) {
|
||||
setState(() {
|
||||
@@ -99,6 +117,19 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
minHeight: 45,
|
||||
),
|
||||
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) {
|
||||
setState(() {
|
||||
if (value.isEmpty) {
|
||||
@@ -216,46 +247,48 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
layoutBuilder:
|
||||
AnimatedSwitcher.defaultLayoutBuilder,
|
||||
),
|
||||
child:
|
||||
(suggestedPlayers.isEmpty &&
|
||||
allPlayers.isNotEmpty)
|
||||
? 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 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: 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.',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -274,7 +307,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
: () async {
|
||||
bool success = await db.groupDao.addGroup(
|
||||
group: Group(
|
||||
name: _groupNameController.text,
|
||||
name: _groupNameController.text.trim(),
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,19 @@ class CustomSearchBar extends StatelessWidget {
|
||||
final String hintText;
|
||||
final ValueChanged<String>? onChanged;
|
||||
final BoxConstraints? constraints;
|
||||
final bool trailingButtonShown;
|
||||
final bool trailingButtonEnabled;
|
||||
final VoidCallback? onTrailingButtonPressed;
|
||||
final IconData trailingButtonicon;
|
||||
|
||||
const CustomSearchBar({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.hintText,
|
||||
this.trailingButtonShown = false,
|
||||
this.trailingButtonicon = Icons.clear,
|
||||
this.trailingButtonEnabled = true,
|
||||
this.onTrailingButtonPressed,
|
||||
this.onChanged,
|
||||
this.constraints,
|
||||
});
|
||||
@@ -22,9 +30,24 @@ class CustomSearchBar extends StatelessWidget {
|
||||
constraints:
|
||||
constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45),
|
||||
hintText: hintText,
|
||||
onChanged: onChanged,
|
||||
onChanged: trailingButtonEnabled ? onChanged : null,
|
||||
hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)),
|
||||
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),
|
||||
side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)),
|
||||
shape: WidgetStateProperty.all(
|
||||
|
||||
Reference in New Issue
Block a user