Files
workouts/Workouts/Views/Splits/SplitDetailView.swift
rzen 13313a32d3 Add CoreData-based workout tracking app with iOS and watchOS targets
- Migrate from SwiftData to CoreData with CloudKit sync
- Add core models: Split, Exercise, Workout, WorkoutLog
- Implement tab-based UI: Workout Logs, Splits, Settings
- Add SF Symbols picker for split icons
- Add exercise picker filtered by split with exclusion of added exercises
- Integrate IndieAbout for settings/about section
- Add Yams for YAML exercise definition parsing
- Include starter exercise libraries (bodyweight, Planet Fitness)
- Add Date extensions for formatting (formattedTime, isSameDay)
- Format workout date ranges to show time-only for same-day end dates
- Add build number update script
- Add app icons
2026-01-19 06:42:15 -05:00

152 lines
5.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// SplitDetailView.swift
// Workouts
//
// Created by rzen on 7/25/25 at 3:27 PM.
//
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
//
import SwiftUI
import CoreData
struct SplitDetailView: View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.dismiss) private var dismiss
@ObservedObject var split: Split
@State private var showingExerciseAddSheet: Bool = false
@State private var showingSplitEditSheet: Bool = false
@State private var itemToEdit: Exercise? = nil
@State private var itemToDelete: Exercise? = nil
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")) {
let sortedExercises = split.exercisesArray
if !sortedExercises.isEmpty {
ForEach(sortedExercises, id: \.objectID) { item in
ListItem(
title: item.name,
subtitle: "\(item.sets) × \(item.reps) × \(item.weight) lbs"
)
.swipeActions {
Button {
itemToDelete = item
} label: {
Label("Delete", systemImage: "trash")
}
.tint(.red)
Button {
itemToEdit = item
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.indigo)
}
}
.onMove(perform: moveExercises)
Button {
showingExerciseAddSheet = true
} label: {
ListItem(systemName: "plus.circle", title: "Add Exercise")
}
} else {
Text("No exercises added yet.")
Button(action: { showingExerciseAddSheet.toggle() }) {
ListItem(title: "Add Exercise")
}
}
}
}
.navigationTitle("\(split.name)")
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
showingSplitEditSheet = true
} label: {
Image(systemName: "pencil")
}
}
}
.sheet(isPresented: $showingExerciseAddSheet) {
ExercisePickerView(onExerciseSelected: { exerciseNames in
addExercises(names: exerciseNames)
}, allowMultiSelect: true)
}
.sheet(isPresented: $showingSplitEditSheet) {
SplitAddEditView(split: split) {
dismiss()
}
}
.sheet(item: $itemToEdit) { item in
ExerciseAddEditView(exercise: item)
}
.confirmationDialog(
"Delete Exercise?",
isPresented: .constant(itemToDelete != nil),
titleVisibility: .visible
) {
Button("Delete", role: .destructive) {
if let item = itemToDelete {
withAnimation {
viewContext.delete(item)
try? viewContext.save()
itemToDelete = nil
}
}
}
Button("Cancel", role: .cancel) {
itemToDelete = nil
}
}
}
private func moveExercises(from source: IndexSet, to destination: Int) {
var exercises = split.exercisesArray
exercises.move(fromOffsets: source, toOffset: destination)
for (index, exercise) in exercises.enumerated() {
exercise.order = Int32(index)
}
try? viewContext.save()
}
private func addExercises(names: [String]) {
if names.count == 1 {
let exercise = Exercise(context: viewContext)
exercise.name = names.first ?? "Unnamed"
exercise.order = Int32(split.exercisesArray.count)
exercise.sets = 3
exercise.reps = 10
exercise.weight = 40
exercise.loadType = Int32(LoadType.weight.rawValue)
exercise.split = split
try? viewContext.save()
itemToEdit = exercise
} else {
let existingNames = Set(split.exercisesArray.map { $0.name })
for name in names where !existingNames.contains(name) {
let exercise = Exercise(context: viewContext)
exercise.name = name
exercise.order = Int32(split.exercisesArray.count)
exercise.sets = 3
exercise.reps = 10
exercise.weight = 40
exercise.loadType = Int32(LoadType.weight.rawValue)
exercise.split = split
}
try? viewContext.save()
}
}
}