Merge remote-tracking branch 'origin/development' into feature/118-bearbeiten-und-löschen-von-gruppen
# Conflicts: # lib/presentation/views/main_menu/group_view/create_group_view.dart
This commit is contained in:
@@ -38,7 +38,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
),
|
||||
KeyedSubtree(
|
||||
key: ValueKey('groups_$tabKeyCount'),
|
||||
child: const GroupsView(),
|
||||
child: const GroupView(),
|
||||
),
|
||||
KeyedSubtree(
|
||||
key: ValueKey('stats_$tabKeyCount'),
|
||||
|
||||
@@ -43,6 +43,7 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
/// Total matches played in this group
|
||||
int totalMatches = 0;
|
||||
|
||||
/// The best player in this group
|
||||
String bestPlayer = '';
|
||||
|
||||
@override
|
||||
|
||||
@@ -14,15 +14,15 @@ import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
|
||||
class GroupsView extends StatefulWidget {
|
||||
class GroupView extends StatefulWidget {
|
||||
/// A view that displays a list of groups
|
||||
const GroupsView({super.key});
|
||||
const GroupView({super.key});
|
||||
|
||||
@override
|
||||
State<GroupsView> createState() => _GroupsViewState();
|
||||
State<GroupView> createState() => _GroupViewState();
|
||||
}
|
||||
|
||||
class _GroupsViewState extends State<GroupsView> {
|
||||
class _GroupViewState extends State<GroupView> {
|
||||
late final AppDatabase db;
|
||||
|
||||
/// Loaded groups from the database
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
|
||||
@@ -21,11 +21,22 @@ import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
||||
class CreateMatchView extends StatefulWidget {
|
||||
/// A view that allows creating a new match
|
||||
/// [onWinnerChanged]: Optional callback invoked when the winner is changed
|
||||
const CreateMatchView({super.key, this.onWinnerChanged});
|
||||
const CreateMatchView({
|
||||
super.key,
|
||||
this.onWinnerChanged,
|
||||
this.matchToEdit,
|
||||
this.onMatchUpdated,
|
||||
});
|
||||
|
||||
/// Optional callback invoked when the winner is changed
|
||||
final VoidCallback? onWinnerChanged;
|
||||
|
||||
/// Optional callback invoked when the match is updated
|
||||
final void Function(Match)? onMatchUpdated;
|
||||
|
||||
/// An optional match to prefill the fields
|
||||
final Match? matchToEdit;
|
||||
|
||||
@override
|
||||
State<CreateMatchView> createState() => _CreateMatchViewState();
|
||||
}
|
||||
@@ -45,19 +56,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// List of all players from the database
|
||||
List<Player> playerList = [];
|
||||
|
||||
/// List of players filtered based on the selected group
|
||||
/// If a group is selected, this list contains all players from [playerList]
|
||||
/// who are not members of the selected group. If no group is selected,
|
||||
/// this list is identical to [playerList].
|
||||
List<Player> filteredPlayerList = [];
|
||||
|
||||
/// The currently selected group
|
||||
Group? selectedGroup;
|
||||
|
||||
/// The index of the currently selected group in [groupsList] to mark it in
|
||||
/// the [ChooseGroupView]
|
||||
String selectedGroupId = '';
|
||||
|
||||
/// The index of the currently selected game in [games] to mark it in
|
||||
/// the [ChooseGameView]
|
||||
int selectedGameIndex = -1;
|
||||
@@ -83,9 +84,11 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
]).then((result) async {
|
||||
groupsList = result[0] as List<Group>;
|
||||
playerList = result[1] as List<Player>;
|
||||
setState(() {
|
||||
filteredPlayerList = List.from(playerList);
|
||||
});
|
||||
|
||||
// If a match is provided, prefill the fields
|
||||
if (widget.matchToEdit != null) {
|
||||
prefillMatchDetails();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,12 +113,19 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final buttonText = widget.matchToEdit != null
|
||||
? loc.save_changes
|
||||
: loc.create_match;
|
||||
final viewTitle = widget.matchToEdit != null
|
||||
? loc.edit_match
|
||||
: loc.create_new_match;
|
||||
|
||||
return ScaffoldMessenger(
|
||||
key: _scaffoldMessengerKey,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(title: Text(loc.create_new_match)),
|
||||
appBar: AppBar(title: Text(viewTitle)),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@@ -157,96 +167,55 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
? loc.none_group
|
||||
: selectedGroup!.name,
|
||||
onPressed: () async {
|
||||
// Remove all players from the previously selected group from
|
||||
// the selected players list, in case the user deselects the
|
||||
// group or selects a different group.
|
||||
selectedPlayers.removeWhere(
|
||||
(player) =>
|
||||
selectedGroup?.members.any(
|
||||
(member) => member.id == player.id,
|
||||
) ??
|
||||
false,
|
||||
);
|
||||
|
||||
selectedGroup = await Navigator.of(context).push(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseGroupView(
|
||||
groups: groupsList,
|
||||
initialGroupId: selectedGroupId,
|
||||
initialGroupId: selectedGroup?.id ?? '',
|
||||
),
|
||||
),
|
||||
);
|
||||
selectedGroupId = selectedGroup?.id ?? '';
|
||||
if (selectedGroup != null) {
|
||||
filteredPlayerList = playerList
|
||||
.where(
|
||||
(p) =>
|
||||
!selectedGroup!.members.any((m) => m.id == p.id),
|
||||
)
|
||||
.toList();
|
||||
} else {
|
||||
filteredPlayerList = List.from(playerList);
|
||||
}
|
||||
setState(() {});
|
||||
|
||||
setState(() {
|
||||
if (selectedGroup != null) {
|
||||
setState(() {
|
||||
selectedPlayers += [...selectedGroup!.members];
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: PlayerSelection(
|
||||
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||
initialSelectedPlayers: selectedPlayers,
|
||||
availablePlayers: filteredPlayerList,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedPlayers = value;
|
||||
removeGroupWhenNoMemberLeft();
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
CustomWidthButton(
|
||||
text: loc.create_match,
|
||||
text: buttonText,
|
||||
sizeRelativeToWidth: 0.95,
|
||||
buttonType: ButtonType.primary,
|
||||
onPressed: _enableCreateGameButton()
|
||||
? () async {
|
||||
// Use a game from the games list
|
||||
Game? gameToUse;
|
||||
if (selectedGameIndex == -1) {
|
||||
// Use the first game as default if none selected
|
||||
final selectedGame = games[0];
|
||||
gameToUse = Game(
|
||||
name: selectedGame.$1,
|
||||
description: selectedGame.$2,
|
||||
ruleset: selectedGame.$3,
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
);
|
||||
} else {
|
||||
// Use the selected game from the list
|
||||
final selectedGame = games[selectedGameIndex];
|
||||
gameToUse = Game(
|
||||
name: selectedGame.$1,
|
||||
description: selectedGame.$2,
|
||||
ruleset: selectedGame.$3,
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
);
|
||||
}
|
||||
// Add the game to the database if it doesn't exist
|
||||
await db.gameDao.addGame(game: gameToUse);
|
||||
|
||||
Match match = Match(
|
||||
name: _matchNameController.text.isEmpty
|
||||
? (hintText ?? '')
|
||||
: _matchNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
game: gameToUse,
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
notes: '',
|
||||
);
|
||||
await db.matchDao.addMatch(match: match);
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
match: match,
|
||||
onWinnerChanged: widget.onWinnerChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
? () {
|
||||
buttonNavigation(context);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
@@ -263,6 +232,155 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// - Either a group is selected OR at least 2 players are selected
|
||||
bool _enableCreateGameButton() {
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers.length > 1));
|
||||
(selectedPlayers.length > 1) && selectedGameIndex != -1);
|
||||
}
|
||||
}
|
||||
|
||||
// If a match was provided to the view, it updates the match in the database
|
||||
// and navigates back to the previous screen.
|
||||
// If no match was provided, it creates a new match in the database and
|
||||
// navigates to the MatchResultView for the newly created match.
|
||||
void buttonNavigation(BuildContext context) async {
|
||||
if (widget.matchToEdit != null) {
|
||||
await updateMatch();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
} else {
|
||||
final match = await createMatch();
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
match: match,
|
||||
onWinnerChanged: widget.onWinnerChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates attributes of the existing match in the database based on the
|
||||
/// changes made in the edit view.
|
||||
Future<void> updateMatch() async {
|
||||
//TODO: Remove when Games implemented
|
||||
final tempGame = await getTemporaryGame();
|
||||
|
||||
final updatedMatch = Match(
|
||||
id: widget.matchToEdit!.id,
|
||||
name: _matchNameController.text.isEmpty
|
||||
? (hintText ?? '')
|
||||
: _matchNameController.text.trim(),
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
game: tempGame,
|
||||
winner: widget.matchToEdit!.winner,
|
||||
createdAt: widget.matchToEdit!.createdAt,
|
||||
endedAt: widget.matchToEdit!.endedAt,
|
||||
notes: widget.matchToEdit!.notes,
|
||||
);
|
||||
|
||||
if (widget.matchToEdit!.name != updatedMatch.name) {
|
||||
await db.matchDao.updateMatchName(
|
||||
matchId: widget.matchToEdit!.id,
|
||||
newName: updatedMatch.name,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.matchToEdit!.group?.id != updatedMatch.group?.id) {
|
||||
await db.matchDao.updateMatchGroup(
|
||||
matchId: widget.matchToEdit!.id,
|
||||
newGroupId: updatedMatch.group?.id,
|
||||
);
|
||||
}
|
||||
|
||||
// Add players who are in updatedMatch but not in the original match
|
||||
for (var player in updatedMatch.players) {
|
||||
if (!widget.matchToEdit!.players.any((p) => p.id == player.id)) {
|
||||
await db.playerMatchDao.addPlayerToMatch(
|
||||
matchId: widget.matchToEdit!.id,
|
||||
playerId: player.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove players who are in the original match but not in updatedMatch
|
||||
for (var player in widget.matchToEdit!.players) {
|
||||
if (!updatedMatch.players.any((p) => p.id == player.id)) {
|
||||
await db.playerMatchDao.removePlayerFromMatch(
|
||||
matchId: widget.matchToEdit!.id,
|
||||
playerId: player.id,
|
||||
);
|
||||
if (widget.matchToEdit!.winner?.id == player.id) {
|
||||
updatedMatch.winner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
widget.onMatchUpdated?.call(updatedMatch);
|
||||
}
|
||||
|
||||
// Creates a new match and adds it to the database.
|
||||
// Returns the created match.
|
||||
Future<Match> createMatch() async {
|
||||
final tempGame = await getTemporaryGame();
|
||||
|
||||
Match match = Match(
|
||||
name: _matchNameController.text.isEmpty
|
||||
? (hintText ?? '')
|
||||
: _matchNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
game: tempGame,
|
||||
);
|
||||
await db.matchDao.addMatch(match: match);
|
||||
return match;
|
||||
}
|
||||
|
||||
// TODO: Remove when games fully implemented
|
||||
Future<Game> getTemporaryGame() async {
|
||||
Game? game;
|
||||
|
||||
final selectedGame = games[selectedGameIndex];
|
||||
game = Game(
|
||||
name: selectedGame.$1,
|
||||
description: selectedGame.$2,
|
||||
ruleset: selectedGame.$3,
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
);
|
||||
|
||||
await db.gameDao.addGame(game: game);
|
||||
return game;
|
||||
}
|
||||
|
||||
// If a match was provided to the view, this method prefills the input fields
|
||||
void prefillMatchDetails() {
|
||||
final match = widget.matchToEdit!;
|
||||
_matchNameController.text = match.name;
|
||||
selectedPlayers = match.players;
|
||||
|
||||
if (match.group != null) {
|
||||
selectedGroup = match.group;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the selected players are from the currently selected group,
|
||||
// the group is also deselected.
|
||||
Future<void> removeGroupWhenNoMemberLeft() async {
|
||||
if (selectedGroup == null) return;
|
||||
|
||||
if (!selectedPlayers.any(
|
||||
(player) =>
|
||||
selectedGroup!.members.any((member) => member.id == player.id),
|
||||
)) {
|
||||
setState(() {
|
||||
selectedGroup = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/match.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||
import 'package:tallee/presentation/widgets/custom_alert_dialog.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
|
||||
class MatchDetailView extends StatefulWidget {
|
||||
/// A view that displays the profile of a match
|
||||
/// - [match]: The match to display
|
||||
/// - [onMatchUpdate]: Callback to refresh the match list
|
||||
const MatchDetailView({
|
||||
super.key,
|
||||
required this.match,
|
||||
required this.onMatchUpdate,
|
||||
});
|
||||
|
||||
/// The match to display
|
||||
final Match match;
|
||||
|
||||
/// Callback to refresh the match list
|
||||
final VoidCallback onMatchUpdate;
|
||||
|
||||
@override
|
||||
State<MatchDetailView> createState() => _MatchDetailViewState();
|
||||
}
|
||||
|
||||
class _MatchDetailViewState extends State<MatchDetailView> {
|
||||
late final AppDatabase db;
|
||||
|
||||
late Match match;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
match = widget.match;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text(loc.match_profile),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => CustomAlertDialog(
|
||||
title: '${loc.delete_match}?',
|
||||
content: loc.this_cannot_be_undone,
|
||||
actions: [
|
||||
AnimatedDialogButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(
|
||||
loc.cancel,
|
||||
style: const TextStyle(color: CustomTheme.textColor),
|
||||
),
|
||||
),
|
||||
AnimatedDialogButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(
|
||||
loc.delete,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.secondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((confirmed) async {
|
||||
if (confirmed! && context.mounted) {
|
||||
await db.matchDao.deleteMatch(matchId: match.id);
|
||||
if (!context.mounted) return;
|
||||
Navigator.pop(context);
|
||||
widget.onMatchUpdate.call();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ListView(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 20,
|
||||
bottom: 100,
|
||||
),
|
||||
children: [
|
||||
const Center(
|
||||
child: ColoredIconContainer(
|
||||
icon: Icons.sports_esports,
|
||||
containerSize: 55,
|
||||
iconSize: 38,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
match.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
'${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(match.createdAt)}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (match.group != null) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.group),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${match.group!.name}${getExtraPlayerCount(match)}',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
InfoTile(
|
||||
title: loc.players,
|
||||
icon: Icons.people,
|
||||
horizontalAlignment: CrossAxisAlignment.start,
|
||||
content: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.start,
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: match.players.map((player) {
|
||||
return TextIconTile(
|
||||
text: player.name,
|
||||
iconEnabled: false,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
InfoTile(
|
||||
title: loc.results,
|
||||
icon: Icons.emoji_events,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
/// TODO: Implement different ruleset results display
|
||||
if (match.winner != null) ...[
|
||||
Text(
|
||||
loc.winner,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
match.winner!.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: CustomTheme.primaryColor,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
Text(
|
||||
loc.no_results_entered_yet,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: CustomTheme.textColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: MediaQuery.paddingOf(context).bottom,
|
||||
child: Row(
|
||||
children: [
|
||||
MainMenuButton(
|
||||
icon: Icons.edit,
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => CreateMatchView(
|
||||
matchToEdit: match,
|
||||
onMatchUpdated: onMatchUpdated,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
MainMenuButton(
|
||||
text: loc.enter_results,
|
||||
icon: Icons.emoji_events,
|
||||
onPressed: () async {
|
||||
match.winner = await Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
match: match,
|
||||
onWinnerChanged: () {
|
||||
widget.onMatchUpdate.call();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Callback for when the match is updated in the edit view,
|
||||
/// updates the match in this view
|
||||
void onMatchUpdated(Match editedMatch) {
|
||||
setState(() {
|
||||
match = editedMatch;
|
||||
});
|
||||
widget.onMatchUpdate.call();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,10 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
@override
|
||||
void initState() {
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
allPlayers = getAllPlayers(widget.match);
|
||||
|
||||
allPlayers = widget.match.players;
|
||||
allPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
if (widget.match.winner != null) {
|
||||
_selectedPlayer = allPlayers.firstWhere(
|
||||
(p) => p.id == widget.match.winner!.id,
|
||||
@@ -54,7 +57,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
widget.onWinnerChanged?.call();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(_selectedPlayer);
|
||||
},
|
||||
),
|
||||
title: Text(widget.match.name),
|
||||
@@ -145,21 +148,4 @@ class _MatchResultViewState extends State<MatchResultView> {
|
||||
}
|
||||
widget.onWinnerChanged?.call();
|
||||
}
|
||||
|
||||
/// Retrieves all players associated with the given [match].
|
||||
/// This includes players directly assigned to the match
|
||||
/// as well as members of the group (if any).
|
||||
/// The returned list is sorted alphabetically by player name.
|
||||
List<Player> getAllPlayers(Match match) {
|
||||
List<Player> players = [];
|
||||
|
||||
if (match.group == null) {
|
||||
players = [...match.players];
|
||||
} else {
|
||||
players = [...match.players, ...match.group!.members];
|
||||
}
|
||||
|
||||
players.sort((a, b) => a.name.compareTo(b.name));
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:core' hide Match;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttericon/rpg_awesome_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -14,7 +12,7 @@ import 'package:tallee/data/dto/match.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_detail_view.dart';
|
||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_tile.dart';
|
||||
@@ -38,7 +36,13 @@ class _MatchViewState extends State<MatchView> {
|
||||
4,
|
||||
Match(
|
||||
name: 'Skeleton match name',
|
||||
game: Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''),
|
||||
game: Game(
|
||||
name: '',
|
||||
ruleset: Ruleset.singleWinner,
|
||||
description: '',
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
),
|
||||
group: Group(
|
||||
name: 'Group name',
|
||||
description: '',
|
||||
@@ -54,7 +58,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
loadGames();
|
||||
loadMatches();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,10 +98,9 @@ class _MatchViewState extends State<MatchView> {
|
||||
Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => MatchResultView(
|
||||
builder: (context) => MatchDetailView(
|
||||
match: matches[index],
|
||||
onWinnerChanged: loadGames,
|
||||
onMatchUpdate: loadMatches,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -120,7 +123,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) =>
|
||||
CreateMatchView(onWinnerChanged: loadGames),
|
||||
CreateMatchView(onWinnerChanged: loadMatches),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -131,8 +134,9 @@ class _MatchViewState extends State<MatchView> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads the games from the database and sorts them by creation date.
|
||||
void loadGames() {
|
||||
/// Loads the matches from the database and sorts them by creation date.
|
||||
void loadMatches() {
|
||||
isLoading = true;
|
||||
Future.wait([
|
||||
db.matchDao.getAllMatches(),
|
||||
Future.delayed(Constants.MINIMUM_SKELETON_DURATION),
|
||||
@@ -140,7 +144,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
final loadedMatches = results[0] as List<Match>;
|
||||
matches = loadedMatches
|
||||
matches = [...loadedMatches]
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user