// // Settings.swift // Haru_Danim // // 앱 설정 키, 무료 제한 (CLAUDE.md §6.6, §7) // import Foundation enum SettingsKeys { /// "light" | "dark" static let theme = "settings.theme" /// "ko" | "en" | "ja" static let language = "settings.language" /// Calendar.weekday 값 (1=일요일 ... 7=토요일). 기본값 2(월요일) static let weekStartWeekday = "settings.weekStartWeekday" /// 자정 기준 분 단위 오프셋. 기본값 0 (00:00) static let dayStartMinutes = "settings.dayStartMinutes" /// "latest"(가장 나중에 시작) | "earliest"(가장 먼저 시작). 기본값 latest static let liveActivityMode = "settings.liveActivityMode" // 프리미엄 여부는 Shared/Premium.swift의 PremiumGate.cacheKey (App Group defaults) 사용 /// 메인 탭 그리드 한 줄에 표시할 행동 버튼 개수. 기본값 3 static let gridColumns = "settings.gridColumns" /// 이 시간(초)보다 짧게 측정된 세션은 기록하지 않음. 0 = 사용 안 함 static let minSessionSeconds = "settings.minSessionSeconds" /// 측정이 이 시간(시)을 넘기면 로컬 알림 (끄는 걸 잊음 방지). 0 = 사용 안 함 static let longSessionAlertHours = "settings.longSessionAlertHours" /// 탭바에 직접 노출할 탭(더보기 제외 1~3개). AppTab rawValue를 쉼표로 연결한 문자열 static let visibleTabs = "settings.visibleTabs" /// 모음 탭 목표 카드 표시 방식. GoalCardStyle rawValue ("perQuest" | "combined") static let goalCardStyle = "settings.goalCardStyle" /// iPhone 내비게이션 방식. NavStyle rawValue ("tabBar" | "radial"). 기기 로컬(비동기화) static let navStyle = "settings.navStyle" /// 글래스 버블 메뉴에 펼칠 탭 순서. AppTab rawValue를 쉼표로 연결. 기기 로컬(비동기화) static let radialTabOrder = "settings.radialTabOrder" /// 글래스 버블 FAB '두 번 눌러 바로 이동' 대상 탭. AppTab rawValue, 빈 값 = 꺼짐. 기기 로컬(비동기화) static let radialDoubleTapTab = "settings.radialDoubleTapTab" } /// iPhone 전용 내비게이션 방식 (iPad·Mac 사이드바에는 적용되지 않음, 기기 로컬 설정) enum NavStyle: String, CaseIterable, Identifiable { /// 기본 하단 탭 바 (노출 탭 + 더보기) case tabBar /// 글래스 버블(Glass Bubbles) 메뉴 — rawValue는 저장 호환을 위해 radial 유지 case radial var id: String { rawValue } var label: String { switch self { case .tabBar: return String(localized: "기본 하단 탭 바") case .radial: return String(localized: "글래스 버블 메뉴") } } } /// 모음 탭 목표 진행 현황 카드의 표시 방식 (선택된 모든 목표 카드에 일괄 적용) enum GoalCardStyle: String, CaseIterable, Identifiable { /// 다짐(최대 3개)별로 하루/주간/월간 진행률을 각각 표시 case perQuest /// 소속 다짐 전체의 하루/주간/월간 진행률을 합산(평균)해 한 번에 표시 case combined var id: String { rawValue } var label: String { switch self { case .perQuest: return String(localized: "다짐별로 각각") case .combined: return String(localized: "전체 다짐 합산") } } } enum TabBarConfig { /// 기본 노출 탭: 메인 · 행동 · 기록 (+더보기) static let defaultVisibleTabs = "main,action,history" static let maxVisible = 3 static let minVisible = 1 } /// 장시간 측정 알림 옵션 선택지 (시간) enum LongSessionAlertOption { static let choices: [(hours: Int, label: String)] = [ (0, String(localized: "사용 안 함")), (1, String(localized: "1시간")), (2, String(localized: "2시간")), (3, String(localized: "3시간")), (6, String(localized: "6시간")), (12, String(localized: "12시간")), ] } /// 짧은 기록 무시 옵션 선택지 (초) enum MinSessionOption { static let choices: [(seconds: Int, label: String)] = [ (0, String(localized: "사용 안 함")), (3, String(localized: "3초")), (5, String(localized: "5초")), (10, String(localized: "10초")), (30, String(localized: "30초")), (60, String(localized: "1분")), ] } enum LiveActivityMode: String, CaseIterable, Identifiable { case latest case earliest var id: String { rawValue } var label: String { switch self { case .latest: return String(localized: "가장 나중에 시작한 행동") case .earliest: return String(localized: "가장 먼저 시작한 행동") } } } enum AppLanguage: String, CaseIterable, Identifiable { case ko, en, ja var id: String { rawValue } var label: String { switch self { case .ko: return "한국어" case .en: return "English" case .ja: return "日本語" } } } // 무료 사용 한도(FreeLimits)는 Shared/Premium.swift로 이동 (위젯·인텐트에서도 사용) /// 집계에 필요한 설정 스냅숏 struct TrackingSettings { var weekStartWeekday: Int var dayStartMinutes: Int static func current() -> TrackingSettings { // App Group 우선(위젯·인텐트와 공유), 이관 전 값은 standard에서 폴백 let group = AppGroup.defaults let standard = UserDefaults.standard func intValue(_ key: String, default defaultValue: Int) -> Int { (group.object(forKey: key) ?? standard.object(forKey: key)) as? Int ?? defaultValue } return TrackingSettings( weekStartWeekday: intValue(SettingsKeys.weekStartWeekday, default: 2), dayStartMinutes: intValue(SettingsKeys.dayStartMinutes, default: 0) ) } var calendar: Calendar { var cal = Calendar.current cal.firstWeekday = weekStartWeekday return cal } }