Files
ZenXercise/lib/src/history/views/history_view.dart
T

132 lines
4.6 KiB
Dart
Raw Normal View History

import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../design/page_header.dart';
import '../../design/zen_nav_bar.dart';
import '../../design/zen_style.dart';
import '../../localization/app_localizations.dart';
import '../../player/views/summary_view.dart';
import '../../settings/settings_controller.dart';
import '../../streak/day.dart';
import '../session_repository.dart';
/// The current Mode's finished Sessions by day, the latest first; each opens
/// its summary again.
class HistoryView extends StatelessWidget {
const HistoryView({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final mode = context.watch<SettingsController>().mode;
return StreamBuilder(
stream: context.read<SessionRepository>().watchSessions(mode),
builder: (context, snapshot) {
final sessions = snapshot.data ?? const [];
if (sessions.isEmpty) {
return ListView(
children: [
PageHeader(title: l10n.historyTitle),
const SizedBox(height: 24),
EmptyState(icon: Icons.history_rounded, title: l10n.historyEmptyTitle, body: l10n.historyEmptyBody),
],
);
}
final today = Day.of(clock.now());
final rows = <Widget>[PageHeader(title: l10n.historyTitle)];
Day? shown;
for (final session in sessions) {
final day = Day.of(session.endedAt);
if (day != shown) {
shown = day;
rows.add(_DayHeader(label: _dayLabel(l10n, day, today)));
}
rows.add(
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
child: _SessionTile(session: session),
),
);
}
return ListView(
padding: EdgeInsets.only(bottom: ZenNavBar.height + 32 + MediaQuery.paddingOf(context).bottom),
children: rows,
);
},
);
}
static String _dayLabel(AppLocalizations l10n, Day day, Day today) {
if (day == today) return l10n.historyToday;
if (day == today.add(-1)) return l10n.historyYesterday;
final date = day.at(0, 0);
return day.year == today.year ? DateFormat.MMMEd().format(date) : DateFormat.yMMMEd().format(date);
}
}
class _DayHeader extends StatelessWidget {
const _DayHeader({required this.label});
final String label;
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
child: Text(label, style: Theme.of(context).textTheme.titleSmall?.copyWith(color: ZenStyle.of(context).inkMuted)),
);
}
class _SessionTile extends StatelessWidget {
const _SessionTile({required this.session});
final Session session;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final s = ZenStyle.of(context);
final text = Theme.of(context).textTheme;
final line = l10n.historySessionLine(
DateFormat.Hm().format(session.endedAt),
l10n.durationMinutes((session.duration.inSeconds / 60).round()),
);
return Card(
child: InkWell(
onTap: () => Navigator.of(context).pushNamed(SummaryView.routeName, arguments: session.id),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 12, 14),
child: Row(
children: [
Icon(
session.complete ? Icons.check_circle_rounded : Icons.flag_rounded,
color: session.complete ? s.accent : s.inkMuted,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(session.planName, style: text.titleMedium, maxLines: 1, overflow: TextOverflow.ellipsis),
const SizedBox(height: 2),
Text(line, style: text.bodySmall?.copyWith(color: s.inkMuted)),
],
),
),
if (!session.complete)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: s.surfaceSoft,
borderRadius: BorderRadius.circular(s.controlRadius.clamp(0, 12)),
),
child: Text(l10n.historyEndedEarly, style: text.labelSmall?.copyWith(color: s.inkMuted)),
),
],
),
),
),
);
}
}