feat(1.5-p9): 건강 다짐 표시 색 — Quest.healthColorHex(기본 분홍=빈 값), 편집기 색 선택, 빌드 7

- targetColor 건강 분기: 빈 값=시스템 분홍(다크 적응), 지정 시 hex — 단일 지점이라 목표 탭·모음·일기·위젯·워치 자동 반영
- 편집기 건강 섹션 '다짐 색'(프리셋 12+피커), 저장 시 기본색은 ''로·행동/꼬리표 전환 시 '' 정규화
- 타임테이블 블록 색과 별개, 시드 걸음 다짐에 #3E7CB1(대비 렌더 검증+스키마 내보내기 트리거)
- QA: 일기 목표 카드에서 걸음=파랑·나머지=분홍 대비 실측, 편집기 렌더, 61건 ALL PASS, 3종 빌드 그린, l10n 0/0
- ⚠️ CloudKit 스키마 추가 — 콘솔 Deploy가 빌드 7 설치보다 선행 필수(CLAUDE.md·plan §12 후속 3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtZwRRjbtM9pZJDM4FkTq6
This commit is contained in:
songyc macbook 2026-08-23 11:18:57 +09:00
parent ac68fddb14
commit 8b1236d6a9
9 changed files with 102 additions and 20 deletions

File diff suppressed because one or more lines are too long

View File

@ -182,3 +182,10 @@
- **실기기 실측(사용자)**: 버블 메뉴 펼침 상태에서 각 원이 이웃 버블 방향으로 늘어나 찌그러짐("서로 인력을 가진 느낌"). 원인: 일기 버블 추가로 8개가 되며 이웃 가장자리 간격이 ~9.6pt로 줄었는데, 유리 합체 판정 거리 `glassBlendSpacing`이 7개 시절 기준 18pt 그대로라 **정지 상태에서도 이웃끼리 합체 판정에 걸림**(1.4까지 7개는 간격 ~20.7pt로 판정 밖 — "1.3까지는 문제없었다"는 증언과 일치). - **실기기 실측(사용자)**: 버블 메뉴 펼침 상태에서 각 원이 이웃 버블 방향으로 늘어나 찌그러짐("서로 인력을 가진 느낌"). 원인: 일기 버블 추가로 8개가 되며 이웃 가장자리 간격이 ~9.6pt로 줄었는데, 유리 합체 판정 거리 `glassBlendSpacing`이 7개 시절 기준 18pt 그대로라 **정지 상태에서도 이웃끼리 합체 판정에 걸림**(1.4까지 7개는 간격 ~20.7pt로 판정 밖 — "1.3까지는 문제없었다"는 증언과 일치).
- 수정: 18→6 (펼침 상태 정원 분리 확인 — 시각 QA. FAB에서 피어나는 모핑은 겹침 구간에서 일어나므로 유지). 마케팅 06-bubble ×3 재촬영(구 샷에 왜곡 찍혀 있었음), 코드 주석에 "탭 추가 시 간격 하한 재확인" 경고. - 수정: 18→6 (펼침 상태 정원 분리 확인 — 시각 QA. FAB에서 피어나는 모핑은 겹침 구간에서 일어나므로 유지). 마케팅 06-bubble ×3 재촬영(구 샷에 왜곡 찍혀 있었음), 코드 주석에 "탭 추가 시 간격 하한 재확인" 경고.
### §12 후속 3 — 건강 다짐 표시 색 (2026-08-23, 빌드 7)
- 사용자 요구: 건강 다짐이 표시 표면(목표 탭·모음 카드·위젯·다짐 현황)에서 고정 분홍 — 만들 때 색을 고를 수 있게(타임테이블 블록 색과 별개).
- 구현: **Quest 신규 필드 `healthColorHex`(기본 "")** + `targetColor` 건강 분기(빈 값=시스템 분홍 → 다크 적응 유지, 값 있으면 hex 고정 — 꼬리표 색과 같은 문법). 편집기 건강 섹션에 '다짐 색'(프리셋 12=AppTheme.tagPresets+컬러 피커), 저장 시 기본 분홍 그대로면 ""로 정규화, 행동·꼬리표 전환 시 "" 정리. targetColor가 단일 공급 지점이라 전 표면 자동 반영.
- ⚠️ **CloudKit 스키마 추가** — DiaryPageItem 전례에 따라 **배포(개발 내보내기→콘솔 Deploy)가 빌드 7 설치보다 선행**. 절차: 아리나 시뮬 내보내기 → 사용자 콘솔 Deploy → 그다음 업로드·설치. 구버전 기기는 분홍 폴백(표시 차이만 — 안전).
- 시드: -seedHealthQuest 걸음 다짐에 #3E7CB1 시드(신규·멱등 재실행 모두 — 렌더 검증+스키마 내보내기 트거 겸용). 도움말·whats-new 3언어 갱신, l10n 0/0.

View File

@ -601,7 +601,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements; CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -643,7 +643,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements; CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -686,7 +686,7 @@
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements"; CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -722,7 +722,7 @@
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements"; CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -758,7 +758,7 @@
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements; CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist; INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
@ -794,7 +794,7 @@
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements; CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist; INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
@ -827,7 +827,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements; CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_FILE = Widgets/Info.plist;
@ -853,7 +853,7 @@
buildSettings = { buildSettings = {
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements; CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6; CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 5GNS4Z2SYT; DEVELOPMENT_TEAM = 5GNS4Z2SYT;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_FILE = Widgets/Info.plist;

View File

@ -674,7 +674,16 @@ enum DebugSeed {
// (-seedDemo ) // (-seedDemo )
let title = String(localized: "건강 습관 만들기") let title = String(localized: "건강 습관 만들기")
let existing = (try? context.fetch(FetchDescriptor<Goal>())) ?? [] let existing = (try? context.fetch(FetchDescriptor<Goal>())) ?? []
guard !existing.contains(where: { $0.title == title }) else { return } if let seeded = existing.first(where: { $0.title == title }) {
// () : (1.5(7))
// + healthColorHex CloudKit ( )
if let steps = seeded.quests.first(where: { $0.healthMetricRaw == HealthMetric.steps.rawValue }),
steps.healthColorHex.isEmpty {
steps.healthColorHex = "#3E7CB1"
DataChange.commit(context: context)
}
return
}
let goal = Goal(title: title, symbolName: "heart.fill", colorHex: "#E0558C", let goal = Goal(title: title, symbolName: "heart.fill", colorHex: "#E0558C",
startDate: Calendar.current.date(byAdding: .day, value: -7, to: .now) ?? .now, startDate: Calendar.current.date(byAdding: .day, value: -7, to: .now) ?? .now,
endDate: nil) endDate: nil)
@ -689,6 +698,8 @@ enum DebugSeed {
context.insert(quest) context.insert(quest)
} }
addQuest(HealthMetric.steps.rawValue, count: 8000, isDuration: false) addQuest(HealthMetric.steps.rawValue, count: 8000, isDuration: false)
// ( / , 1.5(7))
goal.quests.first { $0.healthMetricRaw == HealthMetric.steps.rawValue }?.healthColorHex = "#3E7CB1"
addQuest(HealthMetric.sleep.rawValue, seconds: 8 * 3600, isDuration: true) addQuest(HealthMetric.sleep.rawValue, seconds: 8 * 3600, isDuration: true)
addQuest("workout.running", seconds: 30 * 60, isDuration: true) addQuest("workout.running", seconds: 30 * 60, isDuration: true)
addQuest(HealthMetric.water.rawValue, count: 2000, isDuration: false) addQuest(HealthMetric.water.rawValue, count: 2000, isDuration: false)

View File

@ -4835,6 +4835,22 @@
} }
} }
}, },
"다짐 색": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Quest color"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "クエストの色"
}
}
}
},
"다짐 수정": { "다짐 수정": {
"localizations": { "localizations": {
"en": { "en": {
@ -5059,18 +5075,18 @@
} }
} }
}, },
"다짐을 만들 때 대상에서 '건강'을 고르면 '매일 8,000보', '수면 8시간 이상', '달리기 30분'처럼 건강 데이터를 목표로 삼을 수 있어요. 걸음 수·운동 시간·수면·활동 에너지·이동 거리·일어서기·마음챙김·물 섭취를 고를 수 있고, 운동은 달리기·수영·근력 운동 같은 종목별로도 만들 수 있어요 — 종목별 시간은 건강 앱에 기록된 운동 기준이라 활동 링의 '운동 시간'과는 값이 다를 수 있어요. 건강 다짐은 하루 단위로만 반복돼요(요일 지정은 돼요).\n수면 다짐은 '수면 구간'(기본 저녁 9시~다음 날 아침 9시)을 정해 두면 그 안에서 잔 시간을 통째로 그날 것으로 세요 — 자정을 넘겨 자도 하루가 쪼개지지 않아요. 진행률·연속 달성·목표 판정은 다른 다짐과 똑같은 규칙으로 계산돼요. 건강 데이터가 없는 기기(맥 등)에서는 건강 다짐이 '데이터 없음'으로 보이고 목표 진행률에서 빠져요 — 아이폰에서 보면 정상으로 보여요.": { "다짐을 만들 때 대상에서 '건강'을 고르면 '매일 8,000보', '수면 8시간 이상', '달리기 30분'처럼 건강 데이터를 목표로 삼을 수 있어요. 걸음 수·운동 시간·수면·활동 에너지·이동 거리·일어서기·마음챙김·물 섭취를 고를 수 있고, 운동은 달리기·수영·근력 운동 같은 종목별로도 만들 수 있어요 — 종목별 시간은 건강 앱에 기록된 운동 기준이라 활동 링의 '운동 시간'과는 값이 다를 수 있어요. 건강 다짐은 하루 단위로만 반복돼요(요일 지정은 돼요). 목표 탭·위젯에서 이 다짐이 보일 색도 만들 때 직접 고를 수 있어요 — 기본은 분홍이고, 타임테이블의 수면·운동 블록 색과는 별개예요.\n수면 다짐은 '수면 구간'(기본 저녁 9시~다음 날 아침 9시)을 정해 두면 그 안에서 잔 시간을 통째로 그날 것으로 세요 — 자정을 넘겨 자도 하루가 쪼개지지 않아요. 진행률·연속 달성·목표 판정은 다른 다짐과 똑같은 규칙으로 계산돼요. 건강 데이터가 없는 기기(맥 등)에서는 건강 다짐이 '데이터 없음'으로 보이고 목표 진행률에서 빠져요 — 아이폰에서 보면 정상으로 보여요.": {
"localizations": { "localizations": {
"en": { "en": {
"stringUnit": { "stringUnit": {
"state": "translated", "state": "translated",
"value": "Pick Health as the target when creating a quest, and health data becomes your goal — \"8,000 steps a day,\" \"at least 8 hours of sleep,\" \"30 minutes of running.\" You can choose steps, exercise time, sleep, active energy, distance, stand hours, mindfulness or water intake, and workouts can be tracked by type, like running, swimming or strength training — type-based time comes from workouts logged in the Health app, so it can differ from the activity ring's Exercise Time. Health quests repeat daily only (weekday schedules are fine).\nFor a sleep quest, set a sleep window (9 PM to 9 AM the next morning by default) and everything you sleep within it counts as that day's sleep — crossing midnight doesn't split your night. Progress, streaks and goal judgment follow exactly the same rules as other quests. On devices without health data (like the Mac), health quests show \"No data\" and drop out of goal progress — they look normal again on your iPhone." "value": "Pick 'Health' as a quest's target to set goals from health data — '8,000 steps a day', 'at least 8 hours of sleep', '30 minutes of running'. You can choose steps, exercise time, sleep, active energy, distance, stand hours, mindful minutes or water intake, and workouts can be set by type, like running, swimming or strength training — type-based time comes from workouts logged in the Health app, so it can differ from the activity ring's 'exercise time'. Health quests repeat daily only (weekday schedules are fine). You can also pick the color the quest shows in the Goals tab and widgets — pink by default, and separate from the timetable's sleep and workout block colors.\nFor a sleep quest, set a sleep window (9 PM to 9 AM the next day by default) and everything you sleep within it counts as that day's — sleeping past midnight won't split your night. Progress, streaks and goal judgment follow the same rules as any other quest. On devices without health data (like the Mac), health quests show as 'no data' and drop out of goal progress — they look normal on your iPhone."
} }
}, },
"ja": { "ja": {
"stringUnit": { "stringUnit": {
"state": "translated", "state": "translated",
"value": "クエストを作るとき対象で「ヘルス」を選ぶと、「毎日8,000歩」「睡眠8時間以上」「ランニング30分」のようにヘルスデータを目標にできます。歩数・運動時間・睡眠・アクティブエネルギー・移動距離・スタンド時間・マインドフルネス・水分摂取から選べて、運動はランニング・水泳・筋力トレーニングのような種目別でも作れます — 種目別の時間はヘルスケアアプリに記録された運動が基準なので、アクティビティリングの「運動時間」とは値が異なることがあります。ヘルスクエストは1日単位でのみ繰り返されます曜日指定はできます。\n睡眠クエストは「睡眠時間帯」デフォルトは夜9時〜翌朝9時を決めておくと、その中で眠った時間をまるごとその日の分として数えます — 日付をまたいで眠っても1日が分割されません。進捗・連続達成・目標判定はほかのクエストとまったく同じルールで計算されます。ヘルスデータのない端末Macなどではヘルスクエストが「データなし」と表示され目標の進捗から外れます — iPhoneで見れば通常どおり表示されます。" "value": "クエスト対象で「ヘルス」を選ぶと、「毎日8,000歩」「睡眠8時間以上」「ランニング30分」のようにヘルスデータを目標にできます。歩数・運動時間・睡眠・アクティブエネルギー・移動距離・スタンド時間・マインドフル分・水分摂取から選べ、運動はランニング・水泳・筋力トレーニングな種目別でも作れます — 種目別の時間はヘルスケアAppに記録されたワークアウト基準なので、アクティビティリングの「エクササイズ時間」とは値が異なることがあります。ヘルスクエストは1日単位のみで繰り返します曜日指定はできます。目標タブやウィジェットでクエストが表示される色も作成時に選べます — 基本はピンクで、タイムテーブルの睡眠・運動ブロックの色とは別です。\n睡眠クエストは「睡眠時間帯」初期設定は夜9時〜翌朝9時を決めておくと、その中で眠った時間をまるごとその日のものとして数えます — 日付をまたいで眠っても1日が分割されません。進捗・継続・目標の判定は他のクエストと同じルールで計算されます。ヘルスデータのない端末Macなどではヘルスクエストが「データなし」と表示され目標の進捗から外れます — iPhoneで見れば正常に表示されます。"
} }
} }
} }

View File

@ -317,7 +317,7 @@ struct HelpView: View {
HelpTopic( HelpTopic(
symbol: "figure.run", symbol: "figure.run",
title: "건강 데이터로 다짐 만들기 (프리미엄)", title: "건강 데이터로 다짐 만들기 (프리미엄)",
body: "다짐을 만들 때 대상에서 '건강'을 고르면 '매일 8,000보', '수면 8시간 이상', '달리기 30분'처럼 건강 데이터를 목표로 삼을 수 있어요. 걸음 수·운동 시간·수면·활동 에너지·이동 거리·일어서기·마음챙김·물 섭취를 고를 수 있고, 운동은 달리기·수영·근력 운동 같은 종목별로도 만들 수 있어요 — 종목별 시간은 건강 앱에 기록된 운동 기준이라 활동 링의 '운동 시간'과는 값이 다를 수 있어요. 건강 다짐은 하루 단위로만 반복돼요(요일 지정은 돼요).\n수면 다짐은 '수면 구간'(기본 저녁 9시~다음 날 아침 9시)을 정해 두면 그 안에서 잔 시간을 통째로 그날 것으로 세요 — 자정을 넘겨 자도 하루가 쪼개지지 않아요. 진행률·연속 달성·목표 판정은 다른 다짐과 똑같은 규칙으로 계산돼요. 건강 데이터가 없는 기기(맥 등)에서는 건강 다짐이 '데이터 없음'으로 보이고 목표 진행률에서 빠져요 — 아이폰에서 보면 정상으로 보여요." body: "다짐을 만들 때 대상에서 '건강'을 고르면 '매일 8,000보', '수면 8시간 이상', '달리기 30분'처럼 건강 데이터를 목표로 삼을 수 있어요. 걸음 수·운동 시간·수면·활동 에너지·이동 거리·일어서기·마음챙김·물 섭취를 고를 수 있고, 운동은 달리기·수영·근력 운동 같은 종목별로도 만들 수 있어요 — 종목별 시간은 건강 앱에 기록된 운동 기준이라 활동 링의 '운동 시간'과는 값이 다를 수 있어요. 건강 다짐은 하루 단위로만 반복돼요(요일 지정은 돼요). 목표 탭·위젯에서 이 다짐이 보일 색도 만들 때 직접 고를 수 있어요 — 기본은 분홍이고, 타임테이블의 수면·운동 블록 색과는 별개예요.\n수면 다짐은 '수면 구간'(기본 저녁 9시~다음 날 아침 9시)을 정해 두면 그 안에서 잔 시간을 통째로 그날 것으로 세요 — 자정을 넘겨 자도 하루가 쪼개지지 않아요. 진행률·연속 달성·목표 판정은 다른 다짐과 똑같은 규칙으로 계산돼요. 건강 데이터가 없는 기기(맥 등)에서는 건강 다짐이 '데이터 없음'으로 보이고 목표 진행률에서 빠져요 — 아이폰에서 보면 정상으로 보여요."
), ),
]) ])
} }

