A new 'Workouts Mac' target (same bundle ID as iOS, universal purchase) with a NavigationSplitView shell over the shared data/sync layers: routine management with starter gallery and seed-fork follow, schedule editing (reminders stay iPhone-scheduled), and a browse-only exercise library with the animated figures and reference guides. Multi-writer stance: the Mac writes only routine and schedule documents (whole-document last-writer-wins) and never workout documents, whose per-log merge exists only on the watch ingest path. The Mac reconciles on window activation (throttled) since iCloud syncs while the app is closed, and flushes pending writes on deactivation.
56 lines
2.1 KiB
Swift
56 lines
2.1 KiB
Swift
//
|
|
// WorkoutsMacApp.swift
|
|
// Workouts Mac
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
|
|
@main
|
|
struct WorkoutsMacApp: App {
|
|
@State private var services = MacAppServices()
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
@State private var lastActivationReconcile: Date?
|
|
|
|
/// `.active` also fires on every window focus-in (⌘-Tab back), not just launch —
|
|
/// the metadata observer covers live changes while running, so the activation
|
|
/// reconcile only needs to catch changes synced while the app was completely
|
|
/// inactive for a while.
|
|
private static let activationReconcileInterval: TimeInterval = 5 * 60
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
MacRootGateView()
|
|
.environment(services)
|
|
.environment(services.syncEngine)
|
|
.modelContainer(services.container)
|
|
.task { await services.bootstrap() }
|
|
}
|
|
.defaultSize(width: 1000, height: 700)
|
|
// The macOS iCloud quirk: the ubiquity container can keep syncing while the
|
|
// app isn't running, so re-check on every foreground return rather than
|
|
// relying on the (iOS-only) metadata observer alone.
|
|
.onChange(of: scenePhase) { _, phase in
|
|
switch phase {
|
|
case .active:
|
|
if services.syncEngine.iCloudStatus == .unavailable {
|
|
Task { await services.syncEngine.connect() }
|
|
} else if services.syncEngine.iCloudStatus == .available {
|
|
let now = Date()
|
|
if lastActivationReconcile == nil
|
|
|| now.timeIntervalSince(lastActivationReconcile!) > Self.activationReconcileInterval {
|
|
lastActivationReconcile = now
|
|
Task { await services.syncEngine.rebuildCache() }
|
|
}
|
|
}
|
|
case .background, .inactive:
|
|
Task { await services.syncEngine.flushPendingWrites() }
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|