2026-07-23 사용성 감사(A그룹 5건 + 지시 추가분)를 한 번에 반영.
[테마] '시스템 설정 따름' 추가 + 기본값 light→system. "system"은 스킴을
강제하지 않아(preferredColorScheme nil) 앱·위젯('앱 일치' 옵션,
WidgetThemeOption.scheme: ColorScheme?)·DEBUG 위젯 미리보기까지
시스템 라이트/다크를 그대로 따른다
[온보딩] 첫 실행 3장 소개(OnboardingView — 행동→목표·다짐→정리).
데이터가 하나도 없고 본 적 없을 때만 1회(건너뛰기 포함 재표시 없음,
onboarding.done), 시드·마케팅 촬영 플로우는 자동 건너뜀.
설정 → 지원 '앱 소개 다시 보기'로 재열람
[일기 잠금] 설정 → 일기(iPad 전용 표시) 토글 — DiaryLock이
.deviceOwnerAuthentication으로 Face ID/Touch ID/Optic ID+암호 폴백을
기기별 분기 없이 처리. 일기 탭 진입 게이트(DiaryLockGateView),
백그라운드 재잠금, 토글 변경 시 인증 요구, 잠글 수단 없는 기기는
통과(영구 잠김 방지). NSFaceIDUsageDescription 추가(Info.plist+
InfoPlist.xcstrings ko/en/ja)
[통계] 이전 기간 비교 카드(맨 위) — 총 시간·횟수를 어제/지난주/지난달과
비교(▲▼%, 이전 기록 없으면 상태 문구). 이전 기간은 관계 기반
Aggregator로 직접 집계, 필터 반영. 화면 전용(내보내기 미포함 의도)
[스플래시] 1.2→0.7초 단축 (하루에도 여러 번 여는 앱)
[워치] 행동 실행 탭 시 WKInterfaceDevice.play(.click) 햅틱 — 화면을
안 보고 탭해도 접수 확인
[컴플리케이션] rectangular 빈 상태에 해결 안내("목표 편집에서
'애플워치에서 보기'를 켜면 나타나요") — 옵트인 발견성 보완
[문구] 행동 편집기 꼬리표 색 규칙(여러 개면 가장 먼저 만든 꼬리표),
새로고침 버튼 accessibilityHint
- 도움말 3주제 추가(새로고침 버튼·일기 잠금·이전 기간과 비교)
- 신규 문구 31키 en/ja 완역 + 워치 카탈로그 2키, missing/stale 0
- CLAUDE.md §6·§6.6·§6.7·§6.8·§10·§11·§14 갱신, DEBUG 인자
-showOnboarding·-diaryLockScreen 추가
- 검증: Debug/워치/Store 빌드, 온보딩·설정·비교 카드·잠금 게이트·
컴플리케이션 스크린샷
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
70 lines
2.7 KiB
Swift
70 lines
2.7 KiB
Swift
//
|
||
// 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
|
||
|
||
/// 다른 기기에서 바뀐 데이터를 다시 불러오는 작은 툴바 버튼
|
||
struct RefreshButton: View {
|
||
private let refresh = AppRefresh.shared
|
||
|
||
var body: some View {
|
||
Button {
|
||
refresh.refreshNow()
|
||
} label: {
|
||
if refresh.isRefreshing {
|
||
ProgressView()
|
||
} else {
|
||
Image(systemName: "arrow.clockwise")
|
||
}
|
||
}
|
||
.disabled(refresh.isRefreshing)
|
||
.accessibilityLabel(Text("새로고침"))
|
||
.accessibilityHint(Text("다른 기기에서 바뀐 내용을 다시 불러오고 위젯도 갱신해요"))
|
||
}
|
||
}
|