// // AppRefresh.swift // Haru_Danim // // 수동 새로고침 (모음 탭 버튼) // - iCloud 동기화로 다른 기기에서 바뀐 데이터가 화면에 바로 안 보일 때, 앱을 껐다 켜는 // 대신 쓰는 버튼. token을 바꾸면 루트 뷰 identity가 리셋되어 모든 @Query가 스토어에서 // 다시 조회된다 (재시작과 같은 효과, 데이터는 그대로). // - 원격 변경 자동 반영(CloudSyncMonitor)은 위젯·워치·라이브 액티비티만 갱신한다. // 화면 전체 리셋은 입력 중인 시트를 닫아버릴 수 있어 수동 버튼으로만 한다. // ⚠️ 이 버튼은 "로컬 스토어에 이미 도착한" 변경을 화면에 반영할 뿐, CloudKit 가져오기를 // 강제할 공식 API는 없다. 가져오기 자체는 무음 푸시(UIBackgroundModes: // remote-notification, IOS/Info.plist)가 트리거한다 — 이 모드가 빠져 있으면 앱 // 실행/포그라운드 복귀 때만 가져와서 새로고침을 눌러도 옛 데이터가 보인다(수정된 버그). // import Foundation import Observation import SwiftData import WidgetKit @MainActor @Observable final class AppRefresh { static let shared = AppRefresh() /// RootNavigationView가 .id로 사용 — 바뀌면 화면 전체가 다시 만들어진다 private(set) var token = UUID() private(set) var isRefreshing = false func refreshNow() { guard !isRefreshing else { return } isRefreshing = true token = UUID() Task { @MainActor in // 화면 재구성과 함께 파생 상태도 최신 데이터로 맞춘다 LiveActivityManager.sync(context: DataStore.shared.mainContext) WidgetCenter.shared.reloadAllTimelines() WatchSyncManager.shared.pushSnapshot() // 버튼이 눌렸다는 피드백이 보일 만큼만 잠깐 유지 try? await Task.sleep(for: .milliseconds(700)) isRefreshing = false } } } // MARK: - 새로고침 버튼 (모음 탭 툴바) import SwiftUI /// 다른 기기에서 바뀐 데이터를 다시 불러오는 작은 툴바 버튼. /// 쓰임새(iCloud 동기화 반영·위젯/워치 갱신)가 전부 프리미엄 기능이라 /// 프리미엄일 때만 보인다 — 무료에선 누를 이유가 없는 버튼. /// ⚠️ 프리미엄 판정은 반드시 이 body 안에서 — MainView의 .toolbar { if ... } 조건은 /// @Observable 변경 시 재평가가 보장되지 않아, 실행 중 프리미엄 전환(설정 토글·구매) /// 후 버튼이 앱 재시작 전까지 안 나타나는 실기기 결함이 있었다(2026-07-23) struct RefreshButton: View { private let refresh = AppRefresh.shared var body: some View { if PremiumManager.shared.isPremium { Button { refresh.refreshNow() } label: { if refresh.isRefreshing { ProgressView() } else { Image(systemName: "arrow.clockwise") } } .disabled(refresh.isRefreshing) .accessibilityLabel(Text("새로고침")) .accessibilityHint(Text("다른 기기에서 바뀐 내용을 다시 불러오고 위젯도 갱신해요")) } } }