Refactor UI: move Splits to Settings, redesign ExerciseView

Schema & Models:
- Add notes, loadType, duration fields to WorkoutLog
- Align Watch schema with iOS (use duration Date instead of separate mins/secs)
- Add duration helper properties to Exercise and WorkoutLog

UI Changes:
- Remove Splits and Settings tabs, single Workout Logs view
- Add gear button in nav bar to access Settings as sheet
- Move Splits section into Settings view with inline list
- Redesign ExerciseView with read-only Plan/Notes tiles and Edit buttons
- Add PlanEditView and NotesEditView with Cancel/Save buttons
- Auto-dismiss ExerciseView when completing last set
- Navigate to ExerciseView when adding new exercise

Data Flow:
- Plan edits sync to both WorkoutLog and corresponding Exercise
- Changes propagate up navigation chain via CoreData
This commit is contained in:
2026-01-19 16:10:37 -05:00
parent c65040e756
commit 8b6250e4d6
14 changed files with 592 additions and 106 deletions

View File

@@ -0,0 +1,167 @@
//
// PlanEditView.swift
// Workouts
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
import CoreData
struct PlanEditView: View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.dismiss) private var dismiss
@ObservedObject var workoutLog: WorkoutLog
@State private var sets: Int = 3
@State private var reps: Int = 12
@State private var weight: Int = 0
@State private var durationMinutes: Int = 0
@State private var durationSeconds: Int = 0
@State private var selectedLoadType: LoadType = .weight
// Find the corresponding exercise in the split for syncing changes
private var correspondingExercise: Exercise? {
workoutLog.workout?.split?.exercisesArray.first { $0.name == workoutLog.exerciseName }
}
var body: some View {
NavigationStack {
Form {
// Sets and Reps side by side
Section {
HStack(spacing: 20) {
VStack {
Text("Sets")
.font(.headline)
.foregroundColor(.secondary)
Picker("Sets", selection: $sets) {
ForEach(1...7, id: \.self) { num in
Text("\(num)").tag(num)
}
}
.pickerStyle(.wheel)
.frame(height: 120)
}
.frame(maxWidth: .infinity)
VStack {
Text("Reps")
.font(.headline)
.foregroundColor(.secondary)
Picker("Reps", selection: $reps) {
ForEach(1...40, id: \.self) { num in
Text("\(num)").tag(num)
}
}
.pickerStyle(.wheel)
.frame(height: 120)
}
.frame(maxWidth: .infinity)
}
}
// Load Type Picker
Section {
Picker("Load Type", selection: $selectedLoadType) {
Text("Weight").tag(LoadType.weight)
Text("Time").tag(LoadType.duration)
}
.pickerStyle(.segmented)
}
// Weight or Time picker based on load type
Section {
if selectedLoadType == .weight {
VStack {
Text("Weight")
.font(.headline)
.foregroundColor(.secondary)
Picker("Weight", selection: $weight) {
ForEach(0...300, id: \.self) { num in
Text("\(num) lbs").tag(num)
}
}
.pickerStyle(.wheel)
.frame(height: 150)
}
} else {
HStack(spacing: 20) {
VStack {
Text("Mins")
.font(.headline)
.foregroundColor(.secondary)
Picker("Minutes", selection: $durationMinutes) {
ForEach(0...60, id: \.self) { num in
Text("\(num)").tag(num)
}
}
.pickerStyle(.wheel)
.frame(height: 120)
}
.frame(maxWidth: .infinity)
VStack {
Text("Secs")
.font(.headline)
.foregroundColor(.secondary)
Picker("Seconds", selection: $durationSeconds) {
ForEach(0...59, id: \.self) { num in
Text("\(num)").tag(num)
}
}
.pickerStyle(.wheel)
.frame(height: 120)
}
.frame(maxWidth: .infinity)
}
}
}
}
.navigationTitle("Edit Plan")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
saveChanges()
dismiss()
}
}
}
.onAppear {
sets = Int(workoutLog.sets)
reps = Int(workoutLog.reps)
weight = Int(workoutLog.weight)
durationMinutes = workoutLog.durationMinutes
durationSeconds = workoutLog.durationSeconds
selectedLoadType = workoutLog.loadTypeEnum
}
}
}
private func saveChanges() {
workoutLog.sets = Int32(sets)
workoutLog.reps = Int32(reps)
workoutLog.weight = Int32(weight)
workoutLog.durationMinutes = durationMinutes
workoutLog.durationSeconds = durationSeconds
workoutLog.loadTypeEnum = selectedLoadType
// Sync to corresponding exercise
if let exercise = correspondingExercise {
exercise.sets = workoutLog.sets
exercise.reps = workoutLog.reps
exercise.weight = workoutLog.weight
exercise.loadType = workoutLog.loadType
exercise.duration = workoutLog.duration
}
try? viewContext.save()
}
}