Merge branch 'development' into enhancement/70-konsistenzfehler-im-json-vermeiden
This commit is contained in:
@@ -6,8 +6,8 @@ import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.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/tiles/game_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_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:provider/provider.dart';
|
||||
|
||||
@@ -112,13 +112,13 @@ class _HomeViewState extends State<HomeView> {
|
||||
visible: !isLoading && loadedRecentMatches.isNotEmpty,
|
||||
replacement: const Center(
|
||||
heightFactor: 12,
|
||||
child: Text('No recent games available'),
|
||||
child: Text('No recent matches available'),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
MatchTile(
|
||||
MatchSummaryTile(
|
||||
matchTitle: recentMatches[0].name,
|
||||
game: 'Winner',
|
||||
ruleset: 'Ruleset',
|
||||
@@ -132,20 +132,20 @@ class _HomeViewState extends State<HomeView> {
|
||||
child: Divider(),
|
||||
),
|
||||
if (loadedRecentMatches.length > 1) ...[
|
||||
MatchTile(
|
||||
MatchSummaryTile(
|
||||
matchTitle: recentMatches[1].name,
|
||||
game: 'Winner',
|
||||
ruleset: 'Ruleset',
|
||||
players: _getPlayerText(recentMatches[1]),
|
||||
winner: recentMatches[1].winner == null
|
||||
? 'Game in progress...'
|
||||
? 'Match in progress...'
|
||||
: recentMatches[1].winner!.name,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
] else ...[
|
||||
const Center(
|
||||
heightFactor: 5.35,
|
||||
child: Text('No second game available'),
|
||||
child: Text('No second match available'),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -31,6 +31,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Controller for the match name input field
|
||||
final TextEditingController _matchNameController = TextEditingController();
|
||||
|
||||
/// Hint text for the match name input field
|
||||
String hintText = 'Match Name';
|
||||
|
||||
/// List of all groups from the database
|
||||
List<Group> groupsList = [];
|
||||
|
||||
@@ -107,8 +110,10 @@ 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -132,7 +137,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
|
||||
child: TextInputField(
|
||||
controller: _matchNameController,
|
||||
hintText: 'Match name',
|
||||
hintText: hintText,
|
||||
),
|
||||
),
|
||||
ChooseTile(
|
||||
@@ -151,11 +156,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
);
|
||||
setState(() {
|
||||
if (selectedGameIndex != -1) {
|
||||
hintText = games[selectedGameIndex].$1;
|
||||
selectedRuleset = games[selectedGameIndex].$3;
|
||||
selectedRulesetIndex = rulesets.indexWhere(
|
||||
(r) => r.$1 == selectedRuleset,
|
||||
);
|
||||
} else {
|
||||
hintText = 'Match Name';
|
||||
selectedRuleset = null;
|
||||
}
|
||||
});
|
||||
@@ -228,7 +235,9 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
onPressed: _enableCreateGameButton()
|
||||
? () async {
|
||||
Match match = Match(
|
||||
name: _matchNameController.text.trim(),
|
||||
name: _matchNameController.text.isEmpty
|
||||
? hintText
|
||||
: _matchNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
@@ -258,9 +267,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// Determines whether the "Create Game" button should be enabled based on
|
||||
/// the current state of the input fields.
|
||||
bool _enableCreateGameButton() {
|
||||
return _matchNameController.text.isNotEmpty &&
|
||||
(selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1)) &&
|
||||
return selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1) &&
|
||||
selectedRuleset != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:game_tracker/presentation/views/main_menu/match_view/create_matc
|
||||
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/buttons/custom_width_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -30,9 +30,9 @@ class _MatchViewState extends State<MatchView> {
|
||||
List<Match> matches = List.filled(
|
||||
4,
|
||||
Match(
|
||||
name: 'Skeleton Gamename',
|
||||
name: 'Skeleton match name',
|
||||
group: Group(
|
||||
name: 'Groupname',
|
||||
name: 'Group name',
|
||||
members: List.filled(5, Player(name: 'Player')),
|
||||
),
|
||||
winner: Player(name: 'Player'),
|
||||
@@ -71,10 +71,10 @@ class _MatchViewState extends State<MatchView> {
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (index == matches.length) {
|
||||
return SizedBox(
|
||||
height: MediaQuery.paddingOf(context).bottom - 80,
|
||||
height: MediaQuery.paddingOf(context).bottom - 20,
|
||||
);
|
||||
}
|
||||
return GameHistoryTile(
|
||||
return MatchTile(
|
||||
onTap: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -96,7 +96,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
Positioned(
|
||||
bottom: MediaQuery.paddingOf(context).bottom,
|
||||
child: CustomWidthButton(
|
||||
text: 'Create Game',
|
||||
text: 'Create Match',
|
||||
sizeRelativeToWidth: 0.90,
|
||||
onPressed: () async {
|
||||
Navigator.push(
|
||||
|
||||
@@ -12,13 +12,13 @@ import 'package:provider/provider.dart';
|
||||
|
||||
class PlayerSelection extends StatefulWidget {
|
||||
final Function(List<Player> value) onChanged;
|
||||
final List<Player> availablePlayers;
|
||||
final List<Player>? availablePlayers;
|
||||
final List<Player>? initialSelectedPlayers;
|
||||
|
||||
const PlayerSelection({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.availablePlayers = const [],
|
||||
this.availablePlayers,
|
||||
this.initialSelectedPlayers,
|
||||
});
|
||||
|
||||
@@ -56,17 +56,17 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
if (mounted) {
|
||||
_allPlayersFuture.then((loadedPlayers) {
|
||||
setState(() {
|
||||
// If a list of available players is provided, use that list.
|
||||
if (widget.availablePlayers.isNotEmpty) {
|
||||
widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...widget.availablePlayers];
|
||||
// If a list of available players is provided (even if empty), use that list.
|
||||
if (widget.availablePlayers != null) {
|
||||
widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name));
|
||||
allPlayers = [...widget.availablePlayers!];
|
||||
suggestedPlayers = [...allPlayers];
|
||||
|
||||
if (widget.initialSelectedPlayers != null) {
|
||||
// Ensures that only players available for selection are pre-selected.
|
||||
selectedPlayers = widget.initialSelectedPlayers!
|
||||
.where(
|
||||
(p) => widget.availablePlayers.any(
|
||||
(p) => widget.availablePlayers!.any(
|
||||
(available) => available.id == p.id,
|
||||
),
|
||||
)
|
||||
@@ -149,18 +149,24 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
onIconTap: () {
|
||||
setState(() {
|
||||
// Removes the player from the selection and notifies the parent.
|
||||
selectedPlayers.remove(player);
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
|
||||
// Get the current search query
|
||||
final currentSearch = _searchBarController
|
||||
.text
|
||||
.toLowerCase();
|
||||
selectedPlayers.remove(player);
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
|
||||
// If the player matches the current search query (or search is empty),
|
||||
// they are added back to the suggestions and the list is re-sorted.
|
||||
// they are added back to the `suggestedPlayers` and the list is re-sorted.
|
||||
if (currentSearch.isEmpty ||
|
||||
player.name.toLowerCase().contains(
|
||||
currentSearch,
|
||||
)) {
|
||||
suggestedPlayers.add(player);
|
||||
suggestedPlayers.sort(
|
||||
(a, b) => a.name.compareTo(b.name),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -176,9 +182,6 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
/*
|
||||
|
||||
*/
|
||||
Expanded(
|
||||
child: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
@@ -187,11 +190,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
replacement: TopCenteredMessage(
|
||||
icon: Icons.info,
|
||||
title: 'Info',
|
||||
message: allPlayers.isEmpty
|
||||
? 'No players created yet'
|
||||
: (selectedPlayers.length == allPlayers.length)
|
||||
? 'No more players to add'
|
||||
: 'No players found with that name',
|
||||
message: _getInfoText(),
|
||||
),
|
||||
child: ListView.builder(
|
||||
itemCount: suggestedPlayers.length,
|
||||
@@ -200,11 +199,15 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
text: suggestedPlayers[index].name,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
// If the player is not already selected
|
||||
if (!selectedPlayers.contains(
|
||||
suggestedPlayers[index],
|
||||
)) {
|
||||
selectedPlayers.add(suggestedPlayers[index]);
|
||||
// Add to player to the front of the selectedPlayers
|
||||
selectedPlayers.insert(0, suggestedPlayers[index]);
|
||||
// Notify the parent widget of the change
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
// Remove the player from the suggestedPlayers
|
||||
suggestedPlayers.remove(suggestedPlayers[index]);
|
||||
}
|
||||
});
|
||||
@@ -229,7 +232,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
bool success = await db.playerDao.addPlayer(player: createdPlayer);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
selectedPlayers.add(createdPlayer);
|
||||
selectedPlayers.insert(0, createdPlayer);
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
allPlayers.add(createdPlayer);
|
||||
setState(() {
|
||||
@@ -263,4 +266,21 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the appropriate info text to display when no players
|
||||
/// are available in the suggested players list.
|
||||
String _getInfoText() {
|
||||
if (allPlayers.isEmpty) {
|
||||
// No players exist in the database
|
||||
return 'No players created yet';
|
||||
} else if (selectedPlayers.length == allPlayers.length ||
|
||||
widget.availablePlayers?.isEmpty == true) {
|
||||
// All players have been selected or
|
||||
// available players list is provided but empty
|
||||
return 'No more players to add';
|
||||
} else {
|
||||
// No players match the search query
|
||||
return 'No players found with that name';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class MatchTile extends StatefulWidget {
|
||||
class MatchSummaryTile extends StatefulWidget {
|
||||
final String matchTitle;
|
||||
final String game;
|
||||
final String ruleset;
|
||||
final String players;
|
||||
final String winner;
|
||||
|
||||
const MatchTile({
|
||||
const MatchSummaryTile({
|
||||
super.key,
|
||||
required this.matchTitle,
|
||||
required this.game,
|
||||
@@ -19,10 +19,10 @@ class MatchTile extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<MatchTile> createState() => _MatchTileState();
|
||||
State<MatchSummaryTile> createState() => _MatchSummaryTileState();
|
||||
}
|
||||
|
||||
class _MatchTileState extends State<MatchTile> {
|
||||
class _MatchSummaryTileState extends State<MatchSummaryTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@@ -4,17 +4,17 @@ import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class GameHistoryTile extends StatefulWidget {
|
||||
class MatchTile extends StatefulWidget {
|
||||
final Match match;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const GameHistoryTile({super.key, required this.match, required this.onTap});
|
||||
const MatchTile({super.key, required this.match, required this.onTap});
|
||||
|
||||
@override
|
||||
State<GameHistoryTile> createState() => _GameHistoryTileState();
|
||||
State<MatchTile> createState() => _MatchTileState();
|
||||
}
|
||||
|
||||
class _GameHistoryTileState extends State<GameHistoryTile> {
|
||||
class _MatchTileState extends State<MatchTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final group = widget.match.group;
|
||||
Reference in New Issue
Block a user