feat: statistic detail view
This commit is contained in:
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user