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:
167
Workouts/Views/WorkoutLogs/PlanEditView.swift
Normal file
167
Workouts/Views/WorkoutLogs/PlanEditView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user