Implement real-time sync between iOS and Apple Watch apps using WatchConnectivity framework. This replaces reliance on CloudKit which doesn't work reliably in simulators. - Add WatchConnectivityManager to both iOS and Watch targets - Sync workouts, splits, exercises, and logs between devices - Update iOS views to trigger sync on data changes - Add onChange observer to ExerciseView for live progress updates - Configure App Groups for shared container storage - Add Watch app views: WorkoutLogsView, WorkoutLogListView, ExerciseProgressView
100 lines
2.8 KiB
Swift
100 lines
2.8 KiB
Swift
//
|
|
// WorkoutLogsView.swift
|
|
// Workouts Watch App
|
|
//
|
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import CoreData
|
|
|
|
struct WorkoutLogsView: View {
|
|
@Environment(\.managedObjectContext) private var viewContext
|
|
@EnvironmentObject var connectivityManager: WatchConnectivityManager
|
|
|
|
@FetchRequest(
|
|
sortDescriptors: [NSSortDescriptor(keyPath: \Workout.start, ascending: false)],
|
|
animation: .default
|
|
)
|
|
private var workouts: FetchedResults<Workout>
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
List {
|
|
ForEach(workouts, id: \.objectID) { workout in
|
|
NavigationLink(destination: WorkoutLogListView(workout: workout)) {
|
|
WorkoutRow(workout: workout)
|
|
}
|
|
}
|
|
}
|
|
.overlay {
|
|
if workouts.isEmpty {
|
|
ContentUnavailableView(
|
|
"No Workouts",
|
|
systemImage: "list.bullet.clipboard",
|
|
description: Text("Tap sync or start a workout from iPhone.")
|
|
)
|
|
}
|
|
}
|
|
.navigationTitle("Workouts")
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button {
|
|
connectivityManager.requestSync()
|
|
} label: {
|
|
Image(systemName: "arrow.triangle.2.circlepath")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Workout Row
|
|
|
|
struct WorkoutRow: View {
|
|
@ObservedObject var workout: Workout
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(workout.split?.name ?? Split.unnamed)
|
|
.font(.headline)
|
|
.lineLimit(1)
|
|
|
|
HStack {
|
|
Text(workout.start.formatDate())
|
|
.font(.caption2)
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
|
|
statusIndicator
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusIndicator: some View {
|
|
switch workout.status {
|
|
case .completed:
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundColor(.green)
|
|
case .inProgress:
|
|
Image(systemName: "circle.dotted")
|
|
.foregroundColor(.orange)
|
|
case .notStarted:
|
|
Image(systemName: "circle")
|
|
.foregroundColor(.secondary)
|
|
case .skipped:
|
|
Image(systemName: "xmark.circle")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
WorkoutLogsView()
|
|
.environment(\.managedObjectContext, PersistenceController.preview.viewContext)
|
|
}
|