- 아이폰 일기 개방: AppTab.phoneCases·radialOrder, '그리기' 토글(손가락)·가로 도구줄, 일기 잠금 설정 전 기기 - 건강 카드: DiarySection.health(목록 끝), DiaryHealthCard(지표 선택 공용화·타일 상속·맥 숨김), 인쇄 요약 동승 - 타임테이블 실구간: HealthIntervals 캐시(App Group 400일, CloudKit 금지), injectingHealthBlocks(수면 인디고·운동 주황), 필터 '건강 데이터' 토글+맥 안내 - 수면 표시 구간: SleepWindowPrefs(전체+요일별), 평문 sleep 키=유효 구간 값, 백필 서명 매핑 포함, 새 수면 다짐 기본값 시드 - 실측 수정: 일기 목표 카드 건강 다짐 값 단위, ja 건강 칩 말줄임 - 검증 61/20/27/10 ALL PASS·3종 빌드·카탈로그 0/0·시각 QA 10여 장(26.5/18.5/맥 분기) - 마케팅: 06-bubble 재촬영, 10-week→10-diary 교체, 아이패드 03 재촬영(3언어), whats-new/설명 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WtZwRRjbtM9pZJDM4FkTq6
92 lines
3.2 KiB
Swift
92 lines
3.2 KiB
Swift
//
|
|
// SidebarRootView.swift
|
|
// Haru_Danim
|
|
//
|
|
// iPad · Mac(Designed for iPad) 전용 사이드바 내비게이션.
|
|
// iPhone의 MainTabView와 화면(rootView)은 그대로 공유하고 껍데기만 NavigationSplitView로 바꾼다.
|
|
// iPhone 쪽 UI/로직은 이 파일이 전혀 건드리지 않는다.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
import UIKit
|
|
|
|
/// 넓은 화면(iPad, Mac Designed for iPad) 전용 분기 판정.
|
|
/// 크기 클래스 대신 기기 종류로 판정해 iPhone(가로 모드 포함)은 항상 기존 UI를 유지한다.
|
|
enum DeviceLayout {
|
|
static let isPad = UIDevice.current.userInterfaceIdiom == .pad
|
|
/// 아이폰 여부 (1.5 추가분 — 일기 노트의 '그리기' 토글·손가락 필기 분기용)
|
|
static let isPhone = UIDevice.current.userInterfaceIdiom == .phone
|
|
/// 맥에서 실행 중인 아이패드 앱(Designed for iPad) 여부.
|
|
/// 아이패드 포인트가 맥 화면에 축소 매핑돼 전반적으로 작게 보이므로,
|
|
/// 맥에서만 글자 크기·모음 탭 카드/버튼 폭을 키운다 (아이폰·아이패드 무영향)
|
|
static let isMac: Bool = {
|
|
#if DEBUG
|
|
// 검증용: -forceMacLayout YES → 아이패드 시뮬레이터에서 맥 분기 강제 (§14)
|
|
if UserDefaults.standard.bool(forKey: "forceMacLayout") { return true }
|
|
#endif
|
|
return ProcessInfo.processInfo.isiOSAppOnMac
|
|
}()
|
|
}
|
|
|
|
struct SidebarRootView: View {
|
|
@State private var router: AppRouter
|
|
|
|
init() {
|
|
let router = AppRouter()
|
|
router.usesSidebar = true
|
|
_router = State(initialValue: router)
|
|
}
|
|
|
|
private var selectedTab: AppTab {
|
|
AppTab(rawValue: router.tabSelection) ?? .main
|
|
}
|
|
|
|
/// 사이드바 선택과 라우터의 탭 선택(String)을 잇는 다리
|
|
private var sidebarSelection: Binding<AppTab?> {
|
|
Binding {
|
|
AppTab(rawValue: router.tabSelection) ?? .main
|
|
} set: { newValue in
|
|
if let newValue {
|
|
router.tabSelection = newValue.rawValue
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
List(selection: sidebarSelection) {
|
|
Section {
|
|
ForEach(AppTab.sidebarCases) { tab in
|
|
Label {
|
|
Text(tab.label)
|
|
} icon: {
|
|
Image(systemName: tab.symbol)
|
|
.foregroundStyle(AppTheme.green)
|
|
}
|
|
.tag(tab)
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.sidebar)
|
|
.navigationTitle("하루 다님")
|
|
.navigationSplitViewColumnWidth(min: 190, ideal: 230, max: 300)
|
|
} detail: {
|
|
NavigationStack {
|
|
selectedTab.rootView
|
|
}
|
|
// 탭을 바꾸면 상세 스택을 처음 화면으로 초기화
|
|
.id(selectedTab)
|
|
}
|
|
.environment(router)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
SidebarRootView()
|
|
.modelContainer(for: [
|
|
Tag.self, Action.self, TimeSession.self,
|
|
CountEntry.self, Goal.self, Quest.self,
|
|
], inMemory: true)
|
|
}
|