View File

@ -63,6 +63,9 @@ struct QuestEditorView: View {
to: Calendar.current.startOfDay(for: .now) to: Calendar.current.startOfDay(for: .now)
) ?? .now ) ?? .now
}() }()
/// (1.5(7)) (.pink).
/// / (save() )
@State private var healthColor: Color = .pink
/// ( / kcal / ) /// ( / kcal / )
@State private var healthIntTarget = 8000 @State private var healthIntTarget = 8000
/// (km m) /// (km m)
@ -182,6 +185,34 @@ struct QuestEditorView: View {
components: .hourAndMinute components: .hourAndMinute
) )
} }
// (1.5(7)) · ·· .
// · ,
// · ( )
ColorPicker(selection: $healthColor, supportsOpacity: false) {
Text("다짐 색")
}
LazyVGrid(columns: [GridItem(.adaptive(minimum: 40), spacing: 10)], spacing: 10) {
ForEach(Array(AppTheme.tagPresets.enumerated()), id: \.offset) { index, hex in
Button {
healthColor = Color(hex: hex)
} label: {
Circle()
.fill(Color(hex: hex))
.frame(width: 36, height: 36)
.overlay {
if healthColor.hexString == hex {
Image(systemName: "checkmark")
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.white)
}
}
}
.buttonStyle(.plain)
.accessibilityLabel(String(localized: "미리 설정된 색 \(index + 1)"))
.accessibilityAddTraits(healthColor.hexString == hex ? .isSelected : [])
}
}
.padding(.vertical, 4)
} }
case .action: case .action:
if allActions.isEmpty { if allActions.isEmpty {
@ -503,6 +534,9 @@ struct QuestEditorView: View {
let dayStart = cal.startOfDay(for: .now) let dayStart = cal.startOfDay(for: .now)
sleepStart = cal.date(byAdding: .minute, value: quest.sleepWindowStartMinutes, to: dayStart) ?? sleepStart sleepStart = cal.date(byAdding: .minute, value: quest.sleepWindowStartMinutes, to: dayStart) ?? sleepStart
sleepEnd = cal.date(byAdding: .minute, value: quest.sleepWindowEndMinutes, to: dayStart) ?? sleepEnd sleepEnd = cal.date(byAdding: .minute, value: quest.sleepWindowEndMinutes, to: dayStart) ?? sleepEnd
if !quest.healthColorHex.isEmpty {
healthColor = Color(hex: quest.healthColorHex)
}
} }
measure = quest.measure measure = quest.measure
period = quest.period period = quest.period
@ -533,11 +567,13 @@ struct QuestEditorView: View {
target.targetTag = nil target.targetTag = nil
target.measure = selectedAction?.trackingType ?? .time target.measure = selectedAction?.trackingType ?? .time
target.healthMetricRaw = "" // (plan §3.4) target.healthMetricRaw = "" // (plan §3.4)
target.healthColorHex = ""
case .tag: case .tag:
target.targetTag = selectedTag target.targetTag = selectedTag
target.targetAction = nil target.targetAction = nil
target.measure = measure target.measure = measure
target.healthMetricRaw = "" target.healthMetricRaw = ""
target.healthColorHex = ""
case .health: case .health:
target.targetAction = nil target.targetAction = nil
target.targetTag = nil target.targetTag = nil
@ -559,6 +595,10 @@ struct QuestEditorView: View {
let endComps = cal.dateComponents([.hour, .minute], from: sleepEnd) let endComps = cal.dateComponents([.hour, .minute], from: sleepEnd)
target.sleepWindowStartMinutes = (startComps.hour ?? 21) * 60 + (startComps.minute ?? 0) target.sleepWindowStartMinutes = (startComps.hour ?? 21) * 60 + (startComps.minute ?? 0)
target.sleepWindowEndMinutes = (endComps.hour ?? 9) * 60 + (endComps.minute ?? 0) target.sleepWindowEndMinutes = (endComps.hour ?? 9) * 60 + (endComps.minute ?? 0)
// ( ),
// hex (1.5(7))
target.healthColorHex =
healthColor.hexString == Color.pink.hexString ? "" : healthColor.hexString
period = .daily // ( UI ) period = .daily // ( UI )
} }
target.period = period target.period = period

View File

@ -5,7 +5,7 @@
● 애플 건강과 연결 ● 애플 건강과 연결
• 모음 탭에서 걸음·운동·수면 같은 오늘의 건강 데이터를 타일로 볼 수 있어요 — 값은 기기 밖으로 나가지 않아요 • 모음 탭에서 걸음·운동·수면 같은 오늘의 건강 데이터를 타일로 볼 수 있어요 — 값은 기기 밖으로 나가지 않아요
• 건강 데이터로 다짐을 만들 수 있어요(프리미엄) — '매일 8,000보', '수면 8시간', '달리기 30분'처럼요. 운동은 종목별로도 • 건강 데이터로 다짐을 만들 수 있어요(프리미엄) — '매일 8,000보', '수면 8시간', '달리기 30분'처럼요. 운동은 종목별로도 되고, 다짐이 보일 색도 고를 수 있어
• 수면은 '수면 구간'을 정해 두면 자정을 넘겨 자도 하루가 쪼개지지 않아요. 낮에 자는 날이 있다면 '수면 표시 구간'을 요일별로 맞출 수도 있어요 • 수면은 '수면 구간'을 정해 두면 자정을 넘겨 자도 하루가 쪼개지지 않아요. 낮에 자는 날이 있다면 '수면 표시 구간'을 요일별로 맞출 수도 있어요
● 일기가 더 넓어졌어요 (프리미엄) ● 일기가 더 넓어졌어요 (프리미엄)
@ -26,7 +26,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A
● Apple Health, connected ● Apple Health, connected
• See today's health data — steps, exercise, sleep — as tiles on the Home tab. Values never leave your device • See today's health data — steps, exercise, sleep — as tiles on the Home tab. Values never leave your device
• Build quests from health data (Premium): "8,000 steps a day," "8 hours of sleep," "30 minutes of running" — workouts by type, too • Build quests from health data (Premium): "8,000 steps a day," "8 hours of sleep," "30 minutes of running" — workouts by type, and pick the quest's color too
• Set a sleep window and sleeping past midnight won't split your night. If some days you sleep in the daytime, tune the display window per weekday • Set a sleep window and sleeping past midnight won't split your night. If some days you sleep in the daytime, tune the display window per weekday
● A bigger Diary (Premium) ● A bigger Diary (Premium)
@ -47,7 +47,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A
● Appleヘルスケアと連携 ● Appleヘルスケアと連携
• ホームタブで歩数・運動・睡眠など今日のヘルスデータをタイルで見られます — 値が端末の外に出ることはありません • ホームタブで歩数・運動・睡眠など今日のヘルスデータをタイルで見られます — 値が端末の外に出ることはありません
• ヘルスデータでクエストを作れますプレミアム—「毎日8,000歩」「睡眠8時間」「ランニング30分」など。運動は種目別でもOK • ヘルスデータでクエストを作れますプレミアム—「毎日8,000歩」「睡眠8時間」「ランニング30分」など。運動は種目別でもOK、クエストの色も選べます
• 「睡眠時間帯」を決めておけば、日付をまたいで眠っても1日が分割されません。昼間に眠る日があるなら、表示の時間帯を曜日ごとに調整できます • 「睡眠時間帯」を決めておけば、日付をまたいで眠っても1日が分割されません。昼間に眠る日があるなら、表示の時間帯を曜日ごとに調整できます
● 日記がもっと広く(プレミアム) ● 日記がもっと広く(プレミアム)

View File

@ -396,6 +396,10 @@ final class Quest {
/// '' ( plan §3.4) /// '' ( plan §3.4)
var sleepWindowStartMinutes: Int = 1260 var sleepWindowStartMinutes: Int = 1260
var sleepWindowEndMinutes: Int = 540 var sleepWindowEndMinutes: Int = 540
/// "#RRGGBB" (1.5(7)) = (). ·
/// ( ). = CloudKit
/// ( Deploy) (§3, plan §12)
var healthColorHex: String = ""
/// ///
var sortOrder: Int = 0 var sortOrder: Int = 0
@ -452,7 +456,11 @@ extension Quest {
var targetColor: Color { var targetColor: Color {
if let targetAction { return targetAction.color } if let targetAction { return targetAction.color }
if let targetTag { return targetTag.color } if let targetTag { return targetTag.color }
if healthTarget != nil { return .pink } // : (1.5(7)),
// · ···
if healthTarget != nil {
return healthColorHex.isEmpty ? .pink : Color(hex: healthColorHex)
}
return AppTheme.green return AppTheme.green
} }