- HealthMetric 8종(표준 단위·표기) + HealthStore(HK 조회: 누적 통계·스탠드·마음챙김· 수면 구간[합집합 병합, 깨어난 날 귀속 — plan §3.4 규칙]) + HealthCache(App Group, 지표×dayKey, 400일 보존 — 5.1.3 준수: CloudKit 비저장) - 모음 탭 건강 섹션(타일 줄+접기·연결 CTA[명시적 탭 권한]·안내 시트[사용자 요구 가이드]· 지표 선택 시트[체크+드래그 순서]) — actionGrid·레이아웃 금지구역 무접촉, 편집 모드 숨김 - 3섹션(즐겨찾기·나머지·건강) 순서 렌더러 + 설정 탭 표시 토글·순서 화면(기기 로컬) - HealthKit 엔타이틀먼트(iOS 앱만)·NSHealthShareUsageDescription, 전체 초기화에 건강 키·캐시 청소 추가 - 검증: Debug/Store 빌드, 자가 검증 106건 ALL PASS(26.5+18.5), 시각 QA 7장 (타일·순서·설정·안내·지표·CTA·18.5 빈 상태). 신규 인자 3종 §14 기록 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WtZwRRjbtM9pZJDM4FkTq6
82 lines
3.3 KiB
Swift
82 lines
3.3 KiB
Swift
//
|
|
// HealthMetric.swift
|
|
// Haru_Danim
|
|
//
|
|
// 건강 지표 정의 (1.5 — Docs/plan-1.5.md §3)
|
|
// - 모음 탭 타일·건강 다짐이 공유하는 순수 값 타입 (HealthKit 비의존 — 위젯 확장도 컴파일)
|
|
// - 값의 표준 단위: 시간류(운동·수면·마음챙김)=초, 걸음=보, 에너지=kcal,
|
|
// 거리=m, 스탠드=시간, 물=L. HealthKit 조회·캐시·표기가 전부 이 단위를 쓴다
|
|
//
|
|
|
|
import Foundation
|
|
|
|
nonisolated enum HealthMetric: String, CaseIterable, Identifiable, Codable {
|
|
case steps, exerciseMinutes, sleep, activeEnergy, distance, standHours, mindfulMinutes, water
|
|
|
|
var id: String { rawValue }
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .steps: return String(localized: "걸음 수")
|
|
case .exerciseMinutes: return String(localized: "운동 시간")
|
|
case .sleep: return String(localized: "수면")
|
|
case .activeEnergy: return String(localized: "활동 에너지")
|
|
case .distance: return String(localized: "이동 거리")
|
|
case .standHours: return String(localized: "일어서기")
|
|
case .mindfulMinutes: return String(localized: "마음챙김")
|
|
case .water: return String(localized: "물 섭취")
|
|
}
|
|
}
|
|
|
|
var symbolName: String {
|
|
switch self {
|
|
case .steps: return "figure.walk"
|
|
case .exerciseMinutes: return "figure.run"
|
|
case .sleep: return "bed.double.fill"
|
|
case .activeEnergy: return "flame.fill"
|
|
case .distance: return "map.fill"
|
|
case .standHours: return "figure.stand"
|
|
case .mindfulMinutes: return "brain.head.profile"
|
|
case .water: return "drop.fill"
|
|
}
|
|
}
|
|
|
|
/// 시간류(초 단위) 지표 여부 — 표기·다짐 목표량 입력 방식 분기용
|
|
var isDuration: Bool {
|
|
switch self {
|
|
case .exerciseMinutes, .sleep, .mindfulMinutes: return true
|
|
default: return false
|
|
}
|
|
}
|
|
|
|
/// 표준 단위 값 → 사용자 표기 (타일·다짐·위젯 공통)
|
|
func valueLabel(_ value: Double) -> String {
|
|
switch self {
|
|
case .steps:
|
|
return String(localized: "\(Int(value).formatted())보")
|
|
case .exerciseMinutes, .sleep, .mindfulMinutes:
|
|
return Format.durationShort(value)
|
|
case .activeEnergy:
|
|
return String(localized: "\(Int(value).formatted())kcal")
|
|
case .distance:
|
|
let km = value / 1000
|
|
let text = km.formatted(.number.precision(.fractionLength(km >= 10 ? 0 : 1)))
|
|
return String(localized: "\(text)km")
|
|
case .standHours:
|
|
return String(localized: "\(Int(value))시간")
|
|
case .water:
|
|
let text = value.formatted(.number.precision(.fractionLength(0...2)))
|
|
return String(localized: "\(text)L")
|
|
}
|
|
}
|
|
|
|
/// 타일 기본 표시 세트 (걸음·운동·수면 — 사용자 선택으로 확장)
|
|
static let defaultTileMetrics: [HealthMetric] = [.steps, .exerciseMinutes, .sleep]
|
|
|
|
/// 저장된 선택(순서 포함) 해석 — 비어 있으면 기본 세트
|
|
static func selectedList(raw: String) -> [HealthMetric] {
|
|
let parsed = raw.split(separator: ",").compactMap { HealthMetric(rawValue: String($0)) }
|
|
return parsed.isEmpty ? defaultTileMetrics : parsed
|
|
}
|
|
}
|