Skip to content

Commit 2da856d

Browse files
authored
Merge pull request #354 from DevKor-github/feat/5-minutes-early-preparation
Handle timed preparation cache lifecycle
2 parents 145fcb1 + 5ce7980 commit 2da856d

44 files changed

Lines changed: 3362 additions & 442 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/Architecture.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,10 @@ Database ← ScheduleDao ← ScheduleRepository ← ScheduleEntity
273273
- **Calendar integration** with multiple view modes
274274
- **Preparation time calculation** and management
275275
- **Real-time synchronization** between local and remote data
276-
- **Automatic timer system** for schedule start notifications
276+
- **Runtime preparation flow** with official timer starts, early-start entry, and finish/lateness handling
277+
- **Cache-coherent timed-preparation resume** using snapshot metadata and fingerprint invalidation
277278

278-
📖 **Detailed Documentation**: For comprehensive information about the automatic timer system, see [Schedule Timer System](./Schedule-Timer-System.md)
279+
📖 **Detailed Documentation**: For the canonical v2 state machine, sequence flows, cache coherence model, and verification matrix, see [Schedule Timer & Preparation Runtime Flow (v2)](./Schedule-Timer-System.md)
279280

280281
### 3. **Notification System**
281282

docs/Home.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Welcome to the OnTime-front project documentation! This wiki contains everything
1010
- [Git Workflow](./Git.md) - Git strategy and commit message formats
1111

1212
### Development Resources
13+
- [Schedule Timer & Runtime Flow (v2)](./Schedule-Timer-System.md) - Official behavior for early start, timer transitions, cache coherence, and verification matrix
1314
- 🚧 **Getting Started Guide** *(Coming Soon)* - Setup and installation
1415
- 🚧 **Development Guide** *(Coming Soon)* - Development workflow and best practices
1516
- 🚧 **API Documentation** *(Coming Soon)* - Backend API reference

docs/Schedule-Timer-System.md

Lines changed: 216 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,253 @@
1-
# Schedule Timer System
1+
# Schedule Timer & Preparation Runtime Flow (v2)
22

3-
The OnTime app includes an automatic timer system within the `ScheduleBloc` that manages precise timing for schedule start notifications. This system ensures that schedule start events are triggered at the exact scheduled time.
3+
This document is the canonical runtime-flow reference for preparation timing in OnTime.
4+
It covers official schedule-start timing, early start, cache coherence, resume behavior, finish semantics, and verification scenarios.
45

5-
## 🎯 Overview
6+
## Purpose and Scope
67

7-
The timer system automatically:
8+
The runtime flow decides:
89

9-
- Starts when an upcoming schedule is received
10-
- Triggers a `ScheduleStarted` event at exactly x minute 00 seconds
11-
- Handles proper cleanup and timer management
12-
- Ensures thread-safety and prevents memory leaks
10+
- Which schedule state should be active (`initial`, `notExists`, `upcoming`, `ongoing`, `started`)
11+
- When to open `/scheduleStart` and `/alarmScreen`
12+
- How early-start sessions survive app restarts
13+
- When cached timed-preparation is valid, restored, or invalidated
14+
- How lateness is computed at finish time
1315

14-
## 🔄 Timer Flow
16+
## Core Invariants
17+
18+
1. `step completion != lateness`
19+
- Completing all steps early does not mean late.
20+
- Lateness is decided only when finishing preparation (`ScheduleFinished`) relative to leave threshold.
21+
22+
2. No route bounce after early start
23+
- If the user already early-started a schedule, official start callback must not reopen `/scheduleStart`.
24+
25+
3. No stale cache resurrection after schedule mutation
26+
- Timed-preparation snapshots are valid only when `scheduleFingerprint` matches the current schedule payload.
27+
- Fingerprint mismatch clears persisted session/snapshot and resets to canonical preparation.
28+
29+
## Runtime State Model
30+
31+
### State meanings
32+
33+
- `initial`: bloc boot state before schedule resolution.
34+
- `notExists`: no active schedule to prepare, or schedule already ended.
35+
- `upcoming`: current time is before `preparationStartTime`.
36+
- `ongoing`: current time is between `preparationStartTime` and `scheduleTime`.
37+
- `started`: preparation start flow is active (including early start path).
38+
- `isEarlyStarted`: flag on `ScheduleState` to mark early-started session context while in `started`.
39+
40+
### State transitions
1541

