wip
This commit is contained in:
@ -1,9 +1,10 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@Model
|
||||
final class Exercise: ListableItem {
|
||||
@Attribute(.unique) var name: String = ""
|
||||
final class Exercise {
|
||||
var name: String = ""
|
||||
var setup: String = ""
|
||||
var descr: String = ""
|
||||
var sets: Int = 0
|
||||
@ -31,3 +32,62 @@ final class Exercise: ListableItem {
|
||||
self.weight = weight
|
||||
}
|
||||
}
|
||||
|
||||
extension Exercise: EditableEntity {
|
||||
static func createNew() -> Exercise {
|
||||
return Exercise(name: "", setup: "", descr: "", sets: 3, reps: 10, weight: 30)
|
||||
}
|
||||
|
||||
static var navigationTitle: String {
|
||||
return "Exercises"
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static func formView(for model: Exercise) -> some View {
|
||||
EntityAddEditView(model: model) { $model in
|
||||
// This internal view is necessary to use @Query within the form.
|
||||
ExerciseFormView(model: $model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct ExerciseFormView: View {
|
||||
@Binding var model: Exercise
|
||||
@Query(sort: [SortDescriptor(\ExerciseType.name)]) var exerciseTypes: [ExerciseType]
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
}
|
||||
|
||||
Section(header: Text("Exercise Type")) {
|
||||
Picker("Type", selection: $model.type) {
|
||||
Text("Select a type").tag(nil as ExerciseType?)
|
||||
ForEach(exerciseTypes) { type in
|
||||
Text(type.name).tag(type as ExerciseType?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Description")) {
|
||||
TextEditor(text: $model.descr)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
|
||||
Section(header: Text("Setup")) {
|
||||
TextEditor(text: $model.setup)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
|
||||
Section(header: Text("Weight")) {
|
||||
HStack {
|
||||
Text("\(model.weight)")
|
||||
.bold()
|
||||
Text("lbs")
|
||||
Spacer()
|
||||
Stepper("", value: $model.weight, in: 0...1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@Model
|
||||
final class ExerciseType: ListableItem {
|
||||
@Attribute(.unique) var name: String = ""
|
||||
final class ExerciseType {
|
||||
var name: String = ""
|
||||
var descr: String = ""
|
||||
|
||||
@Relationship(deleteRule: .nullify)
|
||||
@ -14,3 +15,34 @@ final class ExerciseType: ListableItem {
|
||||
self.descr = descr
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - EditableEntity Conformance
|
||||
|
||||
extension ExerciseType: EditableEntity {
|
||||
var count: Int? {
|
||||
return self.exercises?.count
|
||||
}
|
||||
|
||||
static func createNew() -> ExerciseType {
|
||||
return ExerciseType(name: "", descr: "")
|
||||
}
|
||||
|
||||
static var navigationTitle: String {
|
||||
return "Exercise Types"
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static func formView(for model: ExerciseType) -> some View {
|
||||
EntityAddEditView(model: model) { $model in
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
}
|
||||
|
||||
Section(header: Text("Description")) {
|
||||
TextEditor(text: $model.descr)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@Model
|
||||
final class Muscle: ListableItem {
|
||||
@Attribute(.unique) var name: String = ""
|
||||
final class Muscle {
|
||||
var name: String = ""
|
||||
|
||||
var descr: String = ""
|
||||
|
||||
@ -13,9 +14,60 @@ final class Muscle: ListableItem {
|
||||
@Relationship(deleteRule: .nullify)
|
||||
var exercises: [Exercise]? = []
|
||||
|
||||
init(name: String, descr: String, muscleGroup: MuscleGroup) {
|
||||
init(name: String, descr: String, muscleGroup: MuscleGroup? = nil) {
|
||||
self.name = name
|
||||
self.descr = descr
|
||||
self.muscleGroup = muscleGroup
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - EditableEntity Conformance
|
||||
|
||||
extension Muscle: EditableEntity {
|
||||
var count: Int? {
|
||||
return self.exercises?.count
|
||||
}
|
||||
|
||||
static func createNew() -> Muscle {
|
||||
return Muscle(name: "", descr: "")
|
||||
}
|
||||
|
||||
static var navigationTitle: String {
|
||||
return "Muscles"
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static func formView(for model: Muscle) -> some View {
|
||||
EntityAddEditView(model: model) { $model in
|
||||
MuscleFormView(model: $model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Form View
|
||||
|
||||
fileprivate struct MuscleFormView: View {
|
||||
@Binding var model: Muscle
|
||||
@Query(sort: [SortDescriptor(\MuscleGroup.name)]) var muscleGroups: [MuscleGroup]
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
}
|
||||
|
||||
Section(header: Text("Muscle Group")) {
|
||||
Picker("Muscle Group", selection: $model.muscleGroup) {
|
||||
Text("Select a muscle group").tag(nil as MuscleGroup?)
|
||||
ForEach(muscleGroups) { group in
|
||||
Text(group.name).tag(group as MuscleGroup?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Description")) {
|
||||
TextEditor(text: $model.descr)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@Model
|
||||
final class MuscleGroup: ListableItem {
|
||||
@Attribute(.unique) var name: String = ""
|
||||
final class MuscleGroup {
|
||||
var name: String = ""
|
||||
var descr: String = ""
|
||||
|
||||
@Relationship(deleteRule: .nullify)
|
||||
@ -14,3 +15,35 @@ final class MuscleGroup: ListableItem {
|
||||
self.descr = descr
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - EditableEntity Conformance
|
||||
|
||||
extension MuscleGroup: EditableEntity {
|
||||
static func createNew() -> MuscleGroup {
|
||||
return MuscleGroup(name: "", descr: "")
|
||||
}
|
||||
|
||||
static var navigationTitle: String {
|
||||
return "Muscle Groups"
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static func formView(for model: MuscleGroup) -> some View {
|
||||
EntityAddEditView(model: model) { $model in
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
}
|
||||
|
||||
Section(header: Text("Description")) {
|
||||
TextEditor(text: $model.descr)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var count: Int? {
|
||||
return muscles?.count
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@Model
|
||||
final class Split: ListableItem {
|
||||
@Attribute(.unique) var name: String = ""
|
||||
final class Split {
|
||||
var name: String = ""
|
||||
var intro: String = ""
|
||||
|
||||
@Relationship(deleteRule: .cascade, inverse: \SplitExerciseAssignment.split)
|
||||
@ -17,3 +18,80 @@ final class Split: ListableItem {
|
||||
self.intro = intro
|
||||
}
|
||||
}
|
||||
|
||||
// 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 itemToDelete: SplitExerciseAssignment? = nil
|
||||
|
||||
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("Exercises")) {
|
||||
if let assignments = model.exercises, !assignments.isEmpty {
|
||||
ForEach(assignments) { item in
|
||||
ListItem(
|
||||
title: item.exercise?.name ?? "Unnamed",
|
||||
subtitle: "\(item.sets) × \(item.reps) @ \(item.weight) lbs"
|
||||
)
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
itemToDelete = item
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No exercises added yet.")
|
||||
}
|
||||
Button(action: {
|
||||
// TODO: Implement add exercise functionality
|
||||
}) {
|
||||
Label("Add Exercise", systemImage: "plus.circle")
|
||||
}
|
||||
}
|
||||
.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,8 @@ final class Workout {
|
||||
self.end = end
|
||||
self.split = split
|
||||
}
|
||||
|
||||
var label: String {
|
||||
start.formattedDate()
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ final class WorkoutLog {
|
||||
@Relationship(deleteRule: .nullify)
|
||||
var exercise: Exercise?
|
||||
|
||||
init(date: Date, sets: Int, reps: Int, weight: Int, completed: Bool, workout: Workout, exercise: Exercise) {
|
||||
init(workout: Workout, exercise: Exercise, date: Date, sets: Int, reps: Int, weight: Int, completed: Bool) {
|
||||
self.date = date
|
||||
self.sets = sets
|
||||
self.reps = reps
|
||||
|
Reference in New Issue
Block a user