[03-Developer] #226 Catalog Gallery 구현

- Drift schema v2: Protocols.category CHECK 6→7 (light_circadian/sleep/movement/
  nutrition/focus_cognition/recovery_stress/emotion_relationship). schemaVersion
  1→2 + onUpgrade migrateV1ToV2 (DROP+CREATE+reseed flag 클리어).
- protocols.json 34 항목 v2 재분류 (1차 효과 기준). emotion_relationship 0 매핑.
- 도메인: DisplayCategory enum (8) + CatalogItem sealed (Protocol/Break/Diet).
- 데이터: CatalogRepository.all/byId/referencesByIds (3 source 통합).
- 상태: catalog_providers.dart (catalogItems / groupedByCategory / refsByIds).
- UI: ProtocolGalleryScreen (카테고리 칩 + 카드 그리드) + ProtocolPreviewScreen
  (모든 필드 + reference 펼치기 + "내 습관으로" disabled placeholder) +
  CatalogCard / CategoryChipRow / ReferenceExpandCard. HabitListScreen 빈
  상태 CTA + AppBar 액션.
- 테스트: migration_v1_to_v2 3건 + display_category 5건 + catalog_repository
  9건 + gallery widget 3건 + preview widget 3건 = 23 신규. 기존 88 회귀 0,
  flutter analyze 0 issues. 110 passed / 1 skipped.

설계서: docs/design/226-catalog-gallery/{README, fn-catalog_repository,
fn-migration_v1_to_v2}.md + ADR-0004.

Refs #226
This commit is contained in:
2026-06-12 17:20:13 +09:00
parent 4665f06a94
commit 321d3af53b
24 changed files with 1814 additions and 146 deletions

View File

@@ -5,6 +5,7 @@ import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../../core/constants.dart';
import 'tables/catalog_tables.dart';
import 'tables/user_tables.dart';
@@ -42,7 +43,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.memory() : super(NativeDatabase.memory());
@override
int get schemaVersion => 1;
int get schemaVersion => 2;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -51,8 +52,16 @@ class AppDatabase extends _$AppDatabase {
await _createIndexes(m);
},
onUpgrade: (m, from, to) async {
// Phase 1 only has v1. Reaching here is a bug.
assert(false, 'Phase 1 has no upgrade path. from=$from to=$to');
// v1 → v2 (#226): Protocols.category CHECK 6 → 7 신 카테고리.
// Read-only catalog 의 첫 마이그레이션. DROP + reseed 패턴 (ADR-0004).
// user 테이블 (Habits, Phases, ...) 무변화.
if (from == 1 && to >= 2) {
await migrateV1ToV2(m, this);
}
if (from > to || to > schemaVersion) {
assert(false,
'Unknown upgrade path: from=$from to=$to (schemaVersion=$schemaVersion)');
}
},
);
@@ -142,3 +151,18 @@ Future<File> appDatabaseFile() async {
final dir = await getApplicationDocumentsDirectory();
return File(p.join(dir.path, 'life_helper.sqlite'));
}
/// v1 → v2 마이그레이션. fn-migration_v1_to_v2.md 참고.
///
/// - Protocols 테이블 DROP + CREATE (v2 CHECK 적용) + 인덱스 재생성.
/// - `kSeededV1Flag` 클리어 → 다음 부팅이 SeedImporter.importIfNeeded() 호출 → 새 JSON 재시드.
/// - user 테이블 (Habits, Phases, TrackerEntries, ...) 무변화.
///
/// `onUpgrade` 에서 dispatch. 테스트는 직접 호출.
Future<void> migrateV1ToV2(Migrator m, AppDatabase db) async {
await m.deleteTable(db.protocols.actualTableName);
await m.createTable(db.protocols);
await m.createIndex(Index('IDX_protocols_category',
'CREATE INDEX IDX_protocols_category ON protocols(category)'));
await (db.delete(db.metaKv)..where((t) => t.key.equals(kSeededV1Flag))).go();
}