1642
```mermaid
17-
sequenceDiagram
18-
participant UC as UseCase
19-
participant SB as ScheduleBloc
20-
participant Timer as Timer
21-
participant State as State
22-
23-
UC->>SB: ScheduleUpcomingReceived(schedule)
24-
SB->>SB: Cancel existing timer
25-
SB->>SB: _startScheduleTimer(schedule)
26-
SB->>Timer: Create Timer(targetTime)
27-
Note over Timer: Timer set for schedule.scheduleTime<br/>at x minute 00 seconds
28-
SB->>State: emit(ScheduleState.upcoming/ongoing)
29-
30-
Timer-->>SB: Timer fires at exact minute
31-
SB->>SB: add(ScheduleStarted())
32-
SB->>SB: _onScheduleStarted()
33-
SB->>State: emit(ScheduleState.started)
34-
35-
Note over SB: Timer cleanup on close()<br/>or new schedule received
43+
stateDiagram-v2
44+
[*] --> initial
45+
initial --> notExists: no schedule or ended schedule
46+
initial --> upcoming: now < preparationStartTime
47+
initial --> ongoing: preparationStartTime < now < scheduleTime
48+
initial --> started: early-start session restored
49+
50+
upcoming --> started: official start timer -> ScheduleStarted
51+
upcoming --> started: early start action -> SchedulePreparationStarted
52+
upcoming --> notExists: schedule ended/null
53+
54+
ongoing --> started: ScheduleStarted
55+
ongoing --> notExists: schedule ended/null
56+
57+
started --> started: official start callback while isEarlyStarted == true (no-op)
58+
started --> notExists: ScheduleFinished / schedule ended / schedule switch cleanup
59+
60+
notExists --> upcoming: new future schedule
61+
notExists --> ongoing: late entry into prep window
3662
```
3763

38-
## 📋 Implementation Details
64+
## Entry Paths, Router Behavior, and Finish Semantics
3965

40-
### Key Components
66+
### Entry paths
4167

42-
1. **Timer Management**
68+
1. Official timer start
69+
- In `upcoming`, bloc starts a timer targeting `preparationStartTime`.
70+
- Timer callback dispatches `ScheduleStarted`.
71+
- If not early-started, `/scheduleStart` is pushed.
4372

44-
- `_scheduleStartTimer`: Dart Timer instance that handles the countdown
45-
- `_currentScheduleId`: Tracks the active schedule to prevent stale events
73+
2. 5-minute variant start-now
74+
- On `/scheduleStart` (`isFiveMinutesBefore=true`), primary action dispatches `SchedulePreparationStarted` and navigates to `/alarmScreen`.
75+
- Secondary action navigates to `/home`.
4676

47-
2. **Event Handling**
77+
3. Delayed entry
78+
- If user enters during `ongoing`, bloc applies catch-up elapsed time and begins ticking from current clock.
4879

49-
- `ScheduleUpcomingReceived`: Triggers timer setup
50-
- `ScheduleStarted`: Fired when timer completes
80+
4. Stale/ended schedule path
81+
- If schedule is already ended or stream emits `null`, state goes `notExists`.
82+
- Alarm listener redirects safely to `/home` instead of remaining in a loading state.
5183

52-
3. **State Transitions**
53-
- `upcoming``started`: When timer fires for upcoming schedules
54-
- `ongoing``started`: When timer fires for preparation-in-progress schedules
84+
### Router and UX rules
5585

56-
### Timer Calculation
86+
- `/scheduleStart` builder checks `ScheduleState.isEarlyStarted`.
87+
- If early-start is active, `/scheduleStart` resolves directly to `AlarmScreen` (no bounce).
88+
- `/alarmScreen` in `upcoming` shows early-start-ready UI (countdown + start + home), not indefinite spinner.
5789

58-
The timer calculates the exact target time as:
90+
### Finish semantics
5991

