mycode/myApp/HaruDanim/WatchShared/WatchPayload.swift
songyc macbook f9f1898d15 feat(watch): 애플워치 앱 + WCSession 실시간 연동 (CLAUDE.md §9.1)
- WatchShared/WatchPayload: iOS·워치·워치위젯 3개 타깃이 공유하는 Codable 스냅숏
  (꼬리표/행동/목표·다짐 진행률/측정 중 대표 행동 + 추가 개수/프리미엄)
- iOS WatchSyncManager: 워치의 실행·새로 고침 명령 처리(ActionRunner 공용) 후
  Live Activity·홈 위젯 갱신, 데이터 변경 시 updateApplicationContext로 푸시
- 워치 앱: 꼬리표 목록 → 행동 목록 → 탭으로 실행(시간형 토글/횟수형 +1),
  측정 중 실시간 타이머, 대표 행동 표시는 다이나믹 아일랜드 설정과 동일 기준
- 미결제 시 프리미엄 잠금, iPhone 연결 불가 안내
- 수신 스냅숏을 워치 App Group에 캐시 (컴플리케이션용)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
2026-07-10 09:00:01 +09:00

119 lines
3.6 KiB
Swift

//
// WatchPayload.swift
// Haru_Danim
//
// iPhone Apple Watch (CLAUDE.md §9)
// - iOS + + Codable
// - iPhone WCSession , UI
// (App Group defaults )
//
import Foundation
nonisolated enum WatchSync {
/// WCSession / Data
static let snapshotKey = "snapshot"
/// App Group defaults ()
static let cachedSnapshotKey = "watch.cachedSnapshot"
///
static let commandKey = "command"
static let actionIDKey = "actionID"
///
static let runCommand = "run"
static let refreshCommand = "refresh"
}
///
nonisolated struct WatchSnapshot: Codable {
var generatedAt: Date = .now
var isPremium: Bool = false
var tags: [WatchTagInfo] = []
var actions: [WatchActionInfo] = []
var goals: [WatchGoalInfo] = []
/// ( , CLAUDE.md §9.2-1)
var runningName: String?
var runningSymbol: String?
var runningStartAt: Date?
///
var extraRunningCount: Int = 0
func encoded() -> Data? {
try? JSONEncoder().encode(self)
}
static func decode(_ data: Data) -> WatchSnapshot? {
try? JSONDecoder().decode(WatchSnapshot.self, from: data)
}
}
nonisolated struct WatchTagInfo: Codable, Identifiable, Hashable {
var id: UUID
var name: String
var colorHex: String
}
nonisolated struct WatchActionInfo: Codable, Identifiable, Hashable {
var id: UUID
var name: String
var symbolName: String
var colorHex: String
var isCount: Bool
/// ( ) , ( )
var todayValue: Double
var isRunning: Bool
/// ( - )
var tickingBase: Date?
/// id ( ' ' )
var tagIDs: [UUID]
}
nonisolated struct WatchGoalInfo: Codable, Identifiable, Hashable {
var id: UUID
var title: String
var symbolName: String
var colorHex: String
var dayRatio: Double
var weekRatio: Double
var monthRatio: Double
var quests: [WatchQuestInfo]
func ratio(forSpanRaw raw: String) -> Double {
switch raw {
case "week": return weekRatio
case "month": return monthRatio
default: return dayRatio
}
}
}
nonisolated struct WatchQuestInfo: Codable, Identifiable, Hashable {
var id: UUID
var name: String
var symbolName: String
var colorHex: String
var dayRatio: Double
var weekRatio: Double
var monthRatio: Double
/// ' ' ( , CLAUDE.md §9.2-3)
var isAtMost: Bool
var dayAchieved: Bool
var weekAchieved: Bool
var monthAchieved: Bool
func ratio(forSpanRaw raw: String) -> Double {
switch raw {
case "week": return weekRatio
case "month": return monthRatio
default: return dayRatio
}
}
func achieved(forSpanRaw raw: String) -> Bool {
switch raw {
case "week": return weekAchieved
case "month": return monthAchieved
default: return dayAchieved
}
}
}