1 Commits

Author SHA1 Message Date
f0379e97c7 Merge pull request 'MVP' (#141) from development into main
Reviewed-on: #141
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
2026-01-09 12:55:50 +00:00
4 changed files with 161 additions and 175 deletions

View File

@@ -5,11 +5,10 @@ import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -34,7 +33,7 @@ class _HomeViewState extends State<HomeView> {
/// Recent matches to display, initially filled with skeleton matches /// Recent matches to display, initially filled with skeleton matches
List<Match> recentMatches = List.filled( List<Match> recentMatches = List.filled(
3, 2,
Match( Match(
name: 'Skeleton Match', name: 'Skeleton Match',
group: Group( group: Group(
@@ -44,6 +43,7 @@ class _HomeViewState extends State<HomeView> {
Player(name: 'Skeleton Player 2'), Player(name: 'Skeleton Player 2'),
], ],
), ),
winner: Player(name: 'Skeleton Player 1'),
), ),
); );
@@ -90,42 +90,74 @@ class _HomeViewState extends State<HomeView> {
child: InfoTile( child: InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: loc.recent_matches, title: loc.recent_matches,
icon: Icons.history_rounded, icon: Icons.timer,
content: Column( content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40.0),
child: Visibility(
visible: !isLoading && loadedRecentMatches.isNotEmpty,
replacement: Center(
heightFactor: 12,
child: Text(
AppLocalizations.of(
context,
).no_recent_matches_available,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (recentMatches.isNotEmpty) MatchSummaryTile(
for (Match match in recentMatches) matchTitle: recentMatches[0].name,
Padding( game: 'Winner',
padding: const EdgeInsets.symmetric( ruleset: 'Ruleset',
vertical: 6.0, players: _getPlayerText(
recentMatches[0],
context,
), ),
child: MatchTile( winner: recentMatches[0].winner == null
compact: true, ? AppLocalizations.of(
width: constraints.maxWidth * 0.9, context,
match: match, ).match_in_progress
onTap: () { : recentMatches[0].winner!.name,
Navigator.of(context).push(
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) =>
MatchResultView(match: match),
), ),
); const Padding(
}, padding: EdgeInsets.symmetric(vertical: 8.0),
child: Divider(),
), ),
) if (loadedRecentMatches.length > 1) ...[
else MatchSummaryTile(
matchTitle: recentMatches[1].name,
game: 'Winner',
ruleset: 'Ruleset',
players: _getPlayerText(
recentMatches[1],
context,
),
winner: recentMatches[1].winner == null
? AppLocalizations.of(
context,
).match_in_progress
: recentMatches[1].winner!.name,
),
const SizedBox(height: 8),
] else ...[
Center( Center(
heightFactor: 5, heightFactor: 5.35,
child: Text(loc.no_recent_matches_available), child: Text(
AppLocalizations.of(
context,
).no_second_match_available,
), ),
),
],
], ],
), ),
), ),
), ),
Padding( ),
padding: EdgeInsets.zero, ),
child: InfoTile( InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: loc.quick_create, title: loc.quick_create,
icon: Icons.add_box_rounded, icon: Icons.add_box_rounded,
@@ -173,8 +205,6 @@ class _HomeViewState extends State<HomeView> {
], ],
), ),
), ),
),
SizedBox(height: MediaQuery.paddingOf(context).bottom),
], ],
), ),
), ),
@@ -201,6 +231,11 @@ class _HomeViewState extends State<HomeView> {
..sort((a, b) => b.createdAt.compareTo(a.createdAt))) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
.take(2) .take(2)
.toList(); .toList();
if (loadedRecentMatches.length < 2) {
recentMatches.add(
Match(name: 'Dummy Match', winner: null, group: null, players: null),
);
}
if (mounted) { if (mounted) {
setState(() { setState(() {
isLoading = false; isLoading = false;
@@ -208,4 +243,19 @@ class _HomeViewState extends State<HomeView> {
} }
}); });
} }
/// Generates a text representation of the players in the match.
/// If the match has a group, it returns the group name and the number of additional players.
/// If there is no group, it returns the count of players.
String _getPlayerText(Match game, context) {
final loc = AppLocalizations.of(context);
if (game.group == null) {
final playerCount = game.players?.length ?? 0;
return loc.players_count(playerCount);
}
if (game.players == null || game.players!.isEmpty) {
return game.group!.name;
}
return '${game.group!.name} + ${game.players!.length}';
}
} }

View File

@@ -78,11 +78,7 @@ class _MatchViewState extends State<MatchView> {
height: MediaQuery.paddingOf(context).bottom - 20, height: MediaQuery.paddingOf(context).bottom - 20,
); );
} }
return Center( return MatchTile(
child: Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: MatchTile(
width: MediaQuery.sizeOf(context).width * 0.95,
onTap: () async { onTap: () async {
Navigator.push( Navigator.push(
context, context,
@@ -96,8 +92,6 @@ class _MatchViewState extends State<MatchView> {
); );
}, },
match: matches[index], match: matches[index],
),
),
); );
}, },
), ),

View File

@@ -10,16 +10,8 @@ import 'package:intl/intl.dart';
/// creation date, associated group, winner, and players. /// creation date, associated group, winner, and players.
/// - [match]: The match data to be displayed. /// - [match]: The match data to be displayed.
/// - [onTap]: The callback invoked when the tile is tapped. /// - [onTap]: The callback invoked when the tile is tapped.
/// - [width]: Optional width for the tile.
/// - [compact]: Whether to display the tile in a compact mode
class MatchTile extends StatefulWidget { class MatchTile extends StatefulWidget {
const MatchTile({ const MatchTile({super.key, required this.match, required this.onTap});
super.key,
required this.match,
required this.onTap,
this.width,
this.compact = false,
});
/// The match data to be displayed. /// The match data to be displayed.
final Match match; final Match match;
@@ -27,12 +19,6 @@ class MatchTile extends StatefulWidget {
/// The callback invoked when the tile is tapped. /// The callback invoked when the tile is tapped.
final VoidCallback onTap; final VoidCallback onTap;
/// Optional width for the tile.
final double? width;
/// Whether to display the tile in a compact mode
final bool compact;
@override @override
State<MatchTile> createState() => _MatchTileState(); State<MatchTile> createState() => _MatchTileState();
} }
@@ -55,10 +41,13 @@ class _MatchTileState extends State<MatchTile> {
return GestureDetector( return GestureDetector(
onTap: widget.onTap, onTap: widget.onTap,
child: Container( child: Container(
margin: EdgeInsets.zero, margin: CustomTheme.tileMargin,
width: widget.width,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: CustomTheme.standardBoxDecoration, decoration: BoxDecoration(
color: CustomTheme.boxColor,
border: Border.all(color: CustomTheme.boxBorder),
borderRadius: BorderRadius.circular(12),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -91,22 +80,7 @@ class _MatchTileState extends State<MatchTile> {
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
'${group.name}${widget.match.players != null ? ' + ${widget.match.players?.length}' : ''}', group.name,
style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 12),
] else if (widget.compact) ...[
Row(
children: [
const Icon(Icons.person, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Expanded(
child: Text(
'${widget.match.players!.length} ${loc.players}',
style: const TextStyle(fontSize: 14, color: Colors.grey), style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -153,46 +127,9 @@ class _MatchTileState extends State<MatchTile> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
] else ...[
Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
color: Colors.amber.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.amber.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
children: [
const Icon(
Icons.watch_later,
size: 20,
color: Colors.amber,
),
const SizedBox(width: 8),
Expanded(
child: Text(
loc.match_in_progress,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: CustomTheme.textColor,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(height: 12),
], ],
if (_allPlayers.isNotEmpty && widget.compact == false) ...[ if (_allPlayers.isNotEmpty) ...[
Text( Text(
loc.players, loc.players,
style: const TextStyle( style: const TextStyle(

View File

@@ -1,7 +1,7 @@
name: game_tracker name: game_tracker
description: "Game Tracking App for Card Games" description: "Game Tracking App for Card Games"
publish_to: 'none' publish_to: 'none'
version: 0.0.2+70 version: 0.0.1+21
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
@@ -9,6 +9,11 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
material_symbols_icons: ^4.2815.1
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
drift: ^2.27.0 drift: ^2.27.0
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
path_provider: ^2.1.5 path_provider: ^2.1.5