WIP: StatisticsView Rework #247
@@ -80,12 +80,13 @@ class StatisticDao extends DatabaseAccessor<AppDatabase>
|
||||
result.map((row) async {
|
||||
final groups = await db.statisticGroupDao.getGroupsForStatistic(row.id);
|
||||
final games = await db.statisticGameDao.getGamesForStatistic(row.id);
|
||||
final scopes = await db.statisticScopeDao.getScopeForStatistic(row.id);
|
||||
|
||||
return Statistic(
|
||||
type: StatisticType.values.firstWhere(
|
||||
(type) => type.name == row.type,
|
||||
),
|
||||
scopes: [],
|
||||
scopes: scopes,
|
||||
timeframe: Timeframe.values.firstWhereOrNull(
|
||||
(t) => t.name == row.timeframe,
|
||||
),
|
||||
|
||||
@@ -12,12 +12,13 @@ class StatisticGameDao extends DatabaseAccessor<AppDatabase>
|
||||
StatisticGameDao(super.db);
|
||||
|
||||
/// Retrieves a list of games associated with a specific statistic.
|
||||
Future<List<Game>> getGamesForStatistic(String statisticId) async {
|
||||
Future<List<Game>?> getGamesForStatistic(String statisticId) async {
|
||||
final query = select(statisticGameTable).join([
|
||||
innerJoin(gameTable, gameTable.id.equalsExp(statisticGameTable.gameId)),
|
||||
])..where(statisticGameTable.statisticId.equals(statisticId));
|
||||
|
||||
final results = await query.map((row) => row.readTable(gameTable)).get();
|
||||
if (results.isEmpty) return null;
|
||||
return results
|
||||
.map(
|
||||
(row) => Game(
|
||||
|
||||
@@ -12,7 +12,7 @@ class StatisticGroupDao extends DatabaseAccessor<AppDatabase>
|
||||
StatisticGroupDao(super.db);
|
||||
|
||||
/// Retrieves a list of groups associated with a specific statistic.
|
||||
Future<List<Group>> getGroupsForStatistic(String statisticId) async {
|
||||
Future<List<Group>?> getGroupsForStatistic(String statisticId) async {
|
||||
final query = select(statisticGroupTable).join([
|
||||
innerJoin(
|
||||
groupTable,
|
||||
@@ -21,6 +21,7 @@ class StatisticGroupDao extends DatabaseAccessor<AppDatabase>
|
||||
])..where(statisticGroupTable.statisticId.equals(statisticId));
|
||||
|
||||
final results = await query.map((row) => row.readTable(groupTable)).get();
|
||||
if (results.isEmpty) return null;
|
||||
final groups = await Future.wait(
|
||||
results.map((result) async {
|
||||
final groupMembers = await db.playerGroupDao.getPlayersOfGroup(
|
||||
|
||||
@@ -15,8 +15,8 @@ class StatisticScopeDao extends DatabaseAccessor<AppDatabase>
|
||||
final query = select(statisticScopeTable)
|
||||
..where((tbl) => tbl.statisticId.equals(statisticId));
|
||||
|
||||
final results = await query.get();
|
||||
return results
|
||||
final result = await query.get();
|
||||
return result
|
||||
.map(
|
||||
(row) => StatisticScope.values.firstWhere(
|
||||
(e) => e.name == row.scope,
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
"color_yellow": "Gelb",
|
||||
"confirm": "Bestätigen",
|
||||
"could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden",
|
||||
"@could_not_add_player": {
|
||||
"placeholders": {
|
||||
"playerName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create_game": "Spielvorlage erstellen",
|
||||
"create_group": "Gruppe erstellen",
|
||||
"create_match": "Spiel erstellen",
|
||||
|
||||
@@ -20,23 +20,29 @@
|
||||
"color_teal": "Teal",
|
||||
"color_yellow": "Yellow",
|
||||
"confirm": "Confirm",
|
||||
"could_not_add_player": "Could not add player",
|
||||
"could_not_add_player": "Could not add player {playerName}",
|
||||
"@could_not_add_player": {
|
||||
"placeholders": {
|
||||
"playerName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"create_game": "Create Game",
|
||||
"create_group": "Create Group",
|
||||
"create_match": "Create match",
|
||||
"create_new_group": "Create new group",
|
||||
"create_new_match": "Create new match",
|
||||
"create_statistic": "Create statistic",
|
||||
"create_statistic_classifier_subtitle": "Select which key metric you want to display",
|
||||
"create_statistic_classifier_title": "Classifier",
|
||||
"create_statistic_games_subtitle": "Select the filtered games",
|
||||
"create_statistic_games_title": "Games",
|
||||
"create_statistic_groups_subtitle": "Select the filtered groups",
|
||||
"create_statistic_groups_title": "Groups",
|
||||
"create_statistic_scope_subtitle": "Select the main filter for your statistic. This will determine which data is used to calculate the selected classifier.",
|
||||
"create_statistic_scope_title": "Scope",
|
||||
"create_statistic_timeframe_subtitle": "Select a timeframe for which the data will be filtered. Only matches that ended within the selected timeframe will be included in the statistic.",
|
||||
"create_statistic_timeframe_title": "Timeframe",
|
||||
"which_key_metric": "Select which key metric you want to display",
|
||||
"classifier": "Classifier",
|
||||
"select_the_filtered_games": "Select the filtered games",
|
||||
"games": "Games",
|
||||
"select_the_filtered_groups": "Select the filtered groups",
|
||||
"select_main_filter": "Select the main filter for your statistic. This will determine which data is used to calculate the selected classifier.",
|
||||
"scope": "Scope",
|
||||
"select_a_timeframe_for_which_data_will_be_filtered": "Select a timeframe for which the data will be filtered. Only matches that ended within the selected timeframe will be included in the statistic.",
|
||||
"timeframe": "Timeframe",
|
||||
"created_on": "Created on",
|
||||
"data": "Data",
|
||||
"data_successfully_deleted": "Data successfully deleted",
|
||||
@@ -54,6 +60,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": "Filter",
|
||||
"delete_group": "Delete Group",
|
||||
"delete_match": "Delete Match",
|
||||
"delete_player": "Delete player?",
|
||||
@@ -120,6 +127,7 @@
|
||||
"no_results_entered_yet": "No results entered yet",
|
||||
"no_second_match_available": "No second match available",
|
||||
"no_statistics_available": "No statistics available",
|
||||
"no_statistics_created_yet": "No statistics created yet",
|
||||
"none": "None",
|
||||
"none_group": "None",
|
||||
"not_available": "Not available",
|
||||
|
||||
@@ -221,8 +221,8 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @could_not_add_player.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not add player'**
|
||||
String could_not_add_player(Object playerName);
|
||||
/// **'Could not add player {playerName}'**
|
||||
String could_not_add_player(String playerName);
|
||||
|
||||
/// No description provided for @create_game.
|
||||
///
|
||||
@@ -260,65 +260,59 @@ abstract class AppLocalizations {
|
||||
/// **'Create statistic'**
|
||||
String get create_statistic;
|
||||
|
||||
/// No description provided for @create_statistic_classifier_subtitle.
|
||||
/// No description provided for @which_key_metric.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select which key metric you want to display'**
|
||||
String get create_statistic_classifier_subtitle;
|
||||
String get which_key_metric;
|
||||
|
||||
/// No description provided for @create_statistic_classifier_title.
|
||||
/// No description provided for @classifier.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Classifier'**
|
||||
String get create_statistic_classifier_title;
|
||||
String get classifier;
|
||||
|
||||
/// No description provided for @create_statistic_games_subtitle.
|
||||
/// No description provided for @select_the_filtered_games.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select the filtered games'**
|
||||
String get create_statistic_games_subtitle;
|
||||
String get select_the_filtered_games;
|
||||
|
||||
/// No description provided for @create_statistic_games_title.
|
||||
/// No description provided for @games.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Games'**
|
||||
String get create_statistic_games_title;
|
||||
String get games;
|
||||
|
||||
/// No description provided for @create_statistic_groups_subtitle.
|
||||
/// No description provided for @select_the_filtered_groups.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select the filtered groups'**
|
||||
String get create_statistic_groups_subtitle;
|
||||
String get select_the_filtered_groups;
|
||||
|
||||
/// No description provided for @create_statistic_groups_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Groups'**
|
||||
String get create_statistic_groups_title;
|
||||
|
||||
/// No description provided for @create_statistic_scope_subtitle.
|
||||
/// No description provided for @select_main_filter.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select the main filter for your statistic. This will determine which data is used to calculate the selected classifier.'**
|
||||
String get create_statistic_scope_subtitle;
|
||||
String get select_main_filter;
|
||||
|
||||
/// No description provided for @create_statistic_scope_title.
|
||||
/// No description provided for @scope.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Scope'**
|
||||
String get create_statistic_scope_title;
|
||||
String get scope;
|
||||
|
||||
/// No description provided for @create_statistic_timeframe_subtitle.
|
||||
/// No description provided for @select_a_timeframe_for_which_data_will_be_filtered.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select a timeframe for which the data will be filtered. Only matches that ended within the selected timeframe will be included in the statistic.'**
|
||||
String get create_statistic_timeframe_subtitle;
|
||||
String get select_a_timeframe_for_which_data_will_be_filtered;
|
||||
|
||||
/// No description provided for @create_statistic_timeframe_title.
|
||||
/// No description provided for @timeframe.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Timeframe'**
|
||||
String get create_statistic_timeframe_title;
|
||||
String get timeframe;
|
||||
|
||||
/// No description provided for @created_on.
|
||||
///
|
||||
@@ -380,6 +374,12 @@ abstract class AppLocalizations {
|
||||
/// **'If you delete this game template, {count, plural, =1{1 match} other{{count} matches}} using this game template will also be deleted.'**
|
||||
String delete_game_with_matches_warning(int count);
|
||||
|
||||
/// No description provided for @filter.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Filter'**
|
||||
String get filter;
|
||||
|
||||
/// No description provided for @delete_group.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -776,6 +776,12 @@ abstract class AppLocalizations {
|
||||
/// **'No statistics available'**
|
||||
String get no_statistics_available;
|
||||
|
||||
/// No description provided for @no_statistics_created_yet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No statistics created yet'**
|
||||
String get no_statistics_created_yet;
|
||||
|
||||
/// No description provided for @none.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -69,7 +69,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get confirm => 'Bestätigen';
|
||||
|
||||
@override
|
||||
String could_not_add_player(Object playerName) {
|
||||
String could_not_add_player(String playerName) {
|
||||
return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
|
||||
}
|
||||
|
||||
@@ -92,39 +92,33 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get create_statistic => 'Statistik erstellen';
|
||||
|
||||
@override
|
||||
String get create_statistic_classifier_subtitle =>
|
||||
'Wähle die anzuzeigende Hauptmetrik aus';
|
||||
String get which_key_metric => 'Select which key metric you want to display';
|
||||
|
||||
@override
|
||||
String get create_statistic_classifier_title => 'Klassifikator';
|
||||
String get classifier => 'Classifier';
|
||||
|
||||
@override
|
||||
String get create_statistic_games_subtitle =>
|
||||
'Wähle die gefilterten Spielvorlagen';
|
||||
String get select_the_filtered_games => 'Select the filtered games';
|
||||
|
||||
@override
|
||||
String get create_statistic_games_title => 'Spielvorlagen';
|
||||
String get games => 'Games';
|
||||
|
||||
@override
|
||||
String get create_statistic_groups_subtitle =>
|
||||
'Wähle die gefilterten Gruppen';
|
||||
String get select_the_filtered_groups => 'Select the filtered groups';
|
||||
|
||||
@override
|
||||
String get create_statistic_groups_title => 'Gruppen';
|
||||
String get select_main_filter =>
|
||||
'Select the main filter for your statistic. This will determine which data is used to calculate the selected classifier.';
|
||||
|
||||
@override
|
||||
String get create_statistic_scope_subtitle =>
|
||||
'Wähle den Hauptfilter für deine Statistik. Er bestimmt, welche Daten zur Berechnung des Klassifikators verwendet werden.';
|
||||
String get scope => 'Scope';
|
||||
|
||||
@override
|
||||
String get create_statistic_scope_title => 'Bereich';
|
||||
String get select_a_timeframe_for_which_data_will_be_filtered =>
|
||||
'Select a timeframe for which the data will be filtered. Only matches that ended within the selected timeframe will be included in the statistic.';
|
||||
|
||||
@override
|
||||
String get create_statistic_timeframe_subtitle =>
|
||||
'Wähle einen Zeitraum, nach dem die Daten gefiltert werden. Nur Spiele, die innerhalb des Zeitraums beendet wurden, fließen in die Statistik ein.';
|
||||
|
||||
@override
|
||||
String get create_statistic_timeframe_title => 'Zeitraum';
|
||||
String get timeframe => 'Timeframe';
|
||||
|
||||
@override
|
||||
String get created_on => 'Erstellt am';
|
||||
@@ -166,6 +160,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return 'Wenn du diese Spielvorlage löschst, $_temp0 mit dieser Spielvorlage ebenfalls gelöscht.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get filter => 'Filter';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Gruppe löschen';
|
||||
|
||||
@@ -369,6 +366,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get no_statistics_available => 'Keine Statistiken verfügbar';
|
||||
|
||||
@override
|
||||
String get no_statistics_created_yet => 'No statistics created yet';
|
||||
|
||||
@override
|
||||
String get none => 'Kein';
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get confirm => 'Confirm';
|
||||
|
||||
@override
|
||||
String could_not_add_player(Object playerName) {
|
||||
return 'Could not add player';
|
||||
String could_not_add_player(String playerName) {
|
||||
return 'Could not add player $playerName';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -92,37 +92,33 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get create_statistic => 'Create statistic';
|
||||
|
||||
@override
|
||||
String get create_statistic_classifier_subtitle =>
|
||||
'Select which key metric you want to display';
|
||||
String get which_key_metric => 'Select which key metric you want to display';
|
||||
|
||||
@override
|
||||
String get create_statistic_classifier_title => 'Classifier';
|
||||
String get classifier => 'Classifier';
|
||||
|
||||
@override
|
||||
String get create_statistic_games_subtitle => 'Select the filtered games';
|
||||
String get select_the_filtered_games => 'Select the filtered games';
|
||||
|
||||
@override
|
||||
String get create_statistic_games_title => 'Games';
|
||||
String get games => 'Games';
|
||||
|
||||
@override
|
||||
String get create_statistic_groups_subtitle => 'Select the filtered groups';
|
||||
String get select_the_filtered_groups => 'Select the filtered groups';
|
||||
|
||||
@override
|
||||
String get create_statistic_groups_title => 'Groups';
|
||||
|
||||
@override
|
||||
String get create_statistic_scope_subtitle =>
|
||||
String get select_main_filter =>
|
||||
'Select the main filter for your statistic. This will determine which data is used to calculate the selected classifier.';
|
||||
|
||||
@override
|
||||
String get create_statistic_scope_title => 'Scope';
|
||||
String get scope => 'Scope';
|
||||
|
||||
@override
|
||||
String get create_statistic_timeframe_subtitle =>
|
||||
String get select_a_timeframe_for_which_data_will_be_filtered =>
|
||||
'Select a timeframe for which the data will be filtered. Only matches that ended within the selected timeframe will be included in the statistic.';
|
||||
|
||||
@override
|
||||
String get create_statistic_timeframe_title => 'Timeframe';
|
||||
String get timeframe => 'Timeframe';
|
||||
|
||||
@override
|
||||
String get created_on => 'Created on';
|
||||
@@ -164,6 +160,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'If you delete this game template, $_temp0 using this game template will also be deleted.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get filter => 'Filter';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Delete Group';
|
||||
|
||||
@@ -367,6 +366,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get no_statistics_available => 'No statistics available';
|
||||
|
||||
@override
|
||||
String get no_statistics_created_yet => 'No statistics created yet';
|
||||
|
||||
@override
|
||||
String get none => 'None';
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class _MatchViewState extends State<MatchView> {
|
||||
visible: matches.isNotEmpty,
|
||||
replacement: Center(
|
||||
child: TopCenteredMessage(
|
||||
icon: Icons.report,
|
||||
icon: Icons.info,
|
||||
title: loc.info,
|
||||
message: loc.no_matches_created_yet,
|
||||
),
|
||||
|
||||
@@ -68,7 +68,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.create_statistic_classifier_title,
|
||||
loc.classifier,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -77,7 +77,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
loc.create_statistic_classifier_subtitle,
|
||||
loc.select_a_classifier,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -139,7 +139,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.create_statistic_scope_title,
|
||||
loc.scope,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -148,7 +148,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
loc.create_statistic_scope_subtitle,
|
||||
loc.select_a_scope,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -214,7 +214,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.create_statistic_games_title,
|
||||
loc.games,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -223,7 +223,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
loc.create_statistic_games_subtitle,
|
||||
loc.select_the_filtered_games,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -310,7 +310,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.create_statistic_groups_title,
|
||||
loc.groups,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -319,7 +319,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
loc.create_statistic_groups_subtitle,
|
||||
loc.select_the_filtered_groups,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -396,7 +396,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loc.create_statistic_timeframe_title,
|
||||
loc.timeframe,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
@@ -405,7 +405,7 @@ class _CreateStatisticViewState extends State<CreateStatisticView> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
loc.create_statistic_timeframe_subtitle,
|
||||
loc.select_a_timeframe_for_which_data_will_be_filtered,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.textColor,
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/data/models/player.dart';
|
||||
import 'package:tallee/data/models/statistic.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/statistics_view/create_statistic_view.dart'
|
||||
show
|
||||
translateScopeToString,
|
||||
translateStatisticTypeToString,
|
||||
translateTimeframeToString;
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart';
|
||||
|
||||
class StatisticDetailView extends StatefulWidget {
|
||||
const StatisticDetailView({
|
||||
super.key,
|
||||
required this.statistic,
|
||||
required this.values,
|
||||
required this.icon,
|
||||
required this.barColor,
|
||||
});
|
||||
|
||||
final Statistic statistic;
|
||||
final List<(Player, num)> values;
|
||||
final IconData icon;
|
||||
final Color barColor;
|
||||
|
||||
@override
|
||||
State<StatisticDetailView> createState() => _StatisticDetailViewState();
|
||||
}
|
||||
|
||||
class _StatisticDetailViewState extends State<StatisticDetailView> {
|
||||
int displayCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
displayCount = widget.statistic.displayCount;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
final title = translateStatisticTypeToString(
|
||||
widget.statistic.type,
|
||||
context,
|
||||
);
|
||||
const style = TextStyle(fontWeight: FontWeight.bold);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(title)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
StatisticsTile(
|
||||
icon: widget.icon,
|
||||
title: title,
|
||||
width: MediaQuery.sizeOf(context).width * 0.95,
|
||||
values: widget.values,
|
||||
barColor: widget.barColor,
|
||||
selectedGroups: widget.statistic.selectedGroups,
|
||||
selectedGames: widget.statistic.selectedGames,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
InfoTile(
|
||||
icon: Icons.filter_alt,
|
||||
title: loc.filter,
|
||||
content: Column(
|
||||
spacing: 12,
|
||||
|
||||
children: [
|
||||
// Scopes
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(loc.scope, style: style),
|
||||
Text(
|
||||
widget.statistic.scopes
|
||||
.map(
|
||||
(scope) => translateScopeToString(scope, context),
|
||||
)
|
||||
.join('\n'),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Timeframe
|
||||
if (widget.statistic.timeframe != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(loc.timeframe, style: style),
|
||||
Text(
|
||||
translateTimeframeToString(
|
||||
widget.statistic.timeframe!,
|
||||
context,
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Groups
|
||||
if (widget.statistic.selectedGroups != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(loc.groups, style: style),
|
||||
Text(
|
||||
widget.statistic.selectedGroups!
|
||||
.map((group) => group.name)
|
||||
.join('\n'),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Games
|
||||
if (widget.statistic.selectedGames != null)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(loc.games, style: style),
|
||||
Text(
|
||||
widget.statistic.selectedGames!
|
||||
.map((game) => game.name)
|
||||
.join('\n'),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
if (widget.values.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Display count', style: style),
|
||||
Row(
|
||||
children: [
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: displayCount <= 1
|
||||
? null
|
||||
: () => setState(() => displayCount -= 1),
|
||||
),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: Text(
|
||||
'$displayCount',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: displayCount >= widget.values.length
|
||||
? null
|
||||
: () => setState(() => displayCount += 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
import 'package:tallee/data/models/match.dart';
|
||||
import 'package:tallee/data/models/player.dart';
|
||||
import 'package:tallee/data/models/statistic.dart';
|
||||
@@ -14,13 +16,18 @@ List<Color> _colorPalette = AppColor.values
|
||||
.map((c) => getColorFromAppColor(c))
|
||||
.toList();
|
||||
|
||||
/// Build the [StatisticsTile] for a given [Statistic].
|
||||
Widget buildStatisticTile({
|
||||
/// Returns the icon for the given statistic type.
|
||||
IconData getStatisticIconForType(StatisticType type) =>
|
||||
_getStatisticIcon(type: type);
|
||||
|
||||
/// Returns a color from the palette based on the statistic's ID.
|
||||
Color getStatisticColorForStatistic(Statistic stat) => _getStatisticColor(stat);
|
||||
|
||||
/// Computes the statistic values for a given [Statistic].
|
||||
List<(Player, num)> computeStatisticValues({
|
||||
required Statistic statistic,
|
||||
required List<Match> matches,
|
||||
required List<Player> players,
|
||||
required BuildContext context,
|
||||
double? width,
|
||||
}) {
|
||||
final filteredMatches = _getFilterMatches(statistic, matches);
|
||||
final filteredPlayers = _getFilteredPlayers(
|
||||
@@ -29,16 +36,26 @@ Widget buildStatisticTile({
|
||||
filteredMatches,
|
||||
);
|
||||
|
||||
print('Building tile for statistic: $statistic');
|
||||
print('Filtered matches count: ${filteredMatches.length}');
|
||||
print('Filtered players count: ${filteredPlayers.length}');
|
||||
|
||||
final values = _computeValuesForType(
|
||||
return _computeValuesForType(
|
||||
type: statistic.type,
|
||||
matches: filteredMatches,
|
||||
players: filteredPlayers,
|
||||
);
|
||||
print(values);
|
||||
}
|
||||
|
||||
/// Build the [StatisticsTile] for a given [Statistic].
|
||||
Widget buildStatisticTile({
|
||||
required Statistic statistic,
|
||||
required List<Match> matches,
|
||||
required List<Player> players,
|
||||
required BuildContext context,
|
||||
double? width,
|
||||
}) {
|
||||
final values = computeStatisticValues(
|
||||
statistic: statistic,
|
||||
matches: matches,
|
||||
players: players,
|
||||
);
|
||||
|
||||
return StatisticsTile(
|
||||
icon: _getStatisticIcon(type: statistic.type),
|
||||
@@ -46,7 +63,9 @@ Widget buildStatisticTile({
|
||||
width: width ?? MediaQuery.sizeOf(context).width * 0.95,
|
||||
values: values,
|
||||
barColor: _getStatisticColor(statistic),
|
||||
statistic: statistic,
|
||||
displayCount: statistic.displayCount,
|
||||
selectedGroups: statistic.selectedGroups,
|
||||
selectedGames: statistic.selectedGames,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -296,10 +315,8 @@ Widget buildSkeletonStatisticTile({required BuildContext context}) {
|
||||
width: MediaQuery.sizeOf(context).width * 0.95,
|
||||
values: values,
|
||||
barColor: _colorPalette[Random().nextInt(_colorPalette.length)],
|
||||
statistic: Statistic(
|
||||
type: StatisticType.totalMatches,
|
||||
scopes: [StatisticScope.allPlayers],
|
||||
timeframe: Timeframe.last7Days,
|
||||
),
|
||||
selectedGames: [Game(name: 'Game 1', ruleset: Ruleset.highestScore)],
|
||||
selectedGroups: [Group(name: 'Group 1', members: [])],
|
||||
displayCount: 5,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import 'package:tallee/data/models/player.dart';
|
||||
import 'package:tallee/data/models/statistic.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/statistics_view/create_statistic_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/statistics_view/statistic_detail_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/statistics_view/statistic_tile_factory.dart';
|
||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
|
||||
class StatisticsView extends StatefulWidget {
|
||||
/// A view that displays player statistics
|
||||
@@ -45,18 +47,30 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
fixLayoutBuilder: true,
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
...statisticTiles,
|
||||
SizedBox(height: MediaQuery.paddingOf(context).bottom + 80),
|
||||
],
|
||||
Visibility(
|
||||
visible: statisticTiles.isNotEmpty,
|
||||
replacement: Center(
|
||||
child: TopCenteredMessage(
|
||||
icon: Icons.info,
|
||||
title: loc.info,
|
||||
message: loc.no_statistics_created_yet,
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: AppSkeleton(
|
||||
enabled: isLoading,
|
||||
fixLayoutBuilder: true,
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
...statisticTiles,
|
||||
SizedBox(
|
||||
height: MediaQuery.paddingOf(context).bottom + 80,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -75,12 +89,7 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
),
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
final newTile = buildStatisticTile(
|
||||
statistic: newStatistic,
|
||||
matches: _allMatches,
|
||||
players: _allPlayers,
|
||||
context: context,
|
||||
);
|
||||
final newTile = _buildStatisticTile(context, newStatistic);
|
||||
|
||||
setState(() {
|
||||
statisticTiles.add(newTile);
|
||||
@@ -126,15 +135,40 @@ class _StatisticsViewState extends State<StatisticsView> {
|
||||
setState(() {
|
||||
statisticTiles = [
|
||||
for (final statistic in statistics) ...[
|
||||
buildStatisticTile(
|
||||
statistic: statistic,
|
||||
matches: _allMatches,
|
||||
players: _allPlayers,
|
||||
context: context,
|
||||
),
|
||||
_buildStatisticTile(context, statistic),
|
||||
],
|
||||
];
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildStatisticTile(BuildContext context, Statistic statistic) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final values = computeStatisticValues(
|
||||
statistic: statistic,
|
||||
matches: _allMatches,
|
||||
players: _allPlayers,
|
||||
);
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
builder: (context) => StatisticDetailView(
|
||||
statistic: statistic,
|
||||
values: values,
|
||||
icon: getStatisticIconForType(statistic.type),
|
||||
barColor: getStatisticColorForStatistic(statistic),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: buildStatisticTile(
|
||||
statistic: statistic,
|
||||
matches: _allMatches,
|
||||
players: _allPlayers,
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/models/game.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
import 'package:tallee/data/models/player.dart';
|
||||
import 'package:tallee/data/models/statistic.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||
|
||||
@@ -27,7 +26,9 @@ class StatisticsTile extends StatelessWidget {
|
||||
required this.width,
|
||||
required this.values,
|
||||
required this.barColor,
|
||||
required this.statistic,
|
||||
this.displayCount,
|
||||
this.selectedGroups,
|
||||
this.selectedGames,
|
||||
});
|
||||
|
||||
/// The icon displayed next to the title.
|
||||
@@ -45,7 +46,10 @@ class StatisticsTile extends StatelessWidget {
|
||||
/// The color of the bars representing the values.
|
||||
final Color barColor;
|
||||
|
||||
final Statistic statistic;
|
||||
final int? displayCount;
|
||||
|
||||
final List<Group>? selectedGroups;
|
||||
final List<Game>? selectedGames;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -70,8 +74,12 @@ class StatisticsTile extends StatelessWidget {
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxBarWidth = constraints.maxWidth * 0.8;
|
||||
final displayCount = min(values.length, statistic.displayCount);
|
||||
final displayValues = values.take(displayCount).toList();
|
||||
|
||||
// If displayCount wasnt provided, take all values
|
||||
final valuesShown = displayCount == null
|
||||
? values.length
|
||||
: min(values.length, displayCount!);
|
||||
final displayValues = values.take(valuesShown).toList();
|
||||
final maxVal = displayValues.isNotEmpty
|
||||
? displayValues.fold<num>(
|
||||
0,
|
||||
@@ -83,7 +91,7 @@ class StatisticsTile extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
// Bars
|
||||
...List.generate(displayCount, (index) {
|
||||
...List.generate(valuesShown, (index) {
|
||||
/// Fraction of wins
|
||||
final double fraction = (maxVal > 0)
|
||||
? (displayValues[index].$2 / maxVal)
|
||||
@@ -187,12 +195,14 @@ class StatisticsTile extends StatelessWidget {
|
||||
}),
|
||||
|
||||
// Group & Game info
|
||||
if (statistic.selectedGames != null ||
|
||||
statistic.selectedGroups != null)
|
||||
if (hasGame || hasGroup)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
// Game
|
||||
if (hasGroup)
|
||||
@@ -205,7 +215,7 @@ class StatisticsTile extends StatelessWidget {
|
||||
size: 20,
|
||||
),
|
||||
Text(
|
||||
getGameText(statistic.selectedGames!),
|
||||
getGameText(selectedGames!),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -227,7 +237,7 @@ class StatisticsTile extends StatelessWidget {
|
||||
color: CustomTheme.hintColor,
|
||||
),
|
||||
Text(
|
||||
getGroupText(statistic.selectedGroups!),
|
||||
getGroupText(selectedGroups!),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -265,9 +275,7 @@ class StatisticsTile extends StatelessWidget {
|
||||
return text;
|
||||
}
|
||||
|
||||
bool get hasGroup =>
|
||||
statistic.selectedGroups != null && statistic.selectedGroups!.isNotEmpty;
|
||||
bool get hasGroup => selectedGroups != null && selectedGroups!.isNotEmpty;
|
||||
|
||||
bool get hasGame =>
|
||||
statistic.selectedGames != null && statistic.selectedGames!.isNotEmpty;
|
||||
bool get hasGame => selectedGames != null && selectedGames!.isNotEmpty;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: tallee
|
||||
description: "Tracking App for Card Games"
|
||||
publish_to: 'none'
|
||||
version: 0.0.33+276
|
||||
version: 0.0.33+280
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
|
||||
Reference in New Issue
Block a user