wip
This commit is contained in:
@ -21,7 +21,7 @@ struct SplitAddEditView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Form {
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
|
198
Workouts/Views/Splits/SplitDetailView.swift
Normal file
198
Workouts/Views/Splits/SplitDetailView.swift
Normal file
@ -0,0 +1,198 @@
|
||||
//
|
||||
// SplitDetailView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/25/25 at 3:27 PM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct SplitDetailView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State 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
|
||||
@State private var showingDeleteConfirmation: Bool = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section (header: Text("What is a Split?")) {
|
||||
Text("A “split” is simply how you divide (or “split up”) your weekly training across different days. Instead of working every muscle group every session, you assign certain muscle groups, movement patterns, or training emphases to specific days.")
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Section (header: Text("Exercises")) {
|
||||
List {
|
||||
if let assignments = split.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button ("Delete This Split", role: .destructive) {
|
||||
showingDeleteConfirmation = true
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
.navigationTitle("\(split.name)")
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("Start This Split") {
|
||||
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)
|
||||
})
|
||||
.sheet (isPresented: $showingAddSheet) {
|
||||
ExercisePickerView(onExerciseSelected: { exerciseNames in
|
||||
let splitId = split.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete This Split?",
|
||||
isPresented: $showingDeleteConfirmation,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
modelContext.delete(split)
|
||||
try? modelContext.save()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ struct SplitsView: View {
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||
SortableForEach($splits, allowReordering: $allowSorting) { split, dragging in
|
||||
NavigationLink {
|
||||
ExerciseListView(split: split)
|
||||
SplitDetailView(split: split)
|
||||
} label: {
|
||||
SplitItem(
|
||||
name: split.name,
|
||||
|
@ -40,7 +40,7 @@ struct WorkoutLogListView: View {
|
||||
CheckboxListItem(
|
||||
status: workoutLogStatus,
|
||||
title: log.exerciseName,
|
||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||
subtitle: "\(log.sets) × \(log.reps) reps × \(log.weight) lbs"
|
||||
)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
||||
let status = log.status ?? WorkoutStatus.notStarted
|
||||
@ -89,6 +89,21 @@ struct WorkoutLogListView: View {
|
||||
}
|
||||
|
||||
}
|
||||
.onMove(perform: { indices, destination in
|
||||
var workoutLogArray = Array(sortedWorkoutLogs)
|
||||
workoutLogArray.move(fromOffsets: indices, toOffset: destination)
|
||||
for (index, log) in workoutLogArray.enumerated() {
|
||||
log.order = index
|
||||
}
|
||||
if let modelContext = workoutLogArray.first?.modelContext {
|
||||
do {
|
||||
try modelContext.save()
|
||||
} catch {
|
||||
print("Error saving after reordering: \(error)")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,14 +186,14 @@ struct WorkoutLogListView: View {
|
||||
if let _ = workout.logs?.first(where: { $0.status != .completed }) {
|
||||
if let notStartedLogs = workout.logs?.filter({ $0.status == .notStarted }) {
|
||||
if notStartedLogs.count == workout.logs?.count ?? 0 {
|
||||
workout.status = .notStarted
|
||||
workout.status = WorkoutStatus.notStarted.rawValue
|
||||
}
|
||||
}
|
||||
if let _ = workout.logs?.first(where: { $0.status == .inProgress }) {
|
||||
workout.status = .inProgress
|
||||
workout.status = WorkoutStatus.inProgress.rawValue
|
||||
}
|
||||
} else {
|
||||
workout.status = .completed
|
||||
workout.status = WorkoutStatus.completed.rawValue
|
||||
workout.end = Date()
|
||||
}
|
||||
try? modelContext.save()
|
||||
|
@ -7,6 +7,8 @@
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum WorkoutStatus: Int, Codable {
|
||||
case notStarted = 1
|
||||
case inProgress = 2
|
||||
|
@ -25,12 +25,12 @@ struct WorkoutEditView: View {
|
||||
}
|
||||
|
||||
Section (header: Text("Status")) {
|
||||
Text("\(workout.status?.name ?? WorkoutStatus.unnamed)")
|
||||
Text("\(workout.statusName)")
|
||||
}
|
||||
|
||||
Section (header: Text("Start/End")) {
|
||||
DatePicker("Started", selection: $workout.start)
|
||||
if workout.status == .completed {
|
||||
if workout.status == WorkoutStatus.completed.rawValue {
|
||||
DatePicker("Ended", selection: $workoutEndDate)
|
||||
}
|
||||
}
|
||||
@ -46,7 +46,7 @@ struct WorkoutEditView: View {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
try? modelContext.save()
|
||||
if workout.status == .completed {
|
||||
if workout.status == WorkoutStatus.completed.rawValue {
|
||||
workout.end = workoutEndDate
|
||||
}
|
||||
dismiss()
|
||||
|
@ -37,8 +37,8 @@ struct WorkoutListView: View {
|
||||
CalendarListItem(
|
||||
date: workout.start,
|
||||
title: workout.split?.name ?? Split.unnamed,
|
||||
subtitle: "\(workout.status == .completed ? workout.start.humanTimeInterval(to: (workout.end ?? Date())) : "\(workout.start.formattedDate()) - \(workout.status?.name ?? WorkoutStatus.unnamed)" )",
|
||||
subtitle2: "\(workout.status?.name ?? WorkoutStatus.unnamed)"
|
||||
subtitle: "\(workout.status == WorkoutStatus.completed.rawValue ? workout.start.humanTimeInterval(to: (workout.end ?? Date())) : "\(workout.start.formattedDate()) - \(workout.statusName)" )",
|
||||
subtitle2: "\(workout.statusName)"
|
||||
)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
|
Reference in New Issue
Block a user