[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:
166
app/lib/domain/catalog/catalog_item.dart
Normal file
166
app/lib/domain/catalog/catalog_item.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'display_category.dart';
|
||||
|
||||
/// 갤러리 UI 가 소비하는 통합 카탈로그 항목.
|
||||
///
|
||||
/// 3 source (ProtocolsTable / BreakProtocolsTable / DietPatternsTable) 를
|
||||
/// 단일 sealed 계층으로 통합. 카드/필터링은 공통 필드만 보면 충분.
|
||||
sealed class CatalogItem {
|
||||
String get id;
|
||||
String get title;
|
||||
String? get titleEn;
|
||||
|
||||
/// 카드용 1줄 요약 (≤ 60자).
|
||||
String get summary;
|
||||
|
||||
DisplayCategory get displayCategory;
|
||||
|
||||
/// 'strong_rct' / 'meta_analysis' / 'observational' / 'mechanistic' / 'expert_opinion' / null.
|
||||
/// DietPattern 은 'strong'/'moderate'/'mixed'/'weak'.
|
||||
String? get evidenceStrength;
|
||||
|
||||
List<String> get referenceIds;
|
||||
}
|
||||
|
||||
/// Protocols 테이블 1:1 매핑.
|
||||
final class ProtocolCatalogItem implements CatalogItem {
|
||||
ProtocolCatalogItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.titleEn,
|
||||
required this.summary,
|
||||
required this.displayCategory,
|
||||
required this.evidenceStrength,
|
||||
required this.referenceIds,
|
||||
required this.what,
|
||||
required this.whenText,
|
||||
required this.dose,
|
||||
required this.why,
|
||||
required this.how,
|
||||
required this.checkText,
|
||||
required this.caution,
|
||||
required this.defaultAnchor,
|
||||
required this.minDoseForStart,
|
||||
required this.sourceDoc,
|
||||
});
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final String? titleEn;
|
||||
@override
|
||||
final String summary;
|
||||
@override
|
||||
final DisplayCategory displayCategory;
|
||||
@override
|
||||
final String? evidenceStrength;
|
||||
@override
|
||||
final List<String> referenceIds;
|
||||
|
||||
final String what;
|
||||
final String whenText;
|
||||
final String dose;
|
||||
final String why;
|
||||
final List<String> how;
|
||||
final String checkText;
|
||||
final String? caution;
|
||||
final Map<String, dynamic>? defaultAnchor;
|
||||
final String? minDoseForStart;
|
||||
final String? sourceDoc;
|
||||
}
|
||||
|
||||
/// BreakProtocols 테이블 1:1 매핑. displayCategory 는 항상 breakHabit.
|
||||
final class BreakCatalogItem implements CatalogItem {
|
||||
BreakCatalogItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.titleEn,
|
||||
required this.summary,
|
||||
required this.evidenceStrength,
|
||||
required this.referenceIds,
|
||||
required this.breakCategory,
|
||||
required this.hubermanSummary,
|
||||
required this.phases,
|
||||
required this.defaultCommonFrames,
|
||||
required this.tools,
|
||||
required this.medicalWarning,
|
||||
});
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final String? titleEn;
|
||||
@override
|
||||
final String summary;
|
||||
@override
|
||||
DisplayCategory get displayCategory => DisplayCategory.breakHabit;
|
||||
@override
|
||||
final String? evidenceStrength;
|
||||
@override
|
||||
final List<String> referenceIds;
|
||||
|
||||
/// 원본 break 카테고리 (alcohol / nicotine / ...). 카드 sub-tag 로 활용.
|
||||
final String breakCategory;
|
||||
final String hubermanSummary;
|
||||
final List<String> phases;
|
||||
final List<String> defaultCommonFrames;
|
||||
final List<String> tools;
|
||||
final String? medicalWarning;
|
||||
}
|
||||
|
||||
/// DietPatterns 테이블 1:1 매핑. displayCategory 는 항상 nutrition.
|
||||
final class DietCatalogItem implements CatalogItem {
|
||||
DietCatalogItem({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.titleEn,
|
||||
required this.summary,
|
||||
required this.evidenceStrength,
|
||||
required this.referenceIds,
|
||||
required this.name,
|
||||
required this.core,
|
||||
required this.strengths,
|
||||
required this.weaknesses,
|
||||
required this.koreanContextFit,
|
||||
required this.starterLevers,
|
||||
required this.medicalWarning,
|
||||
required this.linkedProtocolIds,
|
||||
});
|
||||
|
||||
@override
|
||||
final String id;
|
||||
@override
|
||||
final String title;
|
||||
@override
|
||||
final String? titleEn;
|
||||
@override
|
||||
final String summary;
|
||||
@override
|
||||
DisplayCategory get displayCategory => DisplayCategory.nutrition;
|
||||
@override
|
||||
final String? evidenceStrength;
|
||||
@override
|
||||
final List<String> referenceIds;
|
||||
|
||||
final String name;
|
||||
final String core;
|
||||
final List<String> strengths;
|
||||
final List<String> weaknesses;
|
||||
final String? koreanContextFit;
|
||||
final List<String> starterLevers;
|
||||
final String? medicalWarning;
|
||||
final List<String> linkedProtocolIds;
|
||||
}
|
||||
|
||||
/// 그룹핑 헬퍼. 빈 카테고리 키는 결과 map 에 미포함.
|
||||
Map<DisplayCategory, List<CatalogItem>> groupByCategory(
|
||||
List<CatalogItem> items) {
|
||||
final result = <DisplayCategory, List<CatalogItem>>{};
|
||||
for (final item in items) {
|
||||
result.putIfAbsent(item.displayCategory, () => []).add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user