Add Progress over time and a targets and reminders setup

Progress gives a Day/Week/Month view of routines held against a weekly
target, compared with the same point of the previous period, with active
time and the daily streak. Targets & reminders sets routines and minutes
per week and schedules a heads-up before each routine. The runner's summary
now shows today's held count and streak. Finished runs are mirrored to a
device run log that feeds both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
alvocool
2026-11-08 20:17:58 +03:00
parent 4b71257429
commit add94f621c
26 changed files with 1590 additions and 4 deletions

View File

@@ -1,8 +1,11 @@
// MOCK DATA — TEMPORARY. See MockSwitch.dart.
import 'dart:math';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../about/internal/application/MeDescription.dart';
import '../about/internal/application/RoutineRunRecord.dart';
import '../about/internal/application/TodoItem.dart';
import '../about/internal/application/UserDetails.dart';
import '../memory/InternalMemory.dart';
@@ -24,9 +27,79 @@ class MockMemory extends InternalMemory {
TodoItem(id: "t2", title: "Book physio for Thursday"),
TodoItem(id: "t3", title: "Reply to Sam about Saturday", done: true),
]);
setRunLog(_history());
}
}
/// Six weeks of runs against the seeded routines, mostly held, with the
/// odd broken chain and skipped day, so Progress has a shape to draw.
static List<RoutineRunRecord> _history() {
final Random random = Random(11);
final DateTime now = DateTime.now();
final DateTime today = DateTime(now.year, now.month, now.day);
final List<RoutineRunRecord> records = <RoutineRunRecord>[];
void add(DateTime day, String id, String title, String kind, int minute,
int seconds, int steps, double holdRate) {
final DateTime at = day.add(Duration(minutes: minute));
if (at.isAfter(now)) {
return;
}
final bool held = random.nextDouble() < holdRate;
records.add(RoutineRunRecord(
routineId: id,
title: title,
kind: kind,
startedAt: at,
seconds: (seconds * (held ? 1 : 0.5) * (0.85 + random.nextDouble() * 0.3))
.round(),
completedSteps: held ? steps : 1 + random.nextInt(steps - 1),
totalSteps: steps,
));
}
for (int ago = 42; ago >= 1; ago--) {
final DateTime day = today.subtract(Duration(days: ago));
final int weekday = day.weekday;
// A quiet day now and then: nothing run at all.
if (random.nextDouble() < 0.12) {
continue;
}
if (weekday <= 5) {
add(day, "routine-morning", "Morning non-negotiables", "MustDo",
7 * 60 + 15, 14 * 60, 4, 0.85);
add(day, "routine-shutdown", "Evening shutdown", "MustDo", 18 * 60,
9 * 60, 4, 0.6);
}
if (weekday == 1 || weekday == 3 || weekday == 5) {
add(day, "routine-strength", "Full body strength", "Workout",
17 * 60 + 30, 44 * 60, 6, 0.75);
}
if (weekday >= 6) {
add(day, "routine-mobility", "Mobility flow", "Workout", 10 * 60,
22 * 60, 5, 0.8);
}
if (random.nextDouble() < 0.35) {
add(day, "routine-breathing", "Box breathing", "Meditation", 21 * 60,
8 * 60, 3, 0.95);
}
}
// Today: the morning chain is already held in the seeded routines.
records.add(RoutineRunRecord(
routineId: "routine-morning",
title: "Morning non-negotiables",
kind: "MustDo",
startedAt: today.add(const Duration(minutes: 7 * 60 + 10)),
seconds: 13 * 60 + 40,
completedSteps: 4,
totalSteps: 4,
));
return records;
}
@override
FlutterSecureStorage get groundedStorage => const MockStorage();
}