Stage 5: History, the shared daily Streak and Reminders
- Streak computed from all complete Sessions: Grace Hour (00:00-01:00 counts for yesterday unless it is done), a Freeze per 7 Streak Days (max 2) spent automatically on a missed day (ADR 0006) - Top bar flame (grey until today is done) opening a panel with current and longest Streak, Freezes and a month calendar; 'Day N of your streak' on a fresh summary - History per Mode grouped by day, reopening each summary - Two adjustable Reminders (19:00, 22:30) scheduled 14 days ahead with flutter_local_notifications, none on a done day; permission asked after the first complete Session or from Settings - Settings: Reminders, the Player chime, exercise credits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
971ff64117
commit
1ffe6269ea
31 files changed
+1972
-50
No files matched your search
@@ -8,17 +8,23 @@ import '../../localization/app_localizations.dart';
|
||||
import '../../modes/mode.dart';
|
||||
import '../../plans/models/plan.dart';
|
||||
import '../../plans/plan_labels.dart';
|
||||
import '../../streak/streak.dart';
|
||||
import '../../streak/views/streak_views.dart';
|
||||
import '../session_runner.dart';
|
||||
|
||||
/// Shown when a Session ends: how long it took, what was done and, for
|
||||
/// Workouts, every logged set.
|
||||
class SummaryView extends StatelessWidget {
|
||||
const SummaryView({super.key, required this.sessionId});
|
||||
const SummaryView({super.key, required this.sessionId, this.justFinished = false});
|
||||
|
||||
static const routeName = '/summary';
|
||||
|
||||
final String sessionId;
|
||||
|
||||
/// Straight from the Player rather than opened again from History: only
|
||||
/// then is the Session's Streak Day worth pointing out.
|
||||
final bool justFinished;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
@@ -50,6 +56,7 @@ class SummaryView extends StatelessWidget {
|
||||
Text(session.complete ? l10n.summaryComplete : l10n.summaryIncomplete, style: text.headlineLarge),
|
||||
const SizedBox(height: 4),
|
||||
Text(session.planName, style: text.titleMedium?.copyWith(color: s.inkMuted)),
|
||||
if (justFinished && session.complete) _StreakLine(session: session),
|
||||
const SizedBox(height: 28),
|
||||
Row(
|
||||
children: [
|
||||
@@ -100,6 +107,53 @@ class SummaryView extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// "Day 12 of your streak", when this Session was the first to complete its day.
|
||||
class _StreakLine extends StatelessWidget {
|
||||
const _StreakLine({required this.session});
|
||||
|
||||
final Session session;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final s = ZenStyle.of(context);
|
||||
final sessions = context.read<SessionRepository>();
|
||||
return StreakBuilder(
|
||||
builder: (context, streak) => FutureBuilder(
|
||||
future: sessions.completeEndTimes(),
|
||||
builder: (context, snapshot) {
|
||||
final times = snapshot.data;
|
||||
if (times == null) return const SizedBox.shrink();
|
||||
final before = streakDays(times.where((t) => t != session.endedAt));
|
||||
final fresh = streakDays(times).length > before.length;
|
||||
if (!fresh || streak.current == 0) return const SizedBox.shrink();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.local_fire_department_rounded, color: s.accent),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
l10n.summaryStreak(streak.current),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(color: s.accent),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// What the summary route takes.
|
||||
class SummaryArguments {
|
||||
const SummaryArguments(this.sessionId, {this.justFinished = false});
|
||||
|
||||
final String sessionId;
|
||||
final bool justFinished;
|
||||
}
|
||||
|
||||
class _Stat extends StatelessWidget {
|
||||
const _Stat({required this.label, required this.value});
|
||||
|
||||
|
||||
Reference in new issue
Block a user