From dbbbfb8506b2691f3aa040dc3a594bad37cb7839 Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Sat, 18 Jul 2026 09:18:35 +0900 Subject: [PATCH] fix(settings): collapse day-start time wheel into a value row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 설정 '하루 시작 시간'의 상시 노출 휠이 화면을 계속 차지한다는 실기기 피드백 → 평소엔 값 행(라벨 + 현재 시각 + 셰브론)만 보이고 탭하면 인라인 휠이 펼쳐지는 방식으로 변경 - 팝오버(컴팩트)로 되돌리지 않음 — 다이얼 감속 중 닫힘 시 정착값 유실 문제가 재발하므로 인라인 휠 유지가 안전. 접힘/펼침은 표시 상태일 뿐 값 반영 경로는 동일 - 접근성: 행에 라벨+현재 값 낭독. 마감 시각·기록 편집기는 일시적 시트라 상시 노출 유지 (§6.5 문서화) - 검증 인자 -settingsShowDayStartWheel 신설(펼친 상태로 시작) — 접힘·펼침 스크린샷 확인, Debug/Store 빌드 성공, 카탈로그 무변화(외부 재저장으로 오염된 워킹트리 카탈로그는 HEAD로 복원) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014CkLDi8Lr21vGqo4SjZaTp --- myApp/HaruDanim/CLAUDE.md | 4 +- myApp/HaruDanim/IOS/Views/SettingsView.swift | 47 +++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index fd8bfae..9842da0 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -199,7 +199,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f ### 6.5 기록 탭 (`HistoryView`, `RecordEditors`) - **범위 조회**: 기록은 전량 로드하지 않고 선택 날짜가 속한 **주 범위**만 @Query로 가져온다(`HistoryRecordsView` — SwiftData 동적 쿼리 패턴: 날짜가 바뀌면 새 조건으로 재생성). 하루 경계 걸침·진행 중 세션은 "시작 < 범위끝 AND (종료 없음 OR 종료 > 범위시작)" 겹침 조건으로 포함 - 날짜 헤더(화살표 이동, 날짜 탭=달력 팝오버, "오늘" 버튼) + 목록/타임테이블 세그먼트 -- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 분 단위 절사(flooredToMinute) — 실측정 기록의 초 찌꺼기(14:32:47)가 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. 다짐 '마감 시각'·설정 '하루 시작 시간'도 같은 RecordTimeWheel(components: .hourAndMinute)로 통일 — 앱의 시각 입력 다이얼은 전부 상시 노출 휠 +- 목록: 시간 기록(시작~종료·총 시간·메모)·횟수 기록(시각·+n·메모), 탭하면 수정 시트. **수정·추가 시트의 시각 입력은 상시 노출 휠 다이얼**(`RecordEditors.swift` RecordTimeWheel) — 컴팩트 팝오버는 다이얼 감속 중 닫히면 정착값이 유실되는 실기기 보고가 있어 폐기. `in:` 범위 제약도 하한 근처 선택을 소리 없이 되감아 금지(검증은 종료<시작 경고 문구+저장 비활성). 편집 상태는 분 단위 절사(flooredToMinute) — 실측정 기록의 초 찌꺼기(14:32:47)가 남으면 다이얼로 맞춘 1시간이 59분으로 표시되는 어긋남 방지. 다짐 '마감 시각'·설정 '하루 시작 시간'도 같은 RecordTimeWheel(components: .hourAndMinute)로 통일 — 앱의 시각 입력 다이얼은 전부 인라인 휠(팝오버 금지). 설정 '하루 시작 시간'은 상시 노출이 거슬린다는 실기기 피드백으로 **평소엔 값 행(라벨+현재 시각+셰브론), 탭하면 휠 펼침** — 마감 시각·기록 편집기는 일시적 시트라 상시 노출 유지 - 타임테이블: 24시간 축(하루 시작 시간 기준), 하루/일주일 전환(주간 보기에서 화살표는 7일씩 점프). 시간=색 블록, 횟수=색 점. 블록/점 탭=수정 - 필터: `RecordFilterSheet`(공용) — 꼬리표 아래 행동이 그룹된 트리, **"제외한 행동 ID" 집합**으로 관리해 기본이 전체 선택. 활성 시 초록 칩 표시 - 내보내기: 현재 날짜/모드/필터 그대로 `ExportBuilder.history` 스냅숏 → 이미지 시트 @@ -332,7 +332,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f | 측정 알림 | `-settings.longSessionAlertHours `(설정 주입) `-longSessionAlertTestSeconds `(시간 대신 N초 발화, provisional 권한 자동) `-alertDump`(예약 상태 → Documents/session-alerts.txt) | | 목표 | `-goalShowEditor` `-goalShowFinished` `-goalReorder` `-goalScrollBottom` `-questShowEditor`(다짐 편집기 표시 — 마감 시각 다짐 우선, 없으면 첫 다짐) `-endGoalYesterday "제목"`(종료일을 어제로 — 실행 시점 자동 판정 검증) `-watchGoals `(생성순 목표 N개를 '애플워치에서 보기'로 설정, 0=전체 해제 — 설정 후 스냅숏 즉시 푸시, 컴플리케이션 필터 검증용) `-streakDump`(모든 다짐의 연속 계산 결과 → Documents/streak-dump.txt) `-progressSelfTest`(spanProgress 수치 37건 자가 검증[환산 캡·수행일 집계·마감·atMost·custom] → Documents/progress-self-test.txt — 인메모리 컨테이너·설정 고정이라 실데이터/기기 설정 무영향) | | 기록/통계 | `-historyMode timetable` `-historyWeekly` `-excludeActions "이름,이름"` `-historyShowFilter` `-statShowFilter` `-statSpan ` `-statScrollBottom` `-showExport` `-exportDump`(Documents/export-dump.png) `-recordShowEditor `(최신 기록 수정 시트) | -| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo `: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) | +| 설정/기타 | `-settingsScrollGoal` `-settingsScrollPremium` `-settingsShowDayStartWheel`(하루 시작 시간 휠 펼침 시작) `-premiumPreview` `-helpPreview`(+`-helpExpandAll`: 전 주제 펼침, `-helpScrollTo `: 이미지 주제로 스크롤) `-dataExportPreview`(+`-dataExportRun`이면 CSV 자동 생성 후 Documents 복사) `-widgetPreview YES|lock` `-widgetPreviewScroll <앵커>` `-widgetRenderMode accented|vibrant`(틴트/투명 홈 렌더링 분기 검증) `cloudSync`(standard bool로 강제) `-intentSmokeTest`(시리 인텐트 10개 시나리오 실행 → Documents/intent-smoke-test.txt, -seedDemo·-premium과 함께) | | 버블 | `-radialExpanded` `-radialDoubleTapTest`(2초 뒤 FAB 더블탭 재현 — `-settings.radialDoubleTapTab <탭>`과 함께) | | 일기 | `-diarySeed`(필기 획 2줄 포함 — 달력 화면에서 먼저 시드 후 재실행으로 열 것) `-diaryOpenToday`(**`-startTab diary` 필수**) `-diaryPage ` `-diaryZoomScale <배>`(2.5초 뒤 fit×N 프로그램 줌 — 획 유지·선명도 확인) `-diaryShowConfig` `-diaryShowExport`(달력 화면 onAppear — `-diaryOpenToday`와 함께 쓰면 안 뜸) `-diaryExportRun pdf|image`(결과를 Documents로 복사) `-diaryShowCalendarPicker` `-diarySeedEvents` `-diaryHideFirstAction` `-diaryShowActionFilter` `-diaryHideFirstGoal` `-diaryShowGoalFilter` `-diarySeedPhoto`(오늘=이모지+사진·어제=사진만 기분 사진 시드 — 달력 백레이어 검증) `-diarySeedTemplate`(2쪽 샘플 PDF 양식 생성) `-diaryShowTemplates`(양식 관리 시트) `-diaryShowPageChooser`(페이지 추가 선택 시트) `-diaryApplyTemplate`(첫 양식으로 페이지 추가) / 섹션 강제: `-diary.sectionOrder "timetable,hero,goals"` `-diary.hiddenSections "mood,todos,records,bars"` | | 워치 | `-complicationPreview` `-complicationScroll` `-complicationScrollTo ` `-autoRunFirstAction` `-autoStopRunning`(측정 중 행 탭과 동일 경로로 대표 측정 종료 — 페어링 심 E2E용) | diff --git a/myApp/HaruDanim/IOS/Views/SettingsView.swift b/myApp/HaruDanim/IOS/Views/SettingsView.swift index d56312a..11bdbbf 100644 --- a/myApp/HaruDanim/IOS/Views/SettingsView.swift +++ b/myApp/HaruDanim/IOS/Views/SettingsView.swift @@ -43,6 +43,15 @@ struct SettingsView: View { ) } + /// 하루 시작 시간 인라인 휠 펼침 상태 (기본 접힘 — 값 행만 표시) + @State private var showsDayStartWheel: Bool = { + #if DEBUG + // 검증용: -settingsShowDayStartWheel YES → 휠 펼친 상태로 시작 + if UserDefaults.standard.bool(forKey: "settingsShowDayStartWheel") { return true } + #endif + return false + }() + /// 하루 시작 시간을 DatePicker와 연결하기 위한 변환 private var dayStartDate: Binding { Binding { @@ -146,13 +155,39 @@ struct SettingsView: View { Text("\(Format.weekdayShort(weekday))요일").tag(weekday) } } + // 평소엔 현재 값만 보이는 행, 탭하면 인라인 휠 펼침 — + // 상시 노출 휠이 설정 탭을 계속 차지한다는 실기기 피드백 반영. + // 팝오버로 되돌리지 않는 이유: 팝오버 다이얼은 감속 중 닫히면 + // 정착값이 유실되는 문제가 있어 휠 인라인 유지가 안전 (§6.5) VStack(alignment: .leading, spacing: 0) { - Text("하루 시작 시간") - RecordTimeWheel( - label: String(localized: "하루 시작 시간"), - selection: dayStartDate, - components: .hourAndMinute - ) + Button { + withAnimation(.snappy(duration: 0.25)) { + showsDayStartWheel.toggle() + } + } label: { + HStack { + Text("하루 시작 시간") + .foregroundStyle(.primary) + Spacer() + Text(dayStartDate.wrappedValue, format: .dateTime.hour().minute()) + .foregroundStyle(showsDayStartWheel ? AppTheme.green : .secondary) + Image(systemName: "chevron.down") + .font(.caption2.weight(.semibold)) + .foregroundStyle(.tertiary) + .rotationEffect(.degrees(showsDayStartWheel ? 180 : 0)) + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityLabel(Text("하루 시작 시간")) + .accessibilityValue(Text(dayStartDate.wrappedValue, format: .dateTime.hour().minute())) + if showsDayStartWheel { + RecordTimeWheel( + label: String(localized: "하루 시작 시간"), + selection: dayStartDate, + components: .hourAndMinute + ) + } } } header: { Text("날짜 기준")