49 lines
1.7 KiB
Markdown
49 lines
1.7 KiB
Markdown
# Feature: Timer
|
|
|
|
## Behaviour
|
|
- Only **one** active timer at a time.
|
|
- Starting a new timer while one is running automatically stops the previous one
|
|
and saves the completed `TimeEntry`.
|
|
- Timer state persists across app restarts: on launch, read `time_entries`
|
|
where `end_time IS NULL` — if found, restore the running timer.
|
|
|
|
## State Machine
|
|
```
|
|
IDLE ──start()──► RUNNING ──stop()──► IDLE
|
|
│
|
|
discard()
|
|
│
|
|
IDLE
|
|
```
|
|
|
|
## TimerState (freezed union)
|
|
```dart
|
|
@freezed
|
|
class TimerState with _$TimerState {
|
|
const factory TimerState.idle() = TimerIdle;
|
|
const factory TimerState.running({
|
|
required int entryId,
|
|
required DateTime startTime,
|
|
required Project project,
|
|
String? note,
|
|
}) = TimerRunning;
|
|
}
|
|
```
|
|
|
|
## TimerNotifier responsibilities
|
|
1. `start(Project project)` — create TimeEntry (end_time = null), emit running
|
|
2. `stop()` — update TimeEntry (set end_time, compute duration_s), emit idle
|
|
3. `discard()` — delete the active TimeEntry, emit idle
|
|
4. `updateNote(String note)` — update note on active entry
|
|
5. `restoreFromDb()` — called on app init; check for open entry
|
|
|
|
## Elapsed Time Display
|
|
- Use a `Timer.periodic(1 second)` inside the notifier while running.
|
|
- Dispose the periodic timer on `stop()` / `discard()`.
|
|
- Widget reads `DateTime.now().difference(startTime)` for display.
|
|
|
|
## No background service (v1)
|
|
- Timer only ticks while app is in foreground.
|
|
- If app is killed mid-session, the entry remains open (end_time = null).
|
|
- On next launch, `restoreFromDb()` re-attaches to the open entry.
|
|
- Future v2: consider `flutter_foreground_task` for background timer.
|