202 lines
7.4 KiB
Swift
202 lines
7.4 KiB
Swift
import Foundation
|
||
import SwiftData
|
||
import SwiftUI
|
||
|
||
@Model
|
||
final class Split {
|
||
var name: String = ""
|
||
var intro: String = ""
|
||
var color: String = "indigo"
|
||
var systemImage: String = "dumbbell.fill"
|
||
|
||
// Returns the SwiftUI Color for the stored color name
|
||
func getColor() -> Color {
|
||
switch color {
|
||
case "red": return .red
|
||
case "orange": return .orange
|
||
case "yellow": return .yellow
|
||
case "green": return .green
|
||
case "mint": return .mint
|
||
case "teal": return .teal
|
||
case "cyan": return .cyan
|
||
case "blue": return .blue
|
||
case "indigo": return .indigo
|
||
case "purple": return .purple
|
||
case "pink": return .pink
|
||
case "brown": return .brown
|
||
default: return .indigo
|
||
}
|
||
}
|
||
|
||
@Relationship(deleteRule: .cascade, inverse: \SplitExerciseAssignment.split)
|
||
var exercises: [SplitExerciseAssignment]? = []
|
||
|
||
@Relationship(deleteRule: .nullify, inverse: \Workout.split)
|
||
var workouts: [Workout]? = []
|
||
|
||
init(name: String, intro: String, color: String = "indigo", systemImage: String = "dumbbell.fill") {
|
||
self.name = name
|
||
self.intro = intro
|
||
self.color = color
|
||
self.systemImage = systemImage
|
||
}
|
||
|
||
static let unnamed = "Unnamed Split"
|
||
}
|
||
|
||
// MARK: - EditableEntity Conformance
|
||
|
||
extension Split: EditableEntity {
|
||
var count: Int? {
|
||
return self.exercises?.count
|
||
}
|
||
|
||
static func createNew() -> Split {
|
||
return Split(name: "", intro: "")
|
||
}
|
||
|
||
static var navigationTitle: String {
|
||
return "Splits"
|
||
}
|
||
|
||
@ViewBuilder
|
||
static func formView(for model: Split) -> some View {
|
||
EntityAddEditView(model: model) { $model in
|
||
SplitFormView(model: $model)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Private Form View
|
||
|
||
fileprivate struct SplitFormView: View {
|
||
@Binding var model: Split
|
||
|
||
@State private var showingAddSheet: Bool = false
|
||
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
||
@State private var itemToDelete: SplitExerciseAssignment? = nil
|
||
|
||
// Available colors for splits
|
||
private let availableColors = ["red", "orange", "yellow", "green", "mint", "teal", "cyan", "blue", "indigo", "purple", "pink", "brown"]
|
||
|
||
// Available system images for splits
|
||
private let availableIcons = ["dumbbell.fill", "figure.strengthtraining.traditional", "figure.run", "figure.hiking", "figure.cooldown", "figure.boxing", "figure.wrestling", "figure.gymnastics", "figure.handball", "figure.core.training", "heart.fill", "bolt.fill"]
|
||
|
||
var body: some View {
|
||
Section(header: Text("Name")) {
|
||
TextField("Name", text: $model.name)
|
||
.bold()
|
||
}
|
||
|
||
Section(header: Text("Description")) {
|
||
TextEditor(text: $model.intro)
|
||
.frame(minHeight: 100)
|
||
}
|
||
|
||
Section(header: Text("Appearance")) {
|
||
Picker("Color", selection: $model.color) {
|
||
ForEach(availableColors, id: \.self) { colorName in
|
||
let tempSplit = Split(name: "", intro: "", color: colorName)
|
||
HStack {
|
||
Circle()
|
||
.fill(tempSplit.getColor())
|
||
.frame(width: 20, height: 20)
|
||
Text(colorName.capitalized)
|
||
}
|
||
.tag(colorName)
|
||
}
|
||
}
|
||
|
||
Picker("Icon", selection: $model.systemImage) {
|
||
ForEach(availableIcons, id: \.self) { iconName in
|
||
HStack {
|
||
Image(systemName: iconName)
|
||
.frame(width: 24, height: 24)
|
||
Text(iconName.replacingOccurrences(of: ".fill", with: "").replacingOccurrences(of: "figure.", with: "").capitalized)
|
||
}
|
||
.tag(iconName)
|
||
}
|
||
}
|
||
}
|
||
|
||
Section(header: Text("Exercises")) {
|
||
NavigationLink {
|
||
NavigationStack {
|
||
Form {
|
||
List {
|
||
if let assignments = model.exercises, !assignments.isEmpty {
|
||
let sortedAssignments = assignments.sorted(by: { $0.order == $1.order ? $0.exercise?.name ?? Exercise.unnamed < $1.exercise?.name ?? Exercise.unnamed : $0.order < $1.order })
|
||
ForEach(sortedAssignments) { item in
|
||
ListItem(
|
||
title: item.exercise?.name ?? Exercise.unnamed,
|
||
text: item.setup.isEmpty ? nil : item.setup,
|
||
subtitle: "\(item.sets) × \(item.reps) × \(item.weight) lbs"
|
||
)
|
||
.swipeActions {
|
||
Button(role: .destructive) {
|
||
itemToDelete = item
|
||
} label: {
|
||
Label("Delete", systemImage: "trash")
|
||
}
|
||
Button {
|
||
itemToEdit = item
|
||
} label: {
|
||
Label("Edit", systemImage: "pencil")
|
||
}
|
||
.tint(.indigo)
|
||
}
|
||
}
|
||
} else {
|
||
Text("No exercises added yet.")
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("Exercises")
|
||
}
|
||
.toolbar {
|
||
ToolbarItem(placement: .navigationBarTrailing) {
|
||
Button(action: { showingAddSheet.toggle() }) {
|
||
Image(systemName: "plus")
|
||
}
|
||
}
|
||
}
|
||
.sheet (isPresented: $showingAddSheet) {
|
||
ExercisePickerView { exercise in
|
||
itemToEdit = SplitExerciseAssignment(
|
||
order: 0,
|
||
sets: 3,
|
||
reps: 10,
|
||
weight: 40,
|
||
split: model,
|
||
exercise: exercise
|
||
)
|
||
}
|
||
}
|
||
.sheet(item: $itemToEdit) { item in
|
||
SplitExerciseAssignmentAddEditView(model: item)
|
||
}
|
||
.confirmationDialog(
|
||
"Delete Exercise?",
|
||
isPresented: .constant(itemToDelete != nil),
|
||
titleVisibility: .visible
|
||
) {
|
||
Button("Delete", role: .destructive) {
|
||
if let item = itemToDelete {
|
||
withAnimation {
|
||
model.exercises?.removeAll { $0.id == item.id }
|
||
itemToDelete = nil
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} label: {
|
||
ListItem(
|
||
text: "Exercises",
|
||
count: model.exercises?.count ?? 0
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|