import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../design/zen_style.dart'; import '../../exercises/exercise_library.dart'; import '../../history/session_repository.dart'; 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, 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); final s = ZenStyle.of(context); final text = Theme.of(context).textTheme; return Scaffold( body: FutureBuilder( future: context.read().get(sessionId), builder: (context, snapshot) { final session = snapshot.data; if (session == null) return const SizedBox.shrink(); final exercises = { for (final item in session.items) ...switch (item) { WorkoutStep(:final exerciseId) || StretchingStep(:final exerciseId) => [exerciseId], Superset(:final members) => members.map((m) => m.exerciseId), }, }; return SafeArea( child: ListView( padding: const EdgeInsets.fromLTRB(20, 40, 20, 24), children: [ Icon( session.complete ? Icons.check_circle_rounded : Icons.flag_rounded, size: 56, color: session.complete ? s.accent : s.inkMuted, ), const SizedBox(height: 16), 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: [ Expanded( child: _Stat( label: l10n.summaryDuration, value: l10n.durationMinutes((session.duration.inSeconds / 60).round()), ), ), const SizedBox(width: 10), Expanded( child: session.mode == Mode.workout ? _Stat(label: l10n.summarySets, value: '${session.setLogs.length}') : _Stat(label: l10n.summaryExercises, value: '${exercises.length}'), ), ], ), if (session.setLogs.isNotEmpty) ...[ const SizedBox(height: 28), Text(l10n.summaryLogTitle, style: text.titleMedium), const SizedBox(height: 10), for (final group in _byExercise(session.setLogs)) _LogGroup(logs: group), ], const SizedBox(height: 28), SizedBox( height: 52, child: FilledButton(onPressed: () => Navigator.of(context).pop(), child: Text(l10n.done)), ), ], ), ); }, ), ); } /// Consecutive logs of the same Exercise together, as they were done. static List> _byExercise(List logs) { final groups = >[]; for (final log in logs) { if (groups.isNotEmpty && groups.last.first.exerciseId == log.exerciseId) { groups.last.add(log); } else { groups.add([log]); } } return groups; } } /// "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(); 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}); final String label; final String value; @override Widget build(BuildContext context) { final s = ZenStyle.of(context); final text = Theme.of(context).textTheme; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: s.surface, borderRadius: BorderRadius.circular(s.radius * 0.7)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: text.bodySmall?.copyWith(color: s.inkMuted)), const SizedBox(height: 4), Text(value, style: text.headlineSmall), ], ), ); } } class _LogGroup extends StatelessWidget { const _LogGroup({required this.logs}); final List logs; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final s = ZenStyle.of(context); final text = Theme.of(context).textTheme; final name = context.read().byId(logs.first.exerciseId)?.name ?? logs.first.exerciseId; return Padding( padding: const EdgeInsets.only(bottom: 14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(name, style: text.titleSmall), for (final log in logs) Text( [ l10n.setLogLine(log.set), if (log.reps != null) l10n.repsCount(log.reps!), if (log.seconds != null) l10n.secondsShort(log.seconds!), if (log.weightKg != null) l10n.weightKg(kgNumber(log.weightKg!)), ].join(' ยท '), style: text.bodyMedium?.copyWith(color: s.inkMuted), ), ], ), ); } }