mycode/myApp/HaruDanim/IOS/Haru_DanimApp.swift
songyc macbook 2fb7b3c75a feat(settings): '짧은 기록 무시'를 iCloud 키-값 저장소로 전 기기 동기화 (1.1)
- 검사가 종료 실행 기기 기준이라 기기별 값이면 교차 사용(아이패드 시작→아이폰 종료)에서
  결과가 갈리던 문제 해소 — 값 자체를 모든 기기에서 통일
- SettingsCloudSync: 시작 시 iCloud 값 채택(없으면 로컬 값 시드), 외부 변경·포그라운드 복귀 때
  pull, 설정 변경 때 push(동일 값 가드). App Group 저장은 유지(위젯·인텐트 읽기 경로 무변경),
  CloudKit 스키마·프리미엄 토글과 무관 — ubiquity-kvstore 엔타이틀먼트만 추가
- 설정: 측정 섹션을 나눠 '짧은 기록 무시' 바로 밑에 전 기기 공통 안내 푸터,
  '장시간 측정 알림'은 기기별 유지 명시. 도움말도 갱신, en/ja 번역(missing 0·stale 0, 실렌더 확인)
- whats-new-1.1 3개 언어에 항목 추가, CLAUDE.md §6.8 갱신

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVeduv1eNdXjk1ay4tSgBg
2026-07-30 02:03:29 +09:00

99 lines
5.0 KiB
Swift

//
// Haru_DanimApp.swift
// Haru_Danim
//
// Created by on 7/7/26.
//
import AppIntents
import SwiftUI
import SwiftData
@main
struct Haru_DanimApp: App {
/// App Group DB (·App Intents , CloudKit )
private let container: ModelContainer
@Environment(\.scenePhase) private var scenePhase
init() {
#if DEBUG
// (-marketingTZ <IANA id> Documents/tz-probe.txt).
// ** SIMCTL_CHILD_TZ **
// (Marketing/README ). launch env ,
// setenv+resetSystemTimeZone·NSTimeZone.default FormatStyle ().
// .
if let tz = UserDefaults.standard.string(forKey: "marketingTZ") {
if let zone = TimeZone(identifier: tz) {
NSTimeZone.default = zone //
}
let probe = "current=\(TimeZone.current.identifier) default=\(NSTimeZone.default.identifier) now=\(Date.now.formatted(date: .abbreviated, time: .standard))"
try? probe.write(to: URL.documentsDirectory.appending(path: "tz-probe.txt"),
atomically: true, encoding: .utf8)
}
#endif
// ( shared )
PremiumManager.bootstrap(provider: StoreKitEntitlementProvider())
SettingsMigration.runIfNeeded()
// " " iCloud -
SettingsCloudSync.shared.start()
container = DataStore.shared
DataStore.ensureUniqueEntityIDs(context: container.mainContext)
// · UI : (@Model) 1
LocalPrefs.adoptLegacyModelValuesIfNeeded(context: container.mainContext)
// / Live Activity·
IntentHooks.afterMutation = { context in
LiveActivityManager.sync(context: context)
WatchSyncManager.shared.pushSnapshot()
}
WatchSyncManager.shared.activate()
// CloudKit ( · ) ··
CloudSyncMonitor.shared.start(container: container)
// (\(\.$action) ) /
HaruDanimShortcuts.updateAppShortcutParameters()
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
.onChange(of: scenePhase) { _, newPhase in
// (/ )
WatchSyncManager.shared.pushSnapshot()
if newPhase == .background {
// ( ):
DiaryLock.shared.relock()
}
if newPhase == .active {
//
// ·· ' '
Self.evaluateEndedGoals(context: container.mainContext)
// ( ) Live Activity +
//
LiveActivityManager.sync(context: container.mainContext)
}
}
}
/// .
/// (GoalListView.onAppear) / .
/// evaluateIfEnded ' ' .
@MainActor
private static func evaluateEndedGoals(context: ModelContext) {
let inProgress = GoalStatus.inProgress.rawValue
let goals = (try? context.fetch(FetchDescriptor<Goal>(
predicate: #Predicate { $0.statusRaw == inProgress }
))) ?? []
guard !goals.isEmpty else { return }
let math = DayMath()
var changed = false
for goal in goals {
let before = goal.status
goal.evaluateIfEnded(math: math)
if goal.status != before { changed = true }
}
if changed { DataChange.commit(context: context) }
}
}