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
@@ -10,6 +10,7 @@ import 'package:zenxercise/src/history/session_repository.dart';
|
||||
import 'package:zenxercise/src/plans/plan_repository.dart';
|
||||
import 'package:zenxercise/src/player/player_effects.dart';
|
||||
import 'package:zenxercise/src/settings/settings_controller.dart';
|
||||
import 'package:zenxercise/src/streak/reminder_scheduler.dart';
|
||||
|
||||
import 'test_database.dart';
|
||||
|
||||
@@ -62,6 +63,31 @@ class FakePlayerEffects implements PlayerEffects {
|
||||
Future<void> keepScreenOn(bool on) async => screenOn = on;
|
||||
}
|
||||
|
||||
/// Records what would be scheduled instead of talking to Android.
|
||||
class FakeNotifier implements Notifier {
|
||||
final scheduled = <(DateTime, String)>[];
|
||||
var permissionRequests = 0;
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
Future<bool> requestPermission() async {
|
||||
permissionRequests++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> enabled() async => true;
|
||||
|
||||
@override
|
||||
Future<void> cancelAll() async => scheduled.clear();
|
||||
|
||||
@override
|
||||
Future<void> schedule({required int id, required DateTime at, required String title, required String body}) async =>
|
||||
scheduled.add((at, body));
|
||||
}
|
||||
|
||||
/// A Pixel-sized screen instead of the default 800×600, so the floating
|
||||
/// navigation bar does not cover what a test taps.
|
||||
void usePhoneScreen(WidgetTester tester) {
|
||||
@@ -78,20 +104,25 @@ Future<SettingsController> pumpApp(
|
||||
PlanRepository? plans,
|
||||
SessionRepository? sessions,
|
||||
PlayerEffects? effects,
|
||||
FakeNotifier? notifier,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues(prefs);
|
||||
final settings = SettingsController(SettingsService());
|
||||
await settings.loadSettings();
|
||||
final db = testDatabase();
|
||||
addTearDown(db.close);
|
||||
final sessionRepository = sessions ?? SessionRepository(db);
|
||||
final fakeNotifier = notifier ?? FakeNotifier();
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: settings),
|
||||
Provider.value(value: library ?? sampleLibrary),
|
||||
Provider.value(value: plans ?? PlanRepository(db)),
|
||||
Provider.value(value: sessions ?? SessionRepository(db)),
|
||||
Provider.value(value: sessionRepository),
|
||||
Provider<PlayerEffects>.value(value: effects ?? FakePlayerEffects()),
|
||||
Provider<Notifier>.value(value: fakeNotifier),
|
||||
Provider.value(value: ReminderScheduler(fakeNotifier, settings, sessionRepository)),
|
||||
],
|
||||
child: const ZenXerciseApp(),
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user