MVP #141
@@ -15,11 +15,23 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|||||||
Future<List<Game>> getAllGames() async {
|
Future<List<Game>> getAllGames() async {
|
||||||
final query = select(gameTable);
|
final query = select(gameTable);
|
||||||
final result = await query.get();
|
final result = await query.get();
|
||||||
return result
|
|
||||||
.map(
|
return Future.wait(
|
||||||
(row) => Game(id: row.id, name: row.name, createdAt: row.createdAt),
|
result.map((row) async {
|
||||||
)
|
final group = await db.groupGameDao.getGroupByGameId(gameId: row.id);
|
||||||
.toList();
|
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].
|
/// Retrieves a [Game] by its [gameId].
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the [Group] associated with the given [gameId].
|
/// 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(
|
final result = await (select(
|
||||||
groupGameTable,
|
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);
|
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
||||||
return group;
|
return group;
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ class PlayerGameDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a list of [Player]s associated with the given [gameId].
|
/// Retrieves a list of [Player]s associated with the given [gameId].
|
||||||
/// Returns an empty list if no players are found.
|
/// Returns null if no players are found.
|
||||||
Future<List<Player>> getPlayersByGameId({required String gameId}) async {
|
Future<List<Player>?> getPlayersByGameId({required String gameId}) async {
|
||||||
final result = await (select(
|
final result = await (select(
|
||||||
playerGameTable,
|
playerGameTable,
|
||||||
)..where((p) => p.gameId.equals(gameId))).get();
|
)..where((p) => p.gameId.equals(gameId))).get();
|
||||||
|
|
||||||
if (result.isEmpty) return <Player>[];
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
final futures = result.map(
|
final futures = result.map(
|
||||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||||
);
|
);
|
||||||
final players = await Future.wait(futures);
|
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
|
/// Associates a player with a game by inserting a record into the
|
||||||
|
|||||||
@@ -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,25 +247,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
layoutBuilder:
|
layoutBuilder:
|
||||||
AnimatedSwitcher.defaultLayoutBuilder,
|
AnimatedSwitcher.defaultLayoutBuilder,
|
||||||
),
|
),
|
||||||
child:
|
child: Visibility(
|
||||||
|
visible:
|
||||||
(suggestedPlayers.isEmpty &&
|
(suggestedPlayers.isEmpty &&
|
||||||
allPlayers.isNotEmpty)
|
allPlayers.isNotEmpty),
|
||||||
? TopCenteredMessage(
|
replacement: ListView.builder(
|
||||||
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,
|
itemCount: suggestedPlayers.length,
|
||||||
itemBuilder:
|
itemBuilder:
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return TextIconListTile(
|
return TextIconListTile(
|
||||||
text: suggestedPlayers[index]
|
text: suggestedPlayers[index].name,
|
||||||
.name,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (!selectedPlayers.contains(
|
if (!selectedPlayers.contains(
|
||||||
@@ -244,8 +266,9 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
suggestedPlayers[index],
|
suggestedPlayers[index],
|
||||||
);
|
);
|
||||||
selectedPlayers.sort(
|
selectedPlayers.sort(
|
||||||
(a, b) => a.name
|
(a, b) => a.name.compareTo(
|
||||||
.compareTo(b.name),
|
b.name,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
suggestedPlayers.remove(
|
suggestedPlayers.remove(
|
||||||
suggestedPlayers[index],
|
suggestedPlayers[index],
|
||||||
@@ -256,6 +279,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user