diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index a962c05..5689c7b 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -1,7 +1,15 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/tiles/double_row_info_tile.dart'; +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/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; +import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:provider/provider.dart'; class GameHistoryView extends StatefulWidget { const GameHistoryView({super.key}); @@ -11,119 +19,43 @@ class GameHistoryView extends StatefulWidget { } class _GameHistoryViewState extends State { - final allGameData = [ - { - 'game': 'Schach', - 'title': 'Abendpartie', - 'players': 2, - 'group': 'Familie', - 'date': '01.06.2024', - }, - { - 'game': 'Monopoly', - 'title': 'Wochenendspaß mit Gras du Saas', - 'players': 4, - 'group': 'Freunde', - 'date': '28.05.2024', - }, - { - 'game': 'Catan', - 'title': 'Strategieabend', - 'players': 3, - 'group': 'Brettspieler', - 'date': '25.05.2024', - }, - { - 'game': 'Uno', - 'title': 'Schnelle Runde', - 'players': 5, - 'group': 'Kollegen', - 'date': '22.05.2024', - }, - { - 'game': 'Poker', - 'title': 'Freitagspoker', - 'players': 6, - 'group': 'Pokerclub', - 'date': '20.05.2024', - }, - { - 'game': 'Scrabble', - 'title': 'Wortschlacht', - 'players': 4, - 'group': 'Familie', - 'date': '18.05.2024', - }, - { - 'game': 'Risiko', - 'title': 'Weltherrschaft', - 'players': 5, - 'group': 'Strategiegruppe', - 'date': '15.05.2024', - }, - { - 'game': 'Zug um Zug', - 'title': 'Zug-Abenteuer', - 'players': 4, - 'group': 'Reisende', - 'date': '12.05.2024', - }, - { - 'game': 'Carcassonne', - 'title': 'Plättchenlegen', - 'players': 3, - 'group': 'Brettspieler', - 'date': '10.05.2024', - }, - { - 'game': 'Pandemie', - 'title': 'Welt retten', - 'players': 4, - 'group': 'Koop-Team', - 'date': '08.05.2024', - }, - { - 'game': 'Cluedo', - 'title': 'Krimiabend', - 'players': 6, - 'group': 'Detektive', - 'date': '05.05.2024', - }, - { - 'game': 'Dixit', - 'title': 'Fantasiespiel', - 'players': 5, - 'group': 'Künstler', - 'date': '02.05.2024', - }, - { - 'game': 'Azul', - 'title': 'Plättchenmeister', - 'players': 4, - 'group': 'Familie', - 'date': '30.04.2024', - }, - { - 'game': 'Splendor', - 'title': 'Edelsteinhändler', - 'players': 3, - 'group': 'Freunde', - 'date': '28.04.2024', - }, - { - 'game': '7 Wonders', - 'title': 'Antike Reiche', - 'players': 7, - 'group': 'Geschichtsfreunde', - 'date': '25.04.2024', - }, - ]; - late List> suggestedGameData; + late Future> _gameListFuture; + late final AppDatabase db; + late bool isLoading = true; + + late final List skeletonData = List.filled( + 4, + Game( + name: 'Skeleton Game', + group: Group( + name: 'Skeleton Group', + members: [ + Player(name: 'Player 1'), + Player(name: 'Player 2'), + Player(name: 'Player 3'), + Player(name: 'Long Name Player 4'), + Player(name: 'Player 5'), + ], + ), + winner: Player(name: 'Skeleton Player 1'), + players: [ + Player(name: 'Skeleton Player 6') + ], + ), + ); @override void initState() { super.initState(); - suggestedGameData = List.from(allGameData); + db = Provider.of(context, listen: false); + _gameListFuture = db.gameDao.getAllGames(); + + Future.wait([_gameListFuture]).then((result) async { + await Future.delayed(const Duration(milliseconds: 250)); + setState(() { + isLoading = false; + }); + }); } @override @@ -131,41 +63,70 @@ class _GameHistoryViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( + alignment: Alignment.center, children: [ - Column( - children: [ - Container(margin: const EdgeInsets.only(bottom: 75)), - Expanded( - child: gameHistoryListView(allGameData, suggestedGameData), - ), - ], - ), - Container( - margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), - child: SearchBar( - leading: const Icon(Icons.search), - onChanged: (value) { - if (value.isEmpty) { - setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(allGameData); - }); - return; - } - final suggestions = allGameData.where((currentGame) { - return currentGame['game'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['title'].toString().toLowerCase().contains( - value.toLowerCase(), - ) || - currentGame['group'].toString().toLowerCase().contains( - value.toLowerCase(), + FutureBuilder>( + future: _gameListFuture, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'Game data could not be loaded', + ), + ); + } + if (snapshot.connectionState == ConnectionState.done && + (!snapshot.hasData || snapshot.data!.isEmpty)) { + return const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Error', + message: 'No Games Available', + ), + ); + } + + final List games = (isLoading + ? skeletonData + : (snapshot.data ?? []) + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .toList(); + + return AppSkeleton( + enabled: isLoading, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: games.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == games.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, ); - }); + } + return GameHistoryTile(game: games[index]); // Placeholder + }, + ), + ); + }, + ), + Positioned( + bottom: MediaQuery.paddingOf(context).bottom, + child: CustomWidthButton( + text: 'Create Game', + sizeRelativeToWidth: 0.90, + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ), + ); setState(() { - suggestedGameData.clear(); - suggestedGameData.addAll(suggestions); + _gameListFuture = db.gameDao.getAllGames(); }); }, ), @@ -174,33 +135,4 @@ class _GameHistoryViewState extends State { ), ); } -} - -Widget gameHistoryListView(allGameData, suggestedGameData) { - if (suggestedGameData.isEmpty && allGameData.isEmpty) { - return const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'Keine Spiele erstellt', - ); - } else if (suggestedGameData.isEmpty) { - return const TopCenteredMessage( - icon: Icons.search, - title: 'Info', - message: 'Kein Spiel mit den Suchparametern gefunden.', - ); - } - return ListView.builder( - itemCount: suggestedGameData.length, - itemBuilder: (context, index) { - final currentGame = suggestedGameData[index]; - return doubleRowInfoTile( - currentGame['game'] + ': ', - currentGame['title'], - "${currentGame['players']} Spieler", - currentGame['group'], - currentGame['date'], - ); - }, - ); -} +} \ No newline at end of file diff --git a/lib/presentation/widgets/tiles/double_row_info_tile.dart b/lib/presentation/widgets/tiles/double_row_info_tile.dart deleted file mode 100644 index 57404ff..0000000 --- a/lib/presentation/widgets/tiles/double_row_info_tile.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; - -Widget doubleRowInfoTile( - String titleOneUpperLeft, - String titleTwoUpperLeft, - String titleUpperRight, - String titleLowerLeft, - String titleLowerRight, -) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: CustomTheme.secondaryColor, - ), - child: Column( - children: [ - Row( - children: [ - Expanded( - flex: 10, - child: Text( - '$titleOneUpperLeft $titleTwoUpperLeft', - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 3, - child: Text( - titleUpperRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - Row( - children: [ - Expanded( - flex: 10, - child: Text( - titleLowerLeft, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - const Spacer(), - Expanded( - flex: 4, - child: Text( - titleLowerRight, - style: const TextStyle(fontSize: 20), - overflow: TextOverflow.ellipsis, - maxLines: 1, - textAlign: TextAlign.end, - ), - ), - ], - ), - ], - ), - ); -} diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart new file mode 100644 index 0000000..3cdd1ad --- /dev/null +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:intl/intl.dart'; + +class GameHistoryTile extends StatefulWidget { + final Game game; + + const GameHistoryTile({ + super.key, + required this.game, + }); + + @override + State createState() => _GameHistoryTileState(); +} + +class _GameHistoryTileState extends State { + + @override + Widget build(BuildContext context) { + final group = widget.game.group; + final winner = widget.game.winner; + final allPlayers = _getAllPlayers(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorder), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + widget.game.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + Text( + _formatDate(widget.game.createdAt), + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + ], + ), + + const SizedBox(height: 8), + + if (group != null) ...[ + Row( + children: [ + const Icon( + Icons.group, + size: 16, + color: Colors.grey, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + group.name, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 12), + ], + + if (winner != null) ...[ + Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.emoji_events, + size: 20, + color: Colors.amber, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Winner: ${winner.name}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(height: 12), + ], + + if (allPlayers.isNotEmpty) ...[ + const Text( + 'Players', + style: TextStyle( + fontSize: 13, + color: Colors.grey, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: allPlayers.map((player) { + return TextIconTile( + text: player.name, + iconEnabled: false, + ); + }).toList(), + ), + ], + ], + ), + ); + } + + String _formatDate(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inDays == 0) { + return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays == 1) { + return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM d, yyyy').format(dateTime); + } + } + + List _getAllPlayers() { + final allPlayers = []; + final playerIds = {}; + + // Add players from game.players + if (widget.game.players != null) { + for (var player in widget.game.players!) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + // Add players from game.group.players + if (widget.game.group?.members != null) { + for (var player in widget.game.group!.members) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); + } + } + } + + return allPlayers; + } + +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index fa4c213..07e4df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 + intl: ^0.18.0 dev_dependencies: flutter_test: