GameResultView erstellen #62
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
@@ -5,6 +6,7 @@ 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/views/main_menu/game_result_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart';
|
||||
@@ -21,7 +23,6 @@ class GameHistoryView extends StatefulWidget {
|
||||
class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
late Future<List<Game>> _gameListFuture;
|
||||
late final AppDatabase db;
|
||||
late bool isLoading = true;
|
||||
|
||||
late final List<Game> skeletonData = List.filled(
|
||||
4,
|
||||
@@ -46,14 +47,10 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_gameListFuture = db.gameDao.getAllGames();
|
||||
|
||||
Future.wait([_gameListFuture]).then((result) async {
|
||||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
});
|
||||
_gameListFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.gameDao.getAllGames(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -81,19 +78,19 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
return const Center(
|
||||
child: TopCenteredMessage(
|
||||
icon: Icons.report,
|
||||
title: 'Error',
|
||||
message: 'No Games Available',
|
||||
title: 'Info',
|
||||
|
sneeex marked this conversation as resolved
Outdated
|
||||
message: 'No games created yet',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final bool isLoading =
|
||||
snapshot.connectionState == ConnectionState.waiting;
|
||||
final List<Game> games =
|
||||
(isLoading ? skeletonData : (snapshot.data ?? [])
|
||||
..sort(
|
||||
(a, b) => b.createdAt.compareTo(a.createdAt),
|
||||
))
|
||||
.toList();
|
||||
|
||||
return AppSkeleton(
|
||||
enabled: isLoading,
|
||||
child: ListView.builder(
|
||||
@@ -106,8 +103,21 @@ class _GameHistoryViewState extends State<GameHistoryView> {
|
||||
);
|
||||
}
|
||||
return GameHistoryTile(
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
ich fände hier noch `fullscreenDialog: true' cool, ich finde das passt besser zur Navigation, also: ich fände hier noch `fullscreenDialog: true' cool, ich finde das passt besser zur Navigation, also:
```dart
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) =>
GameResultView(game: games[index]),
),
);
setState(() {
_gameListFuture = db.gameDao.getAllGames();
});
}
```
sneeex
commented
aber finde die animation dann komisch, das fadet so ein und sehe da jetzt keinen großen unterschied, also warum fullscreendialog und nicht das normale? aber finde die animation dann komisch, das fadet so ein und sehe da jetzt keinen großen unterschied, also warum fullscreendialog und nicht das normale?
flixcoo
commented
Korrektur: Korrektur:
```dart
onTap: () async {
await Navigator.push(
context,
CupertinoPageRoute(
fullscreenDialog: true,
builder: (context) =>
GameResultView(game: games[index]),
),
);
setState(() {
_gameListFuture = db.gameDao.getAllGames();
});
}
```
|
||||
CupertinoPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) =>
|
||||
GameResultView(game: games[index]),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_gameListFuture = db.gameDao.getAllGames();
|
||||
});
|
||||
},
|
||||
game: games[index],
|
||||
); // Placeholder
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
144
lib/presentation/views/main_menu/game_result_view.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class GameResultView extends StatefulWidget {
|
||||
final Game game;
|
||||
|
||||
const GameResultView({super.key, required this.game});
|
||||
|
||||
@override
|
||||
State<GameResultView> createState() => _GameResultViewState();
|
||||
}
|
||||
|
||||
class _GameResultViewState extends State<GameResultView> {
|
||||
late final List<Player> allPlayers;
|
||||
late final AppDatabase db;
|
||||
Player? _selectedPlayer;
|
||||
|
||||
@override
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Umbenennen in Umbenennen in `selectedPlayer` oder `currentWinner` o.Ä.
|
||||
void initState() {
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
allPlayers = getAllPlayers(widget.game);
|
||||
if (widget.game.winner != null) {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.game.winner!.id,
|
||||
);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Text(
|
||||
widget.game.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Winner:',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Würde Würde `height` mindestens auf 10 erhöhen
|
||||
Expanded(
|
||||
child: RadioGroup<Player>(
|
||||
groupValue: _selectedPlayer,
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Macht das Sinn überhaupt zu implementieren? Gibt es Fälle in denen ein Spiel keine Spieler hat? Macht das Sinn überhaupt zu implementieren? Gibt es Fälle in denen ein Spiel keine Spieler hat?
sneeex
commented
Eigentlich nicht, soll ich's weglassen dann? Eigentlich nicht, soll ich's weglassen dann?
|
||||
onChanged: (Player? value) async {
|
||||
setState(() {
|
||||
_selectedPlayer = value;
|
||||
});
|
||||
await _handleWinnerSaving();
|
||||
},
|
||||
child: ListView.builder(
|
||||
itemCount: allPlayers.length,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomRadioListTile(
|
||||
text: allPlayers[index].name,
|
||||
value: allPlayers[index],
|
||||
onContainerTap: (value) async {
|
||||
setState(() {
|
||||
// Check if the already selected player is the same as the newly tapped player.
|
||||
if (_selectedPlayer == value) {
|
||||
// If yes deselected the player by setting it to null.
|
||||
_selectedPlayer = null;
|
||||
} else {
|
||||
// If no assign the newly tapped player to the selected player.
|
||||
(_selectedPlayer = value);
|
||||
}
|
||||
});
|
||||
await _handleWinnerSaving();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleWinnerSaving() async {
|
||||
if (_selectedPlayer == null) {
|
||||
await db.gameDao.removeWinner(gameId: widget.game.id);
|
||||
} else {
|
||||
await db.gameDao.setWinner(
|
||||
|
sneeex marked this conversation as resolved
Outdated
gelbeinhalb
commented
Hier ist auch kein Punkt :) Hier ist auch kein Punkt :)
Würde ich aber auch so lassen höhö
|
||||
gameId: widget.game.id,
|
||||
winnerId: _selectedPlayer!.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Player> getAllPlayers(Game game) {
|
||||
if (game.group == null && game.players != null) {
|
||||
return [...game.players!];
|
||||
} else if (game.group != null && game.players != null) {
|
||||
return [...game.players!, ...game.group!.members];
|
||||
}
|
||||
return [...game.group!.members];
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
child: TopCenteredMessage(
|
||||
icon: Icons.report,
|
||||
title: 'Error',
|
||||
message: 'Group data couldn\'t\nbe loaded.',
|
||||
message: 'Group data couldn\'t\nbe loaded',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
child: TopCenteredMessage(
|
||||
icon: Icons.info,
|
||||
title: 'Info',
|
||||
message: 'No groups created yet.',
|
||||
message: 'No groups created yet',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
50
lib/presentation/widgets/tiles/custom_radio_list_tile.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
class CustomRadioListTile<T> extends StatelessWidget {
|
||||
final String text;
|
||||
final T value;
|
||||
final ValueChanged<T> onContainerTap;
|
||||
|
||||
const CustomRadioListTile({
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Würde Umbennenen in Würde Umbennenen in `RadioListTile`, weil das gibt es bisher noch nicht, oder? Würde `Custom[Widget]` immer nur verwenden, wenn es [Widget] bereits gibt (z.B. `SearchBar`)
sneeex
commented
Ja fair aber dann Guck vielleicht mal was es gibt und was nicht xD https://api.flutter.dev/flutter/material/RadioListTile-class.html Ja fair aber dann Guck vielleicht mal was es gibt und was nicht xD https://api.flutter.dev/flutter/material/RadioListTile-class.html
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.value,
|
||||
required this.onContainerTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onContainerTap(value),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Radio<T>(
|
||||
value: value,
|
||||
activeColor: CustomTheme.primaryColor,
|
||||
toggleable: true,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,142 +6,132 @@ import 'package:intl/intl.dart';
|
||||
|
||||
class GameHistoryTile extends StatefulWidget {
|
||||
final Game game;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const GameHistoryTile({
|
||||
super.key,
|
||||
required this.game,
|
||||
});
|
||||
const GameHistoryTile({super.key, required this.game, required this.onTap});
|
||||
|
||||
@override
|
||||
State<GameHistoryTile> createState() => _GameHistoryTileState();
|
||||
}
|
||||
|
||||
class _GameHistoryTileState extends State<GameHistoryTile> {
|
||||
|
||||
@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) ...[
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: 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: [
|
||||
const Icon(
|
||||
Icons.group,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
group.name,
|
||||
widget.game.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(widget.game.createdAt),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
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(
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (group != null) ...[
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.emoji_events,
|
||||
size: 20,
|
||||
color: Colors.amber,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.group, size: 16, color: Colors.grey),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Winner: ${winner.name}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
group.name,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.grey),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
|
||||
if (allPlayers.isNotEmpty) ...[
|
||||
const Text(
|
||||
'Players',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.w500,
|
||||
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: 6),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: allPlayers.map((player) {
|
||||
return TextIconTile(
|
||||
text: player.name,
|
||||
iconEnabled: false,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
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(),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -187,5 +177,4 @@ class _GameHistoryTileState extends State<GameHistoryTile> {
|
||||
|
||||
return allPlayers;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Ich finde wir sollten für solche generischen Statusmeldungen nicht komplette Sätze mit Punkt machen oder?
Finde
No games created yetpasst da besser rein als die Version mit Punkt am Ende