114 lines
3.3 KiB
Markdown
114 lines
3.3 KiB
Markdown
# TT-005 — Riverpod Providers & Notifiers
|
|
|
|
**Type:** Story
|
|
**Priority:** High
|
|
**Labels:** state-management, riverpod
|
|
**Depends on:** TT-003, TT-004
|
|
**Blocks:** TT-006, TT-007, TT-008, TT-009
|
|
|
|
---
|
|
|
|
## Summary
|
|
Implement all Riverpod providers and notifiers for each feature. This is the
|
|
domain/application layer that connects the data layer (repositories) to the
|
|
presentation layer (screens/widgets).
|
|
|
|
## Providers to implement
|
|
|
|
### Timer Feature
|
|
File: `lib/features/timer/domain/timer_notifier.dart`
|
|
|
|
```dart
|
|
@riverpod
|
|
class TimerNotifier extends _$TimerNotifier {
|
|
@override
|
|
TimerState build(); // reads DB for open entry on init
|
|
|
|
Future<void> start(Project project, {String? note});
|
|
Future<void> stop();
|
|
Future<void> discard();
|
|
void updateNote(String note);
|
|
}
|
|
```
|
|
- Uses a `dart:async Timer.periodic(1s)` while running
|
|
- On `build()`: calls `timeEntriesRepository.getActiveEntry()` to restore state
|
|
- See `.ai/features/timer.md` for full state-machine spec
|
|
|
|
### Projects Feature
|
|
File: `lib/features/projects/domain/projects_provider.dart`
|
|
|
|
```dart
|
|
@riverpod
|
|
Stream<List<Project>> activeProjects(ActiveProjectsRef ref);
|
|
|
|
@riverpod
|
|
Stream<List<Project>> allProjects(AllProjectsRef ref);
|
|
|
|
@riverpod
|
|
class ProjectsNotifier extends _$ProjectsNotifier {
|
|
Future<void> create({required String name, required int colorValue, String? description});
|
|
Future<void> update(Project project);
|
|
Future<void> archive(int id);
|
|
Future<void> delete(int id);
|
|
}
|
|
```
|
|
|
|
### Entries Feature
|
|
File: `lib/features/entries/domain/entries_provider.dart`
|
|
|
|
```dart
|
|
@riverpod
|
|
Stream<List<TimeEntry>> entriesByDateRange(
|
|
EntriesByDateRangeRef ref, {required DateTime from, required DateTime to});
|
|
|
|
@riverpod
|
|
class EntriesNotifier extends _$EntriesNotifier {
|
|
Future<void> create(TimeEntry entry);
|
|
Future<void> update(TimeEntry entry);
|
|
Future<void> delete(int id);
|
|
}
|
|
```
|
|
|
|
### Reports Feature
|
|
File: `lib/features/reports/domain/reports_provider.dart`
|
|
|
|
```dart
|
|
@riverpod
|
|
class ReportsNotifier extends _$ReportsNotifier {
|
|
@override
|
|
ReportData build();
|
|
|
|
void selectPeriod(ReportPeriod period); // day | week | month
|
|
void goToPrevious();
|
|
void goToNext();
|
|
void filterByProject(int? projectId);
|
|
}
|
|
|
|
@freezed
|
|
class ReportData with _$ReportData {
|
|
const factory ReportData({
|
|
required Duration totalDuration,
|
|
required Map<String, Duration> durationByProject,
|
|
required List<double> chartValues, // bars for fl_chart
|
|
required DateTime periodStart,
|
|
required DateTime periodEnd,
|
|
}) = _ReportData;
|
|
}
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
- [ ] All providers use `@riverpod` annotation (code-gen)
|
|
- [ ] `TimerNotifier.build()` restores running timer from DB on app start
|
|
- [ ] `TimerNotifier` disposes its `dart:async Timer` via `ref.onDispose`
|
|
- [ ] All providers inject repositories via `ref.watch(xxxRepositoryProvider)`
|
|
- [ ] Unit tests for `TimerNotifier` (start/stop/discard/restore)
|
|
- [ ] Unit tests for `ProjectsNotifier` (CRUD operations)
|
|
|
|
## Files to create
|
|
- `lib/features/timer/domain/timer_notifier.dart`
|
|
- `lib/features/projects/domain/projects_provider.dart`
|
|
- `lib/features/entries/domain/entries_provider.dart`
|
|
- `lib/features/reports/domain/reports_provider.dart`
|
|
- `lib/features/reports/domain/report_data.dart`
|
|
- `test/features/timer/timer_notifier_test.dart`
|
|
- `test/features/projects/projects_notifier_test.dart`
|