197 lines
8.0 KiB
Swift
197 lines
8.0 KiB
Swift
//
|
||
// SplitExercisesListView.swift
|
||
// Workouts
|
||
//
|
||
// Created by rzen on 7/18/25 at 8:38 AM.
|
||
//
|
||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||
//
|
||
|
||
import SwiftUI
|
||
import SwiftData
|
||
|
||
struct ExerciseListView: View {
|
||
@Environment(\.modelContext) private var modelContext
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
// Use a @Query to observe the Split and its exercises
|
||
@Query private var splits: [Split]
|
||
private var split: Split
|
||
|
||
@State private var showingAddSheet: Bool = false
|
||
@State private var itemToEdit: Exercise? = nil
|
||
@State private var itemToDelete: Exercise? = nil
|
||
@State private var createdWorkout: Workout? = nil
|
||
|
||
// Initialize with a Split and set up a query to observe it
|
||
init(split: Split) {
|
||
self.split = split
|
||
// Create a predicate to fetch only this specific split
|
||
let splitId = split.persistentModelID
|
||
self._splits = Query(filter: #Predicate<Split> { s in
|
||
s.persistentModelID == splitId
|
||
})
|
||
}
|
||
|
||
var body: some View {
|
||
// Use the first Split from our query if available, otherwise fall back to the original split
|
||
let currentSplit = splits.first ?? split
|
||
|
||
NavigationStack {
|
||
Form {
|
||
List {
|
||
if let assignments = currentSplit.exercises, !assignments.isEmpty {
|
||
let sortedAssignments = assignments.sorted(by: { $0.order == $1.order ? $0.name < $1.name : $0.order < $1.order })
|
||
|
||
ForEach(sortedAssignments) { item in
|
||
ListItem(
|
||
title: item.name,
|
||
subtitle: "\(item.sets) × \(item.reps) × \(item.weight) lbs \(item.order)"
|
||
)
|
||
.swipeActions {
|
||
Button {
|
||
itemToDelete = item
|
||
} label: {
|
||
Label("Delete", systemImage: "trash")
|
||
}
|
||
.tint(.red)
|
||
Button {
|
||
itemToEdit = item
|
||
} label: {
|
||
Label("Edit", systemImage: "pencil")
|
||
}
|
||
.tint(.indigo)
|
||
}
|
||
}
|
||
.onMove(perform: { indices, destination in
|
||
var exerciseArray = Array(sortedAssignments)
|
||
exerciseArray.move(fromOffsets: indices, toOffset: destination)
|
||
for (index, exercise) in exerciseArray.enumerated() {
|
||
exercise.order = index
|
||
}
|
||
if let modelContext = exerciseArray.first?.modelContext {
|
||
do {
|
||
try modelContext.save()
|
||
} catch {
|
||
print("Error saving after reordering: \(error)")
|
||
}
|
||
}
|
||
})
|
||
|
||
Button {
|
||
showingAddSheet = true
|
||
} label: {
|
||
ListItem(title: "Add Exercise")
|
||
}
|
||
|
||
} else {
|
||
Text("No exercises added yet.")
|
||
Button(action: { showingAddSheet.toggle() }) {
|
||
ListItem(title: "Add Exercise")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("\(currentSplit.name)")
|
||
}
|
||
.toolbar {
|
||
ToolbarItem(placement: .primaryAction) {
|
||
Button("Start This Split") {
|
||
let split = currentSplit
|
||
let workout = Workout(start: Date(), end: Date(), split: split)
|
||
modelContext.insert(workout)
|
||
if let exercises = split.exercises {
|
||
for assignment in exercises {
|
||
let workoutLog = WorkoutLog(
|
||
workout: workout,
|
||
exerciseName: assignment.name,
|
||
date: Date(),
|
||
order: assignment.order,
|
||
sets: assignment.sets,
|
||
reps: assignment.reps,
|
||
weight: assignment.weight
|
||
)
|
||
modelContext.insert(workoutLog)
|
||
}
|
||
}
|
||
try? modelContext.save()
|
||
|
||
// Set the created workout to trigger navigation
|
||
createdWorkout = workout
|
||
}
|
||
}
|
||
}
|
||
.navigationDestination(item: $createdWorkout, destination: { workout in
|
||
WorkoutLogListView(workout: workout)
|
||
})
|
||
// .toolbar {
|
||
// ToolbarItem(placement: .navigationBarTrailing) {
|
||
// Button(action: { showingAddSheet.toggle() }) {
|
||
// Image(systemName: "plus")
|
||
// }
|
||
// }
|
||
// }
|
||
.sheet (isPresented: $showingAddSheet) {
|
||
ExercisePickerView(onExerciseSelected: { exerciseNames in
|
||
let splitId = currentSplit.persistentModelID
|
||
print("exerciseNames: \(exerciseNames)")
|
||
if exerciseNames.count == 1 {
|
||
itemToEdit = Exercise(
|
||
split: split,
|
||
exerciseName: exerciseNames.first ?? "Exercise.unnamed",
|
||
order: 0,
|
||
sets: 3,
|
||
reps: 10,
|
||
weight: 40
|
||
)
|
||
} else {
|
||
for exerciseName in exerciseNames {
|
||
var duplicateExercise: [Exercise]? = nil
|
||
do {
|
||
duplicateExercise = try modelContext.fetch(FetchDescriptor<Exercise>(predicate: #Predicate{ exercise in
|
||
exerciseName == exercise.name && splitId == exercise.split?.persistentModelID
|
||
}))
|
||
} catch {
|
||
print("ERROR: failed to fetch \(exerciseName)")
|
||
}
|
||
|
||
if let dup = duplicateExercise, dup.count > 0 {
|
||
print("Skipping duplicate \(exerciseName) found \(dup.count) duplicate(s)")
|
||
} else {
|
||
print("Creating \(exerciseName) for \(split.name)")
|
||
modelContext.insert(Exercise(
|
||
split: split,
|
||
exerciseName: exerciseName,
|
||
order: 0,
|
||
sets: 3,
|
||
reps: 10,
|
||
weight: 40
|
||
))
|
||
}
|
||
}
|
||
}
|
||
try? modelContext.save()
|
||
}, allowMultiSelect: true)
|
||
}
|
||
.sheet(item: $itemToEdit) { item in
|
||
ExerciseAddEditView(model: item)
|
||
}
|
||
.confirmationDialog(
|
||
"Delete Exercise?",
|
||
isPresented: .constant(itemToDelete != nil),
|
||
titleVisibility: .visible
|
||
) {
|
||
Button("Delete", role: .destructive) {
|
||
if let item = itemToDelete {
|
||
withAnimation {
|
||
modelContext.delete(item)
|
||
try? modelContext.save()
|
||
itemToDelete = nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|