60-
```dart
61-
final targetTime = DateTime(
62-
scheduleTime.year,
63-
scheduleTime.month,
64-
scheduleTime.day,
65-
scheduleTime.hour,
66-
scheduleTime.minute,
67-
0, // Always trigger at 00 seconds
68-
0, // 0 milliseconds
69-
);
92+
- Finish before leave threshold -> `lateness=0`.
93+
- Finish after leave threshold -> positive lateness minutes.
94+
- Completion dialog copy is late-aware:
95+
late case uses running-late wording, non-late case uses completion wording.
96+
97+
### Sequence flow
98+
99+
```mermaid
100+
sequenceDiagram
101+
participant User
102+
participant Start as "ScheduleStartScreen (5-min variant)"
103+
participant Bloc as ScheduleBloc
104+
participant Router
105+
participant Alarm as AlarmScreen
106+
participant Timer as OfficialStartTimer
107+
108+
User->>Start: Tap "Start Preparing"
109+
Start->>Bloc: add(SchedulePreparationStarted)
110+
Start->>Router: go("/alarmScreen")
111+
Bloc->>Bloc: mark early-start session + cancel start timer
112+
Bloc->>Alarm: emit started(isEarlyStarted=true)
113+
114+
alt User taps "Start in 5 minutes"
115+
User->>Start: Tap secondary button
116+
Start->>Router: go("/home")
117+
end
118+
119+
Timer-->>Bloc: Official start callback
120+
alt already early-started for same schedule
121+
Bloc->>Bloc: no-op (no duplicate push)
122+
else not early-started
123+
Bloc->>Router: push("/scheduleStart")
124+
end
70125
```
71126

72-
### Safety Features
127+
## Cache Coherence and Resume Model
73128

74-
- **Timer Cancellation**: Previous timers are automatically cancelled when new schedules arrive
75-
- **Bloc State Validation**: Timer callbacks verify the bloc is still active before firing events
76-
- **Schedule ID Matching**: Events only fire for the currently tracked schedule
77-
- **Proper Cleanup**: All timers are cancelled when the bloc is disposed
129+
### Persisted artifacts
78130

79-
## 🛡️ Error Handling
131+
1. Early start session
132+
- Stored by schedule ID with `startedAt`.
80133

81-
The system includes several safety mechanisms:
134+
2. Timed preparation snapshot
135+
- Stored by schedule ID with:
136+
`preparation`, `savedAt`, `scheduleFingerprint`.
82137

83-
1. **Past Schedule Protection**: Timers are not set for schedules in the past
84-
2. **Bloc Lifecycle Management**: Timer callbacks check `isClosed` before adding events
85-
3. **Memory Leak Prevention**: All timers are properly cancelled in `close()`
86-
4. **Race Condition Prevention**: Schedule ID tracking prevents stale timer events
138+
### Resume and invalidation policy
87139

88-
## 📱 Usage Example
140+
- On schedule emission, runtime checks for early-start session and snapshot.
141+
- Snapshot is used only when fingerprint matches current schedule payload.
142+
- On restore, preparation is fast-forwarded by `(now - savedAt)`.
143+
- On mismatch, clear persisted session/snapshot and use canonical preparation.
144+
- Persisted state is also cleared on:
145+
finish success, schedule end/null, and schedule-id switch.
89146

90-
The timer system works automatically within the `ScheduleBloc`:
147+
```mermaid
148+
flowchart TD
149+
A["ScheduleUpcomingReceived(incoming)"] --> B{"Incoming schedule exists and not ended?"}
150+
B -- No --> C["Clear persisted state for stale/current id"] --> D["Emit notExists"]
151+
B -- Yes --> E{"Schedule id switched?"}
152+
E -- Yes --> F["Clear persisted state for previous schedule id"]
153+
E -- No --> G["Continue"]
154+
F --> G
155+
156+
G --> H{"Early-start session exists for incoming id?"}
157+
H -- No --> I{"incoming.preparationStartTime > now?"}
158+
I -- Yes --> J["Clear timed snapshot for incoming id (prevent stale pre-start revive)"] --> K["Use canonical incoming preparation"]
159+
I -- No --> L["Load timed snapshot"]
160+
H -- Yes --> L
161+
162+
L --> M{"Snapshot exists?"}
163+
M -- No --> K
164+
M -- Yes --> N{"snapshot.fingerprint == incoming.fingerprint?"}
165+
N -- No --> O["Clear session + timed snapshot"] --> K
166+
N -- Yes --> P["Restore snapshot and fast-forward by now - savedAt"]
167+
168+
K --> Q{"Early-start session exists?"}
169+
P --> Q
170+
Q -- Yes --> R["Emit started(isEarlyStarted=true) and start ticking"]
171+
Q -- No --> S{"now within preparation window?"}
172+
S -- Yes --> T["Emit ongoing and start ticking"]
173+
S -- No --> U["Emit upcoming and arm official start timer"]
174+
175+
R --> V["On finish/end/switch: clear session + snapshot"]
176+
T --> V
177+
U --> V
178+
```
179+
180+
## Public Interfaces / Types
181+
182+
### `EarlyStartSessionRepository`
183+
184+
Contract:
91185

92186
```dart
93-
// When a new schedule is received
94-
bloc.add(ScheduleSubscriptionRequested());
95-
96-
// The bloc will:
97-
// 1. Listen for upcoming schedules
98-
// 2. Automatically start timers for each schedule
99-
// 3. Emit ScheduleStarted events at the exact scheduled time
100-
// 4. Transition to 'started' state
101-
102-
// Listen for state changes
103-
bloc.stream.listen((state) {
104-
if (state.status == ScheduleStatus.started) {
105-
// Handle schedule start (e.g., show notification, start tracking)
106-
}
107-
});
187+
abstract interface class EarlyStartSessionRepository {
188+
Future<void> markStarted({required String scheduleId, required DateTime startedAt});
189+
Future<EarlyStartSessionEntity?> getSession(String scheduleId);
190+
Future<void> clear(String scheduleId);
191+
}
108192
```
109193

110-
## 🔧 Configuration
194+
### Timed snapshot schema
195+
196+
`TimedPreparationSnapshotEntity` fields:
197+
198+
- `PreparationWithTimeEntity preparation`
199+
- `DateTime savedAt`
200+
- `String scheduleFingerprint`
201+
202+
### `ScheduleBloc` additions relevant to this flow
203+
204+
- Dependencies for early-start/session and snapshot read/clear.
205+
- `nowProvider` + notification hook in test constructor for deterministic tests.
206+
- Guarded timer/event handling to prevent closed-bloc event races.
207+
208+
### `ScheduleState` addition
209+
210+
- `bool isEarlyStarted` differentiates early-started started-state from official start flow.
211+
212+
### Router rule
213+
214+
- If `/scheduleStart` resolves while current `ScheduleState.isEarlyStarted == true`, route resolves to `/alarmScreen` UI (`AlarmScreen`) instead of start screen.
215+
216+
## Verification Matrix
217+
218+
### Bloc runtime scenarios
219+
220+
- Early start from `upcoming` transitions to active started flow and begins ticking.
221+
- Official start trigger does not push `/scheduleStart` again when early-start already active.
222+
- Re-emission of same schedule preserves active started state.
223+
- Finish clears timers, early-start session, and timed snapshot; state returns to `notExists`.
224+
- Fingerprint mismatch invalidates persisted state and resets to canonical preparation.
225+
226+
### Widget flow scenarios
111227

112-
The timer system requires no additional configuration and works automatically with:
228+
- 5-minute variant: `Start Preparing` enters alarm flow immediately.
229+
- 5-minute variant: `Start in 5 minutes` returns to home.
230+
- Entering alarm before official start shows early-start-ready UI (countdown and actions).
231+
- Completion dialog:
232+
`Finish Preparation` uses finish path, `Continue Preparing` keeps user in alarm.
233+
- Late vs non-late finish payload behavior is validated.
113234

114-
- Any `ScheduleWithPreparationEntity` that has a valid `scheduleTime`
115-
- Both upcoming and ongoing schedule states
116-
- All timezone-aware DateTime calculations
235+
### Persistence/resume scenarios
117236

118-
## 📊 Performance Considerations
237+
- Early start -> app restart -> restore and fast-forward timed preparation from snapshot metadata.
238+
- Schedule mutation (fingerprint change) prevents stale progress restoration and resets correctly.
119239

120-
- **Single Timer**: Only one timer runs at a time per bloc instance
121-
- **Minimal Memory Footprint**: Timers are created/destroyed as needed
122-
- **Precise Timing**: Uses Dart's native Timer for accurate scheduling
123-
- **Efficient Cleanup**: No lingering resources after bloc disposal
240+
### Boundary scenarios
124241

125-
## 🚀 Future Enhancements
242+
- Exact preparation start boundary transition.
243+
- Late entry catch-up into current step.
244+
- Very-late entry before `scheduleTime` with all steps effectively done.
245+
- Stale notification / ended schedule path redirects safely without stuck loading.
126246

127-
Potential improvements to consider:
247+
## Operational Notes
128248

129-
- Multiple concurrent schedule timers
130-
- Configurable timer precision (seconds vs milliseconds)
131-
- Timer persistence across app restarts
132-
- Integration with system-level scheduling APIs
249+
- Wiki source of truth is this repository `docs/` folder.
250+
- Diagram format is Mermaid for GitHub wiki compatibility.
251+
- Recommended publish flow:
252+
commit docs changes in this repo, then sync with subtree:
253+
`git subtree push --prefix=docs wiki master`

0 commit comments

Comments
 (0)