Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions lib/core/glossary/stat_glossary_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../utils/app_text_styles.dart';
import 'stat_term.dart';

/// Shows a plain-language definition of a statistics [term] in a simple
/// [AlertDialog], mirroring the confirm-dialog pattern used elsewhere
/// (e.g. `settings_page.dart`). UI-only helper for #719.
Future<void> showStatGlossaryDialog(BuildContext context, StatTerm term) {
final l10n = AppLocalizations.of(context);
return showDialog<void>(
context: context,
builder: (ctx) {
final cs = Theme.of(ctx).colorScheme;
return AlertDialog(
title: Text(term.title(l10n)),
content: Text(
term.body(l10n),
style: AppTextStyles.bodyLarge.copyWith(color: cs.onSurface),
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text(l10n.commonDone),
),
],
);
},
);
}
24 changes: 24 additions & 0 deletions lib/core/glossary/stat_term.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:dart_lodge/l10n/gen/app_localizations.dart';

/// A statistics term that newcomers can tap to read a plain-language
/// definition (see #719). Kept UI-agnostic so it can be attached to any
/// stat-label render site.
enum StatTerm { ppr, mpr, checkoutPct, checkoutScore }

extension StatTermL10n on StatTerm {
/// Short heading shown as the glossary dialog title.
String title(AppLocalizations l10n) => switch (this) {
StatTerm.ppr => l10n.glossaryPprTitle,
StatTerm.mpr => l10n.glossaryMprTitle,
StatTerm.checkoutPct => l10n.glossaryCheckoutPctTitle,
StatTerm.checkoutScore => l10n.glossaryCheckoutScoreTitle,
};

/// Plain-language definition shown as the glossary dialog body.
String body(AppLocalizations l10n) => switch (this) {
StatTerm.ppr => l10n.glossaryPprBody,
StatTerm.mpr => l10n.glossaryMprBody,
StatTerm.checkoutPct => l10n.glossaryCheckoutPctBody,
StatTerm.checkoutScore => l10n.glossaryCheckoutScoreBody,
};
}
5 changes: 5 additions & 0 deletions lib/core/widgets/game_summary_section_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../glossary/stat_term.dart';
import '../utils/app_text_styles.dart';
import '../utils/app_theme.dart';
import '../utils/constants.dart';
Expand Down Expand Up @@ -159,6 +160,7 @@ class GameSummarySectionWidget extends StatelessWidget {
return [
PostGameBreakdownRow(
category: l10n.statAvgPpr,
term: StatTerm.ppr,
values: allCompetitors
.map((c) => StatFormatter.fmtDouble(c.threeDartAverage))
.toList(),
Expand Down Expand Up @@ -200,6 +202,7 @@ class GameSummarySectionWidget extends StatelessWidget {
return [
PostGameBreakdownRow(
category: l10n.statAvgMpr,
term: StatTerm.mpr,
values: allCompetitors
.map((c) =>
StatFormatter.fmtDouble(c.marksPerRound, decimals: 2))
Expand Down Expand Up @@ -255,13 +258,15 @@ class GameSummarySectionWidget extends StatelessWidget {
return [
PostGameBreakdownRow(
category: l10n.statAvgPpr,
term: StatTerm.ppr,
values: allCompetitors
.map((c) => StatFormatter.fmtDouble(c.threeDartAverage))
.toList(),
highlights: winnerHighlights,
),
PostGameBreakdownRow(
category: l10n.statCheckout,
term: StatTerm.checkoutPct,
values: allCompetitors
.map((c) =>
StatFormatter.fmtPct(c.checkoutPercentage, isRatio: false))
Expand Down
61 changes: 61 additions & 0 deletions lib/core/widgets/glossary_term_label.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../glossary/stat_glossary_dialog.dart';
import '../glossary/stat_term.dart';
import '../utils/app_spacing.dart';
import '../utils/app_theme.dart';

/// A stat label that advertises a tappable definition: the [label] text
/// followed by a small info glyph in the accent colour. Tapping it opens
/// [showStatGlossaryDialog] for [term] (#719).
///
/// The caller passes the already-cased display string ([label]) so the widget
/// stays casing-agnostic — stats tables use normal case, the recap breakdown
/// upper-cases.
class GlossaryTermLabel extends StatelessWidget {
const GlossaryTermLabel({
required this.label,
required this.term,
this.style,
this.textAlign,
super.key,
});

final String label;
final StatTerm term;
final TextStyle? style;
final TextAlign? textAlign;

@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final l10n = AppLocalizations.of(context);

return Semantics(
button: true,
label: term.title(l10n),
child: InkWell(
onTap: () => showStatGlossaryDialog(context, term),
borderRadius: BorderRadius.circular(AppTheme.radiusSmall),
splashColor: AppTheme.kineticSplashColor,
highlightColor: AppTheme.kineticSplashColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
label,
style: style,
textAlign: textAlign,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: AppSpacing.space1),
Icon(Icons.info_outline, size: 14, color: cs.primary),
],
),
),
);
}
}
21 changes: 17 additions & 4 deletions lib/core/widgets/post_game_stats_breakdown_widget.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../glossary/stat_term.dart';
import '../utils/app_text_styles.dart';
import '../utils/app_theme.dart';
import 'glossary_term_label.dart';

/// Minimum comfortable width per breakdown column (category + each player).
/// Below this the columns would be cramped, so the table falls back to a
Expand Down Expand Up @@ -34,11 +36,16 @@ class PostGameBreakdownRow {
required this.category,
required this.values,
required this.highlights,
this.term,
});

final String category;
final List<String> values;
final List<bool> highlights;

/// When set, [category] renders as a tappable glossary term (info glyph +
/// definition dialog) instead of plain text (#719).
final StatTerm? term;
}

/// Reusable "STATISTICS BREAKDOWN" table shell — section title with a short
Expand Down Expand Up @@ -241,10 +248,16 @@ class _BreakdownTable extends StatelessWidget {
children: [
Padding(
padding: cellPadding,
child: Text(
row.category.toUpperCase(),
style: categoryStyle,
),
child: row.term != null
? GlossaryTermLabel(
label: row.category.toUpperCase(),
term: row.term!,
style: categoryStyle,
)
: Text(
row.category.toUpperCase(),
style: categoryStyle,
),
),
...List.generate(columns.length, (i) {
final isHighlight = row.highlights[i];
Expand Down
24 changes: 19 additions & 5 deletions lib/core/widgets/stats_table_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';

import '../glossary/stat_term.dart';
import '../utils/app_spacing.dart';
import 'glossary_term_label.dart';

sealed class StatsTableRow {}

Expand All @@ -27,7 +29,11 @@ class StatsTableDataRow extends StatsTableRow {
final String label;
final String col1;
final String? col2;
StatsTableDataRow(this.label, this.col1, [this.col2]);

/// When set, [label] renders as a tappable glossary term (info glyph +
/// definition dialog) instead of plain text (#719).
final StatTerm? term;
StatsTableDataRow(this.label, this.col1, [this.col2, this.term]);
}

class StatsTableWidget extends StatelessWidget {
Expand Down Expand Up @@ -130,10 +136,18 @@ class StatsTableWidget extends StatelessWidget {
child: Row(
children: [
Expanded(
child: Text(
row.label,
style: theme.textTheme.bodyMedium?.copyWith(color: cs.onSurface),
),
child: Builder(builder: (_) {
final labelStyle =
theme.textTheme.bodyMedium?.copyWith(color: cs.onSurface);
if (row.term != null) {
return GlossaryTermLabel(
label: row.label,
term: row.term!,
style: labelStyle,
);
}
return Text(row.label, style: labelStyle);
}),
),
SizedBox(
width: valueColumnWidth,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:dart_lodge/core/glossary/stat_term.dart';
import 'package:dart_lodge/core/utils/constants.dart';
import 'package:dart_lodge/core/utils/name_formatter.dart';
import 'package:dart_lodge/core/utils/stat_formatter.dart';
Expand Down Expand Up @@ -226,6 +227,7 @@ class _LegBreakdownTableWidgetState extends State<LegBreakdownTableWidget> {
final rows = statRows
.map((r) => PostGameBreakdownRow(
category: r.category,
term: r.term,
values: r.values,
highlights: [
for (final c in competitors)
Expand All @@ -247,13 +249,15 @@ class _LegBreakdownTableWidgetState extends State<LegBreakdownTableWidget> {
return [
_StatRow(
category: l10n.statAvgPpr,
term: StatTerm.ppr,
values: competitors
.map((c) => StatFormatter.fmtDouble(c.threeDartAverage))
.toList(),
highlightWinner: true,
),
_StatRow(
category: l10n.statCheckout,
term: StatTerm.checkoutPct,
values: competitors
.map((c) =>
StatFormatter.fmtPct(c.checkoutPercentage, isRatio: false))
Expand Down Expand Up @@ -299,6 +303,7 @@ class _LegBreakdownTableWidgetState extends State<LegBreakdownTableWidget> {
return [
_StatRow(
category: l10n.statAvgMpr,
term: StatTerm.mpr,
values: competitors
.map((c) => StatFormatter.fmtDouble(c.marksPerRound, decimals: 2))
.toList(),
Expand Down Expand Up @@ -339,10 +344,12 @@ class _StatRow {
final String category;
final List<String> values;
final bool highlightWinner;
final StatTerm? term;

_StatRow({
required this.category,
required this.values,
this.highlightWinner = false,
this.term,
});
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../../../../core/glossary/stat_term.dart';
import '../../../../core/utils/stat_formatter.dart';
import '../../../../core/widgets/stats_table_widget.dart';
import '../../domain/entities/player_stats.dart';
Expand All @@ -17,7 +18,7 @@ class CricketStatsDetailTableWidget extends StatelessWidget {
StatsTableHeader(l10n.statsColAverage, col2: l10n.statsColBest),
StatsTableDataRow('MPR',
StatFormatter.fmtDouble(stats.marksPerTurn, decimals: 2),
StatFormatter.fmtDouble(stats.bestLegMpt, decimals: 2)),
StatFormatter.fmtDouble(stats.bestLegMpt, decimals: 2), StatTerm.mpr),
StatsTableDataRow(l10n.statFirst9Mpr,
StatFormatter.fmtDouble(stats.firstNineMpr, decimals: 2),
'—'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'package:dart_lodge/l10n/gen/app_localizations.dart';
import '../../../../core/glossary/stat_term.dart';
import '../../../../core/utils/stat_formatter.dart';
import '../../../../core/widgets/stats_table_widget.dart';
import '../../domain/entities/player_stats.dart';
Expand All @@ -17,17 +18,19 @@ class StatsDetailTableWidget extends StatelessWidget {
StatsTableHeader(l10n.statsColAverage, col2: l10n.statsColBest),
StatsTableDataRow('PPR',
StatFormatter.fmtDouble(stats.threeDartAverage),
StatFormatter.fmtDouble(stats.bestLegPpr)),
StatFormatter.fmtDouble(stats.bestLegPpr), StatTerm.ppr),
StatsTableDataRow(l10n.statsFirst9Ppr,
StatFormatter.fmtDouble(stats.firstNinePpr),
StatFormatter.fmtDouble(stats.bestFirstNinePpr)),
StatsTableDataRow(l10n.statsCheckoutPct,
StatFormatter.fmtPct(stats.checkoutPercentage, isRatio: false),
StatFormatter.fmtPct(stats.bestGameCheckoutPercentage, isRatio: false)),
StatFormatter.fmtPct(stats.bestGameCheckoutPercentage, isRatio: false),
StatTerm.checkoutPct),
StatsTableDataRow(
l10n.statsCheckoutPoints,
StatFormatter.fmtDouble(stats.avgCheckoutScore),
StatFormatter.fmtInt(stats.highestCheckout),
StatTerm.checkoutScore,
),
StatsTableDataRow(l10n.statsWinPct, StatFormatter.fmtPct(stats.winRate), '—'),
StatsTableHeader(l10n.statsColTotal, col2: l10n.statsColPerLeg),
Expand Down
10 changes: 9 additions & 1 deletion lib/l10n/arb/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -562,5 +562,13 @@
"achievementUnlockedOn": "Freigeschaltet am {date}",
"achievementProgress": "{current} / {target}",
"achievementsLoadFailed": "Erfolge konnten nicht geladen werden: {error}",
"achievementUnlockedBanner": "🏆 Erfolg freigeschaltet: {title}"
"achievementUnlockedBanner": "🏆 Erfolg freigeschaltet: {title}",
"glossaryPprTitle": "PPR — Punkte pro Runde",
"glossaryPprBody": "Dein Drei-Dart-Average: die durchschnittlichen Punkte pro kompletter Aufnahme von 3 Darts. Höher ist besser — ein solider Amateur liegt bei etwa 40–60.",
"glossaryMprTitle": "MPR — Marks pro Runde",
"glossaryMprBody": "Eine Cricket-Statistik: die durchschnittliche Anzahl an Marks (Treffer auf 15–20 und das Bull) pro Aufnahme von 3 Darts. Ein Triple zählt als 3 Marks, das Maximum pro Aufnahme ist also 9.",
"glossaryCheckoutPctTitle": "Checkout %",
"glossaryCheckoutPctBody": "Wie oft du das Leg beendest, wenn du eine Chance auf ein Doppel hast: getroffene Checkouts geteilt durch die Anzahl deiner Chancen.",
"glossaryCheckoutScoreTitle": "Checkout-Score",
"glossaryCheckoutScoreBody": "Der Punktestand, mit dem du ein Leg beendet hast — die Summe, die du mit deiner Gewinn-Aufnahme ausgecheckt hast. Ein höherer Checkout ist ein schwierigeres Finish."
}
Loading