fix+feat(1.5-p7): 소킹 실측 2건 수정 + 추가 2건 — 빌드 5
- fix: 더보기 탭 일기 달력 날짜 탭 무반응 — morePath [AppTab]→NavigationPath(Date 푸시 가능), 회귀 인자 -morePushDiaryToday(착지 실측) - fix: 수면 블록이 수면 단계별로 칸칸이 갈라짐 — 10분 이하 깸 병합(sleepDisplayMergeGap, 표시 전용·값 무영향) - feat: 기록 탭 타임테이블(하루·주간)에 수면·운동 블록 — 단일 공급 지점·숨김 토글 공유, 내보내기 injectingHealthBlocks(forDayKeys:)+trim 확장 - feat: 타임테이블 표시 색 설정(설정→건강 데이터 — 기본 인디고·주황, 전 타임테이블·내보내기 공통), -healthColorPreview - QA: 주간 6일 블록 렌더·커스텀 색 픽셀 검증(#FF2D55→(255,221,228))·색 설정 화면·Date 푸시 착지, l10n 0/0, Debug/Store 그린 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WtZwRRjbtM9pZJDM4FkTq6
This commit is contained in:
parent
3593c248d3
commit
8b1bc44f67
File diff suppressed because one or more lines are too long
@ -170,3 +170,10 @@
|
||||
6. 부수 실측 수정: 일기 목표 카드 건강 다짐 값 단위("8,432회/8,000보" → 지표 단위).
|
||||
|
||||
검증: 자가 검증 61/20/27/10 ALL PASS, 3종 빌드 그린, 전 카탈로그 missing/stale 0, 시각 QA 10여 장(아이폰 요약·카드·블록·노트·필터·버블8·수면 구간, 아이패드, 맥 분기[카드 숨김], 18.5). 마케팅: 아이폰 06-bubble 재촬영(버블 8개)·10-week→**10-diary** 교체·아이패드 03-diary-summary 재촬영(건강 카드·블록), whats-new-1.5/설명 3언어 갱신. 빌드 4.
|
||||
|
||||
### §12 후속 — 소킹 실기기 피드백 반영 (같은 날 밤, 빌드 5)
|
||||
|
||||
- **버그 ⓐ**: 아이폰 더보기 탭 안 일기 달력에서 날짜를 눌러도 상세로 안 들어감(터치 반응만) — `AppRouter.morePath: [AppTab]`(고정 타입)에는 Date를 푸시할 수 없어 조용히 무시. → `NavigationPath`로 변경, 회귀 감시 인자 `-morePushDiaryToday`(더보기 스택에 [일기, 오늘] 프로그램 푸시 → 상세 착지 실측 확인). 라디얼·사이드바(비타입 스택)는 원래 정상 — "처음엔 됐다"는 증언과 일치.
|
||||
- **버그 ⓑ**: 수면 블록이 수면 단계(코어·렘·깸) 로우데이터 그대로 칸칸이 갈라짐 — 표시 병합 규칙 신설: **10분 이하의 깸은 이어 붙임**(`sleepDisplayMergeGap`, mergedIntervals에 tolerance 파라미터). 사용자 규칙 "연속으로 잤으면 한 칸, 20분쯤 깼다 자면 분리". 값(합계)은 실제 잔 시간 그대로 무영향.
|
||||
- **추가 요구 2건**: ①**타임테이블 표시 색 설정**(설정 → 건강 데이터 — 수면·운동 ColorPicker, 기본 인디고·주황=빈 값, App Group 기기 로컬, DiaryHealthTimetable.sleepColor/workoutColor 단일 지점이라 전 타임테이블·내보내기 자동 공통) ②**기록 탭 타임테이블에도 수면·운동 블록**(하루·주간 — 같은 공급 지점·같은 숨김 토글[기록 필터 시트에 건강 섹션 추가], 내보내기 `injectingHealthBlocks(forDayKeys:)` + trim 범위 확장. plan-1.6 후보 1의 잔여분 조기 흡수 — 1.6에 남은 것은 물 점 표시뿐).
|
||||
- 도움말·whats-new·팝업 문구 갱신, l10n 0/0, 빌드 5.
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
> **상태: 보류** — 사용자 결정(2026-08-22): "다음 업데이트 사항으로 두자. 생각이 바뀔 수도, 더 다양한 방법이 떠오를 수도 있으니 일단 보류."
|
||||
> ⚠️ **구현 금지** — 사용자가 재논의·진행을 지시할 때만 착수. 그 전까지 이 문서는 아이디어 기록 전용.
|
||||
>
|
||||
> 📌 **같은 날 저녁 대부분 1.5로 조기 흡수됨**(사용자 재결정 — plan-1.5.md §12): 후보 1의 **일기 표면**(하루 정리 타임테이블 수면·운동 블록)과 후보 2(수면 표시 구간)는 **1.5(4)에 구현 완료**. 1.6에 남은 잔여분: **기록 탭 타임테이블에도 건강 블록 표시**(HealthIntervals 캐시·주입 문법·색은 이미 있어 표시 계층만 — 난이도 하), 물 점 표시(미구현 — 1.5(4)는 수면·운동만).
|
||||
> 📌 **같은 날 저녁 대부분 1.5로 조기 흡수됨**(사용자 재결정 — plan-1.5.md §12): 후보 1의 **일기 표면**(하루 정리 타임테이블 수면·운동 블록)과 후보 2(수면 표시 구간)는 **1.5(4)에 구현 완료**. 1.6에 남은 잔여분: 물 점 표시뿐(1.5(4)는 수면·운동만 — 기록 탭 타임테이블 표시와 색 설정은 같은 날 밤 1.5(5)에 추가 흡수됨, plan-1.5.md §12 후속).
|
||||
|
||||
## 후보 1 — 타임테이블에 건강 데이터 표시 (일기 표면은 1.5(4)에 구현 — 잔여: 기록 탭)
|
||||
|
||||
|
||||
@ -601,7 +601,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -643,7 +643,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = IOS/Haru_Danim.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -686,7 +686,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -722,7 +722,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Haru_DanimWatch Watch App/Haru_DanimWatch Watch App.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -758,7 +758,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
|
||||
@ -794,7 +794,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Haru_DanimWatchWidgets/Haru_DanimWatchWidgetsExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Haru_DanimWatchWidgets/Info.plist;
|
||||
@ -827,7 +827,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
@ -853,7 +853,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = Widgets/Haru_DanimWidgets.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 4;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Widgets/Info.plist;
|
||||
|
||||
@ -40,6 +40,9 @@ struct ContentView: View {
|
||||
} else if UserDefaults.standard.bool(forKey: "sleepWindowPreview") {
|
||||
// 검증용: -sleepWindowPreview YES → 수면 표시 구간 설정 화면 직접 표시 (1.5 추가분)
|
||||
NavigationStack { SleepWindowSettingsView() }
|
||||
} else if UserDefaults.standard.bool(forKey: "healthColorPreview") {
|
||||
// 검증용: -healthColorPreview YES → 타임테이블 표시 색 설정 화면 직접 표시 (1.5(4))
|
||||
NavigationStack { HealthColorSettingsView() }
|
||||
} else {
|
||||
RootNavigationView()
|
||||
}
|
||||
@ -233,7 +236,9 @@ final class AppRouter {
|
||||
var pendingStatsActionID: PersistentIdentifier?
|
||||
|
||||
/// 더보기 탭 내부 내비게이션 경로. 더보기에 숨은 탭으로 바로 진입할 때 라우터가 채워 넣는다.
|
||||
var morePath: [AppTab] = []
|
||||
/// ⚠️ 반드시 NavigationPath(이종 허용) — [AppTab] 고정 타입이면 더보기 안 화면이 다른 타입을
|
||||
/// 푸시하지 못한다(1.5(4) 실측: 일기 달력의 날짜(Date) 탭이 조용히 무시돼 상세로 못 들어감)
|
||||
var morePath = NavigationPath()
|
||||
|
||||
/// 기록 탭으로 이동하며 행동 필터를 예약. 기록 탭이 노출 탭에 없으면 더보기 안의 기록 화면까지 바로 진입.
|
||||
func openHistory(filtering actionID: PersistentIdentifier, visibleTabsRaw: String) {
|
||||
@ -256,7 +261,7 @@ final class AppRouter {
|
||||
if visible.contains(tab) {
|
||||
tabSelection = tab.rawValue
|
||||
} else {
|
||||
morePath = [tab]
|
||||
morePath = NavigationPath([tab])
|
||||
tabSelection = AppTab.moreTabValue
|
||||
}
|
||||
}
|
||||
@ -387,11 +392,21 @@ struct MainTabView: View {
|
||||
}
|
||||
.onAppear {
|
||||
#if DEBUG
|
||||
// 검증용: -morePushDiaryToday YES → 더보기 스택에 [일기, 오늘 날짜]를 프로그램 푸시.
|
||||
// 일기 달력의 날짜 탭과 같은 '이종 타입(Date) 푸시' 경로 실측 — morePath가 [AppTab]
|
||||
// 고정 타입이던 시절엔 이 푸시가 조용히 무시됐다(1.5(4) 수정의 회귀 감시 인자)
|
||||
if UserDefaults.standard.bool(forKey: "morePushDiaryToday") {
|
||||
var path = NavigationPath([AppTab.diary])
|
||||
path.append(DayMath().dayKey(for: .now))
|
||||
router.morePath = path
|
||||
router.tabSelection = AppTab.moreTabValue
|
||||
return
|
||||
}
|
||||
// 검증용: -startTab이 노출 탭에 없으면 더보기 안의 그 화면까지 바로 진입
|
||||
let valid = visibleTabs.map(\.rawValue) + [AppTab.moreTabValue]
|
||||
if !valid.contains(router.tabSelection) {
|
||||
if let hidden = AppTab(rawValue: router.tabSelection) {
|
||||
router.morePath = [hidden]
|
||||
router.morePath = NavigationPath([hidden])
|
||||
}
|
||||
router.tabSelection = AppTab.moreTabValue
|
||||
}
|
||||
@ -410,14 +425,7 @@ struct MoreTabView: View {
|
||||
@Bindable var router = router
|
||||
NavigationStack(path: $router.morePath) {
|
||||
List(tabs) { tab in
|
||||
NavigationLink(value: tab) {
|
||||
Label {
|
||||
Text(tab.label)
|
||||
} icon: {
|
||||
Image(systemName: tab.symbol)
|
||||
.foregroundStyle(AppTheme.green)
|
||||
}
|
||||
}
|
||||
moreRow(tab)
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(AppTheme.background)
|
||||
@ -427,6 +435,18 @@ struct MoreTabView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func moreRow(_ tab: AppTab) -> some View {
|
||||
NavigationLink(value: tab) {
|
||||
Label {
|
||||
Text(tab.label)
|
||||
} icon: {
|
||||
Image(systemName: tab.symbol)
|
||||
.foregroundStyle(AppTheme.green)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@ -286,12 +286,14 @@ final class HealthDataStore {
|
||||
}
|
||||
|
||||
/// 구간 합집합 (겹침 병합, 시작 오름차순) — mergedDuration의 구간 보존판.
|
||||
/// 아이폰+워치가 같은 잠을 겹쳐 기록해도 타임테이블에 블록이 이중으로 그려지지 않게 한다
|
||||
static func mergedIntervals(_ intervals: [(Date, Date)]) -> [(Date, Date)] {
|
||||
/// 아이폰+워치가 같은 잠을 겹쳐 기록해도 타임테이블에 블록이 이중으로 그려지지 않게 한다.
|
||||
/// tolerance: 이 간격 이하로 떨어진 이웃 구간도 이어 붙인다 (표시 전용 — 값 합산에는 쓰지 않음)
|
||||
static func mergedIntervals(_ intervals: [(Date, Date)],
|
||||
tolerance: TimeInterval = 0) -> [(Date, Date)] {
|
||||
let sorted = intervals.sorted { $0.0 < $1.0 }
|
||||
var result: [(Date, Date)] = []
|
||||
for (start, end) in sorted {
|
||||
if let last = result.last, start <= last.1 {
|
||||
if let last = result.last, start.timeIntervalSince(last.1) <= tolerance {
|
||||
result[result.count - 1].1 = max(last.1, end)
|
||||
} else {
|
||||
result.append((start, end))
|
||||
|
||||
@ -3626,6 +3626,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"기본 색으로 되돌리기": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Reset to Default Colors"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "基本の色に戻す"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"기본 정보": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -9897,18 +9913,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 모든 날짜에 함께 적용돼요.": {
|
||||
"애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 일기·기록 탭의 모든 타임테이블에 함께 적용되고, 블록 색은 설정 → 건강 데이터에서 바꿀 수 있어요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Shows sleep and workout time from Apple Health on the timetable, at their actual times. This setting applies to all dates."
|
||||
"value": "Shows sleep and workout time from Apple Health on the timetable, at their actual times. This setting applies to every timetable in the Diary and History tabs, and you can change the block colors in Settings → Health data."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Appleヘルスケアの睡眠・運動時間を、実際の時間どおりタイムテーブルに表示します。この設定はすべての日付に適用されます。"
|
||||
"value": "Appleヘルスケアの睡眠・運動時間を、実際の時間どおりタイムテーブルに表示します。この設定は日記・記録タブのすべてのタイムテーブルに適用され、ブロックの色は設定 → ヘルスデータで変えられます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12301,6 +12317,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"일기·기록 탭 타임테이블에 깔리는 수면·운동 블록의 색이에요. 꼬리표 색과 비슷해서 헷갈리면 바꿔 보세요 — 모든 타임테이블과 내보내기에 함께 적용돼요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The colors of the sleep and workout blocks on the Diary and History timetables. If they look too close to your tag colors, change them here — the change applies to every timetable and export."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "日記・記録タブのタイムテーブルに敷かれる睡眠・運動ブロックの色です。タグの色と紛らわしければここで変えてください — すべてのタイムテーブルと書き出しに適用されます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"일기가 잠겨 있어요": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -12425,18 +12457,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요(수면 남보라·운동 주황) — 타임테이블 필터의 '건강 데이터' 토글로 켜고 끌 수 있어요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요.": {
|
||||
"일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 일기와 기록 탭의 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요 — 기본 색은 수면 남보라·운동 주황이고, 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끄면 두 타임테이블에 함께 적용돼요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The Diary's daily summary page has a 'Health data' card — see that day's steps, exercise, sleep and more alongside your diary. Pick which metrics to show with the button at the card's top right; the choice applies to all dates. The timetable shows sleep and workouts as light blocks at their actual times (indigo for sleep, orange for workouts) — toggle them with 'Health data' in the timetable filter.\nHealth values stay on each device, so the card's values can differ between devices — an iPad shows the same values as your iPhone only when the system's Health iCloud sync is on, and on the Mac the card and blocks don't appear."
|
||||
"value": "The Diary's daily summary page has a 'Health data' card — see that day's steps, exercise, sleep and more alongside your diary. Pick which metrics to show with the button at the card's top right; the choice applies to all dates. The Diary and History timetables show sleep and workouts as light blocks at their actual times — indigo and orange by default, changeable in Settings → Health data → 'Timetable colors'. The 'Health data' toggle in the filter turns them on or off for both timetables.\nHealth values stay on each device, so the card's values can differ between devices — an iPad shows the same values as your iPhone only when the system's Health iCloud sync is on, and on the Mac the card and blocks don't appear."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "日記の1日のまとめページに「ヘルスデータ」カードがあります — その日の歩数・運動・睡眠などの値を日記と一緒に見られます。カード右上のボタンで表示する指標を選ぶと、すべての日付に適用されます。タイムテーブルには睡眠・運動が実際の時間どおり薄いブロックで敷かれます(睡眠は藍色・運動はオレンジ)— タイムテーブルフィルターの「ヘルスデータ」トグルでオン・オフできます。\nヘルスの値は端末の中だけにあるため、カードの値も端末ごとに異なることがあります — iPadはシステムのヘルスケアiCloud同期をオンにした場合のみiPhoneと同じ値になり、Macではカードとブロックは表示されません。"
|
||||
"value": "日記の1日のまとめページに「ヘルスデータ」カードがあります — その日の歩数・運動・睡眠などの値を日記と一緒に見られます。カード右上のボタンで表示する指標を選ぶと、すべての日付に適用されます。日記・記録タブのタイムテーブルには睡眠・運動が実際の時間どおり薄いブロックで敷かれます — 基本の色は藍色とオレンジで、設定 → ヘルスデータ →「タイムテーブルの表示色」で変えられます。フィルターの「ヘルスデータ」トグルでオン・オフすると、両方のタイムテーブルに適用されます。\nヘルスの値は端末の中だけにあるため、カードの値も端末ごとに異なることがあります — iPadはシステムのヘルスケアiCloud同期をオンにした場合のみiPhoneと同じ値になり、Macではカードとブロックは表示されません。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14276,18 +14308,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 수면은 남보라, 운동은 주황이에요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(모든 날짜 공통), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요.": {
|
||||
"첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 기본은 수면 남보라·운동 주황이고, 색이 꼬리표와 헷갈리면 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(기록 탭 타임테이블에도 함께 적용), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events to show alongside — events have dashed borders, so they're easy to tell from actions. The filter button picks which of that day's recorded actions go on the timetable, and the filter on the This Day's Goals card keeps only the goals you want. These choices are saved per date, and the timetable selection carries into diary exports as is.\nSleep and workout time from Apple Health also appear as light blocks, at the times you actually slept and worked out — indigo for sleep, orange for workouts. Toggle them with 'Health data' in the filter (applies to all dates); they don't appear on the Mac, which has no health data.\nTired of repeating the same choices every day? Try 'Default Display' at the top right of the calendar screen. Decide once whether to include calendar events by default (all calendars, or selected ones only) and which goals and actions to hide by default, and it applies to every date. Days where you picked differently keep their own choice, and 'Reset to Default' takes you back anytime."
|
||||
"value": "Use the calendar button at the top right of the first page's timetable card to pick that day's calendar events to show alongside — events have dashed borders, so they're easy to tell from actions. The filter button picks which of that day's recorded actions go on the timetable, and the filter on the This Day's Goals card keeps only the goals you want. These choices are saved per date, and the timetable selection carries into diary exports as is.\nSleep and workout time from Apple Health also appear as light blocks, at the times you actually slept and worked out — indigo and orange by default, changeable in Settings → Health data → 'Timetable colors' if they clash with your tag colors. Toggle them with 'Health data' in the filter (this also applies to the History tab's timetable); they don't appear on the Mac, which has no health data.\nTired of repeating the same choices every day? Try 'Default Display' at the top right of the calendar screen. Decide once whether to include calendar events by default (all calendars, or selected ones only) and which goals and actions to hide by default, and it applies to every date. Days where you picked differently keep their own choice, and 'Reset to Default' takes you back anytime."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます — 予定は破線の枠なのでアクションとすぐ区別できます。フィルターボタンではその日に記録したアクションのうちタイムテーブルに載せるものを選び、「この日の目標」カードのフィルターでは見たい目標だけを残します。これらの選択は日付ごとに保存され、タイムテーブルの選択は日記の書き出しにもそのまま反映されます。\nAppleヘルスケアの睡眠・運動時間も、実際に眠った・運動した時間どおり薄いブロックで敷かれます — 睡眠は藍色、運動はオレンジです。フィルターの「ヘルスデータ」トグルでオン・オフでき(すべての日付に共通)、ヘルスデータのないMacでは表示されません。\n毎日同じ選択を繰り返したくないなら、カレンダー画面右上の「デフォルト表示設定」を使ってみてください。カレンダー予定をデフォルトで入れるか(すべてのカレンダー/選んだカレンダーのみ)、どの目標・アクションをデフォルトで隠すかを一度決めれば、すべての日付に適用されます。日付別フィルターで別の選択をした日はその選択が優先され、「デフォルトに戻す」でいつでも戻れます。"
|
||||
"value": "最初のページのタイムテーブルカード右上のカレンダーボタンで、その日のカレンダー予定を選んで一緒に表示できます — 予定は破線の枠なのでアクションとすぐ区別できます。フィルターボタンではその日に記録したアクションのうちタイムテーブルに載せるものを選び、「この日の目標」カードのフィルターでは見たい目標だけを残します。これらの選択は日付ごとに保存され、タイムテーブルの選択は日記の書き出しにもそのまま反映されます。\nAppleヘルスケアの睡眠・運動時間も、実際に眠った・運動した時間どおり薄いブロックで敷かれます — 基本の色は藍色とオレンジで、タグの色と紛らわしければ設定 → ヘルスデータ →「タイムテーブルの表示色」で変えられます。フィルターの「ヘルスデータ」トグルでオン・オフでき(記録タブのタイムテーブルにも適用)、ヘルスデータのないMacでは表示されません。\n毎日同じ選択を繰り返したくないなら、カレンダー画面右上の「デフォルト表示設定」を使ってみてください。カレンダー予定をデフォルトで入れるか(すべてのカレンダー/選んだカレンダーのみ)、どの目標・アクションをデフォルトで隠すかを一度決めれば、すべての日付に適用されます。日付別フィルターで別の選択をした日はその選択が優先され、「デフォルトに戻す」でいつでも戻れます。"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14986,6 +15018,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"타임테이블 표시 색": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Timetable colors"
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "タイムテーブルの表示色"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"타임테이블 필터": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
@ -16287,18 +16335,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"하루 정리에 그날의 건강 값을 담는 카드가 생기고, 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요. 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.": {
|
||||
"하루 정리에 그날의 건강 값을 담는 카드가 생기고, 일기·기록 탭 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요(색은 설정에서 변경). 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.": {
|
||||
"localizations": {
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "The daily summary gains a card with that day's health values, and sleep and workouts now appear on the timetable at their actual times. If you sleep in the daytime some days, tune the 'Sleep display window' per weekday in Settings."
|
||||
"value": "The daily summary gains a card with that day's health values, and the Diary and History timetables show sleep and workouts at their actual times (colors changeable in Settings). If you sleep in the daytime some days, tune the 'Sleep display window' per weekday in Settings."
|
||||
}
|
||||
},
|
||||
"ja": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "1日のまとめにその日のヘルス値をまとめるカードが加わり、タイムテーブルには睡眠・運動が実際の時間どおりに表示されます。昼間に眠る日があるなら、設定の「睡眠の表示時間帯」を曜日ごとに合わせてみてください。"
|
||||
"value": "1日のまとめにその日のヘルス値をまとめるカードが加わり、日記・記録タブのタイムテーブルには睡眠・運動が実際の時間どおりに表示されます(色は設定で変更可)。昼間に眠る日があるなら、設定の「睡眠の表示時間帯」を曜日ごとに合わせてみてください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +63,33 @@ struct ExportSnapshot {
|
||||
return copy
|
||||
}
|
||||
|
||||
/// 타임테이블 섹션(하루·주간)에 건강(수면·운동) 블록을 날짜별로 끼워 넣는다 (1.5(4) —
|
||||
/// 기록 탭 화면과 내보내기 일치용). 기록이 없는 시간대를 잘라낸 표시 범위(trimHours)는
|
||||
/// 블록이 걸치는 만큼 확장한다 — 새벽 수면이 잘린 그리드 밖으로 사라지지 않게
|
||||
func injectingHealthBlocks(forDayKeys dayKeys: [Date], math: DayMath) -> ExportSnapshot {
|
||||
var copy = self
|
||||
copy.sections = sections.map { section in
|
||||
guard case .timetable(let title, let subtitle, var data) = section,
|
||||
data.days.count == dayKeys.count else { return section }
|
||||
var changed = false
|
||||
for (index, key) in dayKeys.enumerated() {
|
||||
let blocks = DiaryHealthTimetable.blocks(dayKey: key, math: math)
|
||||
guard !blocks.isEmpty else { continue }
|
||||
data.days[index].healthBlocks = blocks
|
||||
changed = true
|
||||
if let lo = blocks.map(\.startFrac).min() {
|
||||
data.hourLo = min(data.hourLo, max(0, Int(lo.rounded(.down))))
|
||||
}
|
||||
if let hi = blocks.map(\.endFrac).max() {
|
||||
data.hourHi = max(data.hourHi, min(24, Int(hi.rounded(.up))))
|
||||
}
|
||||
}
|
||||
guard changed else { return section }
|
||||
return .timetable(title: title, subtitle: subtitle, data)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
/// 타임테이블 섹션만 다른 스냅숏의 것으로 교체.
|
||||
/// 일기의 날짜별 타임테이블 필터용 — 기록·통계 섹션은 전체(자신)를 유지한다.
|
||||
func replacingTimetableSections(with other: ExportSnapshot) -> ExportSnapshot {
|
||||
|
||||
@ -362,8 +362,22 @@ struct HealthGuideSheet: View {
|
||||
enum DiaryHealthTimetable {
|
||||
/// 숨김 저장 키 (standard defaults — 기기 로컬, 모든 날짜 공통. "sleep"/"workout" 쉼표 연결)
|
||||
static let hiddenKey = "diary.timetableHealthHidden"
|
||||
static let sleepColor = Color.indigo
|
||||
static let workoutColor = Color.orange
|
||||
|
||||
/// 블록 색 — 기본은 지표 고정색(수면 인디고·운동 주황)이지만, 꼬리표 색과 겹치면
|
||||
/// 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있다(모든 타임테이블 공통, 1.5(4))
|
||||
static var sleepColor: Color { customColor(LocalPrefsKeys.healthSleepColor) ?? .indigo }
|
||||
static var workoutColor: Color { customColor(LocalPrefsKeys.healthWorkoutColor) ?? .orange }
|
||||
|
||||
private static func customColor(_ key: String) -> Color? {
|
||||
guard let hex = AppGroup.defaults.string(forKey: key), !hex.isEmpty else { return nil }
|
||||
return Color(hex: hex)
|
||||
}
|
||||
|
||||
/// 수면 블록 병합 허용 간격 — 건강 앱 로우데이터는 수면 단계(코어·렘·깸)마다 쪼개져 있어
|
||||
/// 그대로 그리면 블록이 칸칸이 갈라진다(1.5(4) 실기기 실측). 이 간격 이하의 짧은 깸은
|
||||
/// 이어 붙여 한 덩어리로 그린다 — 사용자 규칙: "연속으로 잤으면 한 칸, 20분쯤 깼다 다시
|
||||
/// 잤으면 나뉘는 게 맞다" → 10분 기준. 값(수면 합계)은 실제 잔 시간 그대로라 무영향
|
||||
static let sleepDisplayMergeGap: TimeInterval = 10 * 60
|
||||
|
||||
static var hidden: Set<String> {
|
||||
Set((UserDefaults.standard.string(forKey: hiddenKey) ?? "")
|
||||
@ -385,9 +399,14 @@ enum DiaryHealthTimetable {
|
||||
func frac(_ date: Date) -> Double { date.timeIntervalSince(dayStart) / 3600 }
|
||||
var blocks: [ExportTimetableData.Block] = []
|
||||
if !hiddenSet.contains("sleep") {
|
||||
for interval in HealthIntervals.intervals(HealthIntervals.sleepKey, dayKey: dayKey) {
|
||||
let merged = HealthDataStore.mergedIntervals(
|
||||
HealthIntervals.intervals(HealthIntervals.sleepKey, dayKey: dayKey)
|
||||
.map { ($0.start, $0.end) },
|
||||
tolerance: sleepDisplayMergeGap
|
||||
)
|
||||
for interval in merged {
|
||||
blocks.append(ExportTimetableData.Block(
|
||||
startFrac: frac(interval.start), endFrac: frac(interval.end),
|
||||
startFrac: frac(interval.0), endFrac: frac(interval.1),
|
||||
color: sleepColor, symbol: HealthMetric.sleep.symbolName,
|
||||
name: HealthMetric.sleep.name
|
||||
))
|
||||
@ -600,6 +619,63 @@ struct SleepWindowSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 타임테이블 표시 색 설정 (1.5(4) — 수면·운동 블록 색, 모든 타임테이블 공통)
|
||||
|
||||
/// 수면·운동 블록의 표시 색. 기본은 지표 고정색(인디고·주황)이지만 꼬리표 색과 겹치면
|
||||
/// 여기서 바꾼다 — 일기·기록 탭 타임테이블과 내보내기에 모두 적용(단일 공급 지점이
|
||||
/// DiaryHealthTimetable.sleepColor/workoutColor라 자동 일치).
|
||||
struct HealthColorSettingsView: View {
|
||||
@AppStorage(LocalPrefsKeys.healthSleepColor, store: AppGroup.defaults)
|
||||
private var sleepHex = ""
|
||||
@AppStorage(LocalPrefsKeys.healthWorkoutColor, store: AppGroup.defaults)
|
||||
private var workoutHex = ""
|
||||
|
||||
private func binding(_ hex: Binding<String>, fallback: Color) -> Binding<Color> {
|
||||
Binding(
|
||||
get: { hex.wrappedValue.isEmpty ? fallback : Color(hex: hex.wrappedValue) },
|
||||
set: { hex.wrappedValue = $0.hexString }
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
ColorPicker(selection: binding($sleepHex, fallback: .indigo), supportsOpacity: false) {
|
||||
Label {
|
||||
Text("수면")
|
||||
} icon: {
|
||||
Image(systemName: HealthMetric.sleep.symbolName)
|
||||
.foregroundStyle(DiaryHealthTimetable.sleepColor)
|
||||
}
|
||||
}
|
||||
ColorPicker(selection: binding($workoutHex, fallback: .orange), supportsOpacity: false) {
|
||||
Label {
|
||||
Text("운동")
|
||||
} icon: {
|
||||
Image(systemName: "figure.run")
|
||||
.foregroundStyle(DiaryHealthTimetable.workoutColor)
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("일기·기록 탭 타임테이블에 깔리는 수면·운동 블록의 색이에요. 꼬리표 색과 비슷해서 헷갈리면 바꿔 보세요 — 모든 타임테이블과 내보내기에 함께 적용돼요.")
|
||||
}
|
||||
if !sleepHex.isEmpty || !workoutHex.isEmpty {
|
||||
Section {
|
||||
Button("기본 색으로 되돌리기") {
|
||||
sleepHex = ""
|
||||
workoutHex = ""
|
||||
}
|
||||
.font(.callout)
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(AppTheme.background)
|
||||
.navigationTitle("타임테이블 표시 색")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
/// 수면 구간 스펙("시작분-끝분") 하나를 편집하는 시작·끝 휠 두 줄
|
||||
private struct SleepWindowEditorRows: View {
|
||||
@Binding var spec: String
|
||||
|
||||
@ -312,7 +312,7 @@ struct HelpView: View {
|
||||
HelpTopic(
|
||||
symbol: "heart.text.square.fill",
|
||||
title: "일기에서 건강 데이터 보기",
|
||||
body: "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요(수면 남보라·운동 주황) — 타임테이블 필터의 '건강 데이터' 토글로 켜고 끌 수 있어요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요."
|
||||
body: "일기의 하루 정리 첫 페이지에 '건강 데이터' 카드가 있어요 — 그날의 걸음·운동·수면 같은 값을 일기와 함께 봐요. 카드 오른쪽 위 버튼으로 보여줄 지표를 고르면 모든 날짜에 함께 적용돼요. 일기와 기록 탭의 타임테이블에는 수면·운동이 실제 시간 그대로 옅은 블록으로 깔려요 — 기본 색은 수면 남보라·운동 주황이고, 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끄면 두 타임테이블에 함께 적용돼요.\n건강 값은 기기 안에만 있어서 카드의 값도 기기마다 다를 수 있어요 — 아이패드는 시스템의 건강 iCloud 동기화를 켠 경우에만 아이폰과 같은 값이 보이고, 맥에서는 카드와 블록이 보이지 않아요."
|
||||
),
|
||||
HelpTopic(
|
||||
symbol: "figure.run",
|
||||
@ -352,7 +352,7 @@ struct HelpView: View {
|
||||
HelpTopic(
|
||||
symbol: "calendar.badge.plus",
|
||||
title: "타임테이블에 일정·필터",
|
||||
body: "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 수면은 남보라, 운동은 주황이에요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(모든 날짜 공통), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요."
|
||||
body: "첫 페이지 타임테이블 카드의 오른쪽 위 캘린더 버튼으로 그날의 캘린더 일정을 골라 함께 보여줄 수 있어요 — 일정은 점선 테두리라 행동과 바로 구분돼요. 필터 버튼으로는 그날 기록된 행동 중 타임테이블에 올릴 것만 고르고, '이 날의 목표' 카드의 필터로는 보고 싶은 목표만 남겨요. 이 선택들은 날짜마다 따로 저장되고, 타임테이블 선택은 일기를 내보낼 때도 그대로 담겨요.\n애플 건강의 수면·운동 시간도 실제 잔 시간·운동한 시간 그대로 옅은 블록으로 깔려요 — 기본은 수면 남보라·운동 주황이고, 색이 꼬리표와 헷갈리면 설정 → 건강 데이터 → '타임테이블 표시 색'에서 바꿀 수 있어요. 필터의 '건강 데이터' 토글로 켜고 끌 수 있고(기록 탭 타임테이블에도 함께 적용), 건강 데이터가 없는 맥에서는 보이지 않아요.\n매일 같은 선택을 되풀이하기 싫다면 달력 화면 오른쪽 위의 '기본 표시 설정'을 써 보세요. 캘린더 일정을 기본으로 넣을지(모든 캘린더/선택한 캘린더만), 어떤 목표·행동을 기본으로 숨길지 한 번만 정하면 모든 날짜에 적용돼요. 날짜별 필터로 다르게 고른 날은 그 선택이 우선하고, '기본값으로 되돌리기'로 언제든 돌아갈 수 있어요."
|
||||
),
|
||||
HelpTopic(
|
||||
symbol: "applepencil",
|
||||
|
||||
@ -154,11 +154,13 @@ struct HistoryView: View {
|
||||
CountEntryEditorView(entry: entry)
|
||||
}
|
||||
.sheet(isPresented: $showingFilter) {
|
||||
// 건강 섹션(수면·운동 블록 토글)은 일기 필터와 같은 값 공유 — 타임테이블 공통 (1.5(4))
|
||||
RecordFilterSheet(
|
||||
title: "기록 필터",
|
||||
tags: tags,
|
||||
actions: allActions,
|
||||
excludedActionIDs: $excludedActionIDs
|
||||
excludedActionIDs: $excludedActionIDs,
|
||||
showsHealthSection: true
|
||||
)
|
||||
}
|
||||
.onAppear {
|
||||
@ -415,16 +417,21 @@ private struct HistoryRecordsView: View {
|
||||
}
|
||||
.sheet(isPresented: $showingExport) {
|
||||
// 지금 보고 있는 날짜/보기 모드/필터 그대로 스냅숏을 만들어 이미지로 내보낸다
|
||||
// (스냅숏 집계 범위는 하루 또는 이 주간 — 조회 범위와 일치)
|
||||
// (스냅숏 집계 범위는 하루 또는 이 주간 — 조회 범위와 일치).
|
||||
// 건강 블록도 화면과 동일하게 주입 — 하루/주간 각각의 날짜 키로 (1.5(4))
|
||||
let weekly = mode == .timetable && timetableWeekly
|
||||
let healthDayKeys = weekly
|
||||
? math.dayKeys(in: math.weekRange(containing: math.dayRange(forKey: dayKey).lowerBound))
|
||||
: [dayKey]
|
||||
ExportImageSheet(snapshot: ExportBuilder.history(
|
||||
dayKey: dayKey,
|
||||
weeklyTimetable: mode == .timetable && timetableWeekly,
|
||||
weeklyTimetable: weekly,
|
||||
sessions: sessions,
|
||||
entries: entries,
|
||||
orderedActions: orderedActions,
|
||||
excludedActionIDs: excludedActionIDs,
|
||||
math: math
|
||||
))
|
||||
).injectingHealthBlocks(forDayKeys: healthDayKeys, math: math))
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,6 +565,10 @@ struct TimetableView: View {
|
||||
let onTapSession: (TimeSession) -> Void
|
||||
let onTapEntry: (CountEntry) -> Void
|
||||
|
||||
/// 수면·운동 블록 숨김 — 일기 타임테이블 필터와 같은 값(전 타임테이블 공통, 1.5(4)).
|
||||
/// @AppStorage로 읽어 필터 시트 토글이 즉시 재렌더된다
|
||||
@AppStorage(DiaryHealthTimetable.hiddenKey) private var healthHiddenRaw = ""
|
||||
|
||||
private var hourHeight: CGFloat { weekly ? 26 : 48 }
|
||||
|
||||
var body: some View {
|
||||
@ -627,9 +638,17 @@ struct TimetableView: View {
|
||||
let range = math.dayRange(forKey: key)
|
||||
let daySegments = segments(range)
|
||||
let dayCounts = counts(range)
|
||||
// 수면·운동 실구간 — 일기 타임테이블과 같은 단일 공급 지점(색·병합·숨김 규칙 동일, 1.5(4))
|
||||
let healthBlocks = DiaryHealthTimetable.blocks(
|
||||
dayKey: key, math: math,
|
||||
hidden: Set(healthHiddenRaw.split(separator: ",").map(String.init))
|
||||
)
|
||||
return GeometryReader { geo in
|
||||
ZStack(alignment: .topLeading) {
|
||||
gridLines(width: geo.size.width)
|
||||
ForEach(healthBlocks) { item in
|
||||
healthBlock(item, width: geo.size.width)
|
||||
}
|
||||
ForEach(daySegments) { item in
|
||||
block(item, in: range, width: geo.size.width)
|
||||
}
|
||||
@ -640,6 +659,36 @@ struct TimetableView: View {
|
||||
}
|
||||
.frame(height: hourHeight * 24)
|
||||
.background(AppTheme.surface, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
}
|
||||
|
||||
/// 건강(수면·운동) 블록 — 내보내기(healthBlockView)와 같은 반투명+실선 문법, 표시 전용(탭 없음)
|
||||
private func healthBlock(_ item: ExportTimetableData.Block, width: CGFloat) -> some View {
|
||||
let y = CGFloat(item.startFrac) * hourHeight
|
||||
let height = max(CGFloat(item.endFrac - item.startFrac) * hourHeight, 5)
|
||||
return RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.fill(item.color.opacity(0.16))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.strokeBorder(item.color.opacity(0.55), lineWidth: 1)
|
||||
)
|
||||
.overlay(alignment: .topLeading) {
|
||||
if !weekly && height >= 18 {
|
||||
Label(item.name, systemImage: item.symbol)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.foregroundStyle(item.color)
|
||||
.lineLimit(1)
|
||||
.padding(4)
|
||||
} else if height >= 12 {
|
||||
Image(safeSymbol: item.symbol)
|
||||
.font(.system(size: 7, weight: .bold))
|
||||
.foregroundStyle(item.color)
|
||||
.padding(2)
|
||||
}
|
||||
}
|
||||
.frame(width: max(width - 4, 8), height: height)
|
||||
.offset(x: 2, y: y)
|
||||
.accessibilityLabel(Text(verbatim: item.name))
|
||||
}
|
||||
|
||||
private func gridLines(width: CGFloat) -> some View {
|
||||
|
||||
@ -112,7 +112,7 @@ struct RecordFilterSheet: View {
|
||||
Text("건강 데이터")
|
||||
} footer: {
|
||||
if HealthDataStore.isAvailable {
|
||||
Text("애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 모든 날짜에 함께 적용돼요.")
|
||||
Text("애플 건강의 수면·운동 시간을 실제 구간 그대로 타임테이블에 보여줘요. 이 설정은 일기·기록 탭의 모든 타임테이블에 함께 적용되고, 블록 색은 설정 → 건강 데이터에서 바꿀 수 있어요.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,6 +212,19 @@ struct SettingsView: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
// 1.5(4): 타임테이블 수면·운동 블록 색 (모든 타임테이블 공통)
|
||||
NavigationLink {
|
||||
HealthColorSettingsView()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("타임테이블 표시 색")
|
||||
Spacer()
|
||||
Circle().fill(DiaryHealthTimetable.sleepColor)
|
||||
.frame(width: 14, height: 14)
|
||||
Circle().fill(DiaryHealthTimetable.workoutColor)
|
||||
.frame(width: 14, height: 14)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("건강 데이터")
|
||||
} footer: {
|
||||
|
||||
@ -86,7 +86,7 @@ struct WhatsNewView: View {
|
||||
symbol: "heart.text.square.fill",
|
||||
tint: .pink,
|
||||
title: String(localized: "일기에서 건강 데이터 보기"),
|
||||
body: String(localized: "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요. 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.")
|
||||
body: String(localized: "하루 정리에 그날의 건강 값을 담는 카드가 생기고, 일기·기록 탭 타임테이블에는 수면·운동이 실제 시간 그대로 깔려요(색은 설정에서 변경). 낮에 자는 날이 있다면 설정의 '수면 표시 구간'을 요일별로 맞춰 보세요.")
|
||||
)
|
||||
feature(
|
||||
symbol: "questionmark.circle.fill",
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
● 일기가 더 넓어졌어요 (프리미엄)
|
||||
• 일기 탭이 아이폰에도 생겼어요 — 하루 정리를 확인하고, '그리기'를 켜면 손가락으로 노트에 쓸 수 있어요
|
||||
• 하루 정리에 그날의 건강 값을 담는 '건강 데이터' 카드가 생겼어요
|
||||
• 타임테이블에 수면·운동이 실제 시간 그대로 깔려요 — 하루가 정말 한눈에 정리돼요
|
||||
• 일기·기록 탭 타임테이블에 수면·운동이 실제 시간 그대로 깔려요 — 색도 취향대로 바꿀 수 있어요
|
||||
|
||||
● 애플워치 '행동 기록' 컴플리케이션
|
||||
• 행동의 누적 기록(하루·이번 주·이번 달·지난 7일·지난 30일)을 워치 페이스에서 바로 봐요
|
||||
@ -32,7 +32,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A
|
||||
● A bigger Diary (Premium)
|
||||
• The Diary tab is now on iPhone — review your day, and turn on Draw to write on note pages with your finger
|
||||
• The daily summary gains a Health Data card with that day's values
|
||||
• Sleep and workouts now appear on the timetable at their actual times — your whole day, truly at a glance
|
||||
• Sleep and workouts now appear on the Diary and History timetables at their actual times — with colors you can customize
|
||||
|
||||
● Action Log complication for Apple Watch
|
||||
• See an action's running total (day, this week, this month, last 7/30 days) right on your watch face
|
||||
@ -53,7 +53,7 @@ This update brings Haru Danim closer to Apple Health — and your Apple Watch. A
|
||||
● 日記がもっと広く(プレミアム)
|
||||
• 日記タブがiPhoneにも登場 — 1日のまとめを確認し、「描く」をオンにすれば指でノートに書けます
|
||||
• 1日のまとめに、その日のヘルス値をまとめる「ヘルスデータ」カードが加わりました
|
||||
• タイムテーブルに睡眠・運動が実際の時間どおりに表示されます — 1日が本当にひと目でわかります
|
||||
• 日記・記録タブのタイムテーブルに睡眠・運動が実際の時間どおりに表示されます — 色も好みに変えられます
|
||||
|
||||
● Apple Watch「アクションの記録」コンプリケーション
|
||||
• アクションの累計(1日・今週・今月・過去7日・過去30日)をウォッチフェイスで直接確認
|
||||
|
||||
@ -46,6 +46,10 @@ nonisolated enum LocalPrefsKeys {
|
||||
/// 모음 탭 건강 타일에 표시할 지표 — HealthMetric raw 쉼표 연결(순서 포함, 1.5).
|
||||
/// 기본: 걸음·운동·수면
|
||||
static let healthMetrics = "local.healthMetrics"
|
||||
/// 타임테이블 수면 블록 색 — "#RRGGBB", 빈 값=기본(인디고). 모든 타임테이블 공통 (1.5(4))
|
||||
static let healthSleepColor = "local.healthSleepColor"
|
||||
/// 타임테이블 운동 블록 색 — "#RRGGBB", 빈 값=기본(주황). 모든 타임테이블 공통 (1.5(4))
|
||||
static let healthWorkoutColor = "local.healthWorkoutColor"
|
||||
/// 수면 표시 구간(타일·일기 건강 카드 공용) — "시작분-끝분" 스펙, 빈 값=기본(1260-540) (1.5)
|
||||
static let sleepWindowGlobal = "local.sleepWindowGlobal"
|
||||
/// 요일별 수면 구간 사용 여부 — Bool (1.5)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user