Files
life-helper/app/lib/state/providers.dart
joungmin 8fe6a8f378 [Developer] #204 Phase 1 MVP — Flutter app skeleton complete
- Drift 21 tables (8 catalog + 11 user + habit_dose_variants + meta_kv)
  with R1~R10 CHECK constraints and 19 indexes
- 8 hand-crafted seed JSON catalogs in app/assets/seed/
  (refs 84, protocols 34, methodologies 21, frame_patterns 30,
   reward_menu_items 30, break_protocols 8, common_frames 5, diet_patterns 5)
- 6 domain functions: recommend_variant, compute_streak,
  validate_frame_level, active_habit_quota, weekly_minimum_ratio,
  seed_importer (transactional, idempotent)
- 4 vertical-slice Riverpod screens: HabitList, HabitCreate, CheckIn, Streak
- 31 unit tests passing; flutter analyze clean
- OQ-5 streak semantics: missing entry ≠ explicit blank
  (missing = end of history; only TrackerValue.blank triggers Never-miss-twice)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 10:33:03 +09:00

66 lines
2.2 KiB
Dart

import 'package:drift/drift.dart' as drift;
import 'package:drift/native.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/constants.dart';
import '../core/time.dart';
import '../data/db/app_database.dart';
import '../data/db/daos/habit_dao.dart';
import '../data/db/daos/meta_dao.dart';
import '../data/db/daos/tracker_dao.dart';
import '../data/seed/seed_importer.dart';
/// Override in tests with an in-memory database.
final appDatabaseProvider = Provider<AppDatabase>((ref) {
throw UnimplementedError('appDatabaseProvider must be overridden in main()');
});
Future<AppDatabase> openProductionDatabase() async {
final file = await appDatabaseFile();
return AppDatabase(NativeDatabase.createInBackground(file));
}
final habitDaoProvider = Provider<HabitDao>((ref) {
return HabitDao(ref.watch(appDatabaseProvider));
});
final trackerDaoProvider = Provider<TrackerDao>((ref) {
return TrackerDao(ref.watch(appDatabaseProvider));
});
final metaDaoProvider = Provider<MetaDao>((ref) {
return MetaDao(ref.watch(appDatabaseProvider));
});
/// One-time bootstrap: ensure default user row + seed catalogs.
final bootstrapProvider = FutureProvider<void>((ref) async {
final db = ref.watch(appDatabaseProvider);
// Ensure default user.
final existing = await (db.select(db.users)
..where((t) => t.id.equals(kLocalDefaultUserId)))
.getSingleOrNull();
if (existing == null) {
await db.into(db.users).insert(UsersCompanion.insert(
id: kLocalDefaultUserId,
displayName: const drift.Value('You'),
createdAt: nowKst().toIso8601String(),
));
}
// Seed catalogs (idempotent).
await SeedImporter(db).importIfNeeded();
if (kDebugMode) {
debugPrint('bootstrap done');
}
});
/// Active habits stream for current user.
final activeHabitsProvider = StreamProvider<List<Habit>>((ref) {
final db = ref.watch(appDatabaseProvider);
return (db.select(db.habits)
..where((t) => t.userId.equals(kLocalDefaultUserId))
..where((t) => t.status.equals('active'))
..orderBy([(t) => drift.OrderingTerm.asc(t.startedAt)]))
.watch();
});