add draft views for sets of exercises

This commit is contained in:
Felix Förtsch
2024-09-02 20:44:56 +02:00
parent 8e1d3306b9
commit 0905ea7d3f
9 changed files with 207 additions and 97 deletions
@@ -0,0 +1,48 @@
//
// ExerciseListItem.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 02.09.24.
//
import SwiftUI
struct ExerciseListItem: View {
var workout: Workout
@State var exercise: WorkoutItem
init(_ workout: Workout, _ exercise: WorkoutItem ) {
self.workout = workout
self.exercise = exercise
}
var body: some View {
Button(action: {
// workout.addExercise(from: exercise)
}) {
HStack {
Text(String(exercise.reps))
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.white)
.frame(width: 20, height: 10)
.padding(8)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 8))
Text(exercise.name)
.foregroundStyle(.black)
Spacer()
Stepper(
value: $exercise.reps,
in: 0...100,
step: 1
) {}
}
}
}
}
#Preview {
List {
ExerciseListItem(Workout(name: "RR"), WorkoutItem(from: Exercise("Push-ups")))
}
}
+72
View File
@@ -0,0 +1,72 @@
//
// SetListItem.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 02.09.24.
//
import SwiftUI
struct SetListItem: View {
var workout: Workout
@State var set: WorkoutItem
init(_ workout: Workout, _ set: WorkoutItem ) {
self.workout = workout
self.set = set
}
var body: some View {
HStack {
HStack {
Text(String(set.reps))
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.white)
.frame(width: 20, height: 10)
.padding(8)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 8))
Image(systemName: "repeat")
Text("Set")
}
.fontWeight(.bold)
Spacer()
Stepper(
value: $set.reps,
in: 0...100,
step: 1
) {}
Button(action: {
}) {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.green)
}
}
ForEach(set.workoutItems) { workoutItem in
ExerciseListItem(workout, workoutItem)
.padding(.leading)
}
}
private func addExerciseToSet() {
}
}
#Preview {
let set = WorkoutItem(workoutItems: [
WorkoutItem(10, "Squat"),
WorkoutItem(10, "Squat"),
WorkoutItem(10, "Squat")])
List {
SetListItem(Workout(name: "RR"), set)
}
}
#Preview("Empty Database") {
let set = WorkoutItem(workoutItems: [])
List {
SetListItem(Workout(name: "RR"), set)
}
}
+5 -2
View File
@@ -9,10 +9,11 @@ import Foundation
import SwiftData import SwiftData
@Model @Model
final class Exercise { final class Exercise: Identifiable {
var id = UUID()
static var systemImage = "figure.run" static var systemImage = "figure.run"
@Attribute(.unique) var name: String @Attribute(.unique) var name: String
var metric: String = "reps" // var metric: String = "reps"
// var exerciseDescription: ExerciseDescription? // var exerciseDescription: ExerciseDescription?
var timestamp: Date = Date.now var timestamp: Date = Date.now
@@ -20,7 +21,9 @@ final class Exercise {
init(_ name: String = "") { init(_ name: String = "") {
self.name = name self.name = name
} }
}
extension Exercise {
static let sampleData: [Exercise] = [ static let sampleData: [Exercise] = [
Exercise("Dips"), Exercise("Dips"),
Exercise("Chin-ups"), Exercise("Chin-ups"),
+1 -3
View File
@@ -39,9 +39,7 @@ class SampleData {
context.insert(workoutItem) context.insert(workoutItem)
} }
for workout in Workout.sampleData { context.insert(Workout.sampleData)
context.insert(workout)
}
do { do {
try context.save() try context.save()
+1 -1
View File
@@ -36,6 +36,6 @@ struct AddWorkout: View {
#Preview { #Preview {
Color.clear Color.clear
.sheet(isPresented: .constant(true)) { .sheet(isPresented: .constant(true)) {
AddWorkout(workout: Workout.sampleData.first!) AddWorkout(workout: Workout.sampleData)
} }
} }
@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
struct AddExerciseToWorkout: View { struct AddWorkoutItemToWorkout: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
@Query(sort: \Exercise.name) private var exercises: [Exercise] @Query(sort: \Exercise.name) private var exercises: [Exercise]
@@ -16,18 +16,21 @@ struct AddExerciseToWorkout: View {
var body: some View { var body: some View {
Group { Group {
if !exercises.isEmpty { List {
List { Section(header: Text("Utilities")) {
Section(header: Text("Excersises")) { AddExerciseToWorkoutListItem(WorkoutItem(workoutItems: []), workout)
ForEach(exercises) { exercise in
AddExerciseToWorkoutListItem(exercise, workout)
}
}
} }
} else { Section(header: Text("Excersises")) {
ContentUnavailableView { if !exercises.isEmpty {
// TODO: Add Button that allows adding an exercise ForEach(exercises) { exercise in
Label("No Exercises", systemImage: Exercise.systemImage)
AddExerciseToWorkoutListItem(WorkoutItem(from: exercise), workout)
}} else {
ContentUnavailableView {
// TODO: Add Button that allows adding an exercise
Label("No Exercises", systemImage: Exercise.systemImage)
}
}
} }
} }
} }
@@ -37,20 +40,20 @@ struct AddExerciseToWorkout: View {
struct AddExerciseToWorkoutListItem: View { struct AddExerciseToWorkoutListItem: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
var exercise: Exercise var workoutItem: WorkoutItem
var workout: Workout var workout: Workout
init(_ exercise: Exercise, _ workout: Workout) { init(_ workoutItem: WorkoutItem, _ workout: Workout) {
self.exercise = exercise self.workoutItem = workoutItem
self.workout = workout self.workout = workout
} }
var body: some View { var body: some View {
Button(action: { Button(action: {
workout.addExercise(exercise) workout.add(workoutItem: workoutItem)
}) { }) {
HStack { HStack {
Text(exercise.name) Text(workoutItem.name)
.foregroundStyle(.black) .foregroundStyle(.black)
Spacer() Spacer()
Image(systemName: "plus.circle.fill") Image(systemName: "plus.circle.fill")
@@ -61,11 +64,11 @@ struct AddExerciseToWorkoutListItem: View {
} }
#Preview("With Sample Data") { #Preview("With Sample Data") {
AddExerciseToWorkout(workout: Workout.sampleData.first!) AddWorkoutItemToWorkout(workout: Workout.sampleData)
.modelContainer(SampleData.shared.modelContainer) .modelContainer(SampleData.shared.modelContainer)
} }
#Preview("Empty Database") { #Preview("Empty Database") {
AddExerciseToWorkout(workout: Workout.sampleData.first!) AddWorkoutItemToWorkout(workout: Workout.sampleData)
.modelContainer(for: Exercise.self, inMemory: true) .modelContainer(for: Exercise.self, inMemory: true)
} }
+20 -19
View File
@@ -17,36 +17,31 @@ final class Workout: Identifiable {
// Other properties and methods // Other properties and methods
var timestamp: Date = Date.now var timestamp: Date = Date.now
@Relationship(deleteRule: .cascade) var exercises: [WorkoutItem] = [] @Relationship(deleteRule: .cascade) var workoutItems: [WorkoutItem] = []
init(name: String) { init(name: String) {
self.name = name self.name = name
} }
func addExercise(_ exercise: Exercise) { func add(workoutItem: WorkoutItem) {
self.exercises.append(WorkoutItem(from: exercise)) self.workoutItems.append(workoutItem)
updateExercisePositions() updateExercisePositions()
} }
// func addExercise(_ exercise: WorkoutItem) { func add(workoutItems: [WorkoutItem]) {
// self.exercises.append(exercise) for workoutItem in workoutItems {
// updateExercisePositions() self.workoutItems.append(workoutItem)
// }
func addExercise(_ exercises: [WorkoutItem]) {
for exercise in exercises {
self.exercises.append(exercise)
} }
updateExercisePositions() updateExercisePositions()
} }
func moveExercise(from source: IndexSet, to destination: Int) { func moveWorkoutItem(from source: IndexSet, to destination: Int) {
exercises.move(fromOffsets: source, toOffset: destination) workoutItems.move(fromOffsets: source, toOffset: destination)
updateExercisePositions() updateExercisePositions()
} }
private func updateExercisePositions() { private func updateExercisePositions() {
for (index, exercise) in exercises.enumerated() { for (index, exercise) in workoutItems.enumerated() {
exercise.position = index exercise.position = index
} }
} }
@@ -56,11 +51,17 @@ final class Workout: Identifiable {
extension Workout { extension Workout {
private convenience init(name: String, exercises: [WorkoutItem]) { private convenience init(name: String, exercises: [WorkoutItem]) {
self.init(name: name) self.init(name: name)
self.exercises = exercises self.workoutItems = exercises
} }
static let sampleData: [Workout] = [ static let sampleData: Workout = {
Workout(name: "Recommended Routine", exercises: WorkoutItem.sampleData), var workout = Workout(name: "Recommended Routine")
Workout(name: "Marathon Plan", exercises: WorkoutItem.sampleData)
] for workoutItem in WorkoutItem.sampleData {
workout.add(workoutItem: workoutItem)
}
return workout
}()
} }
+19 -47
View File
@@ -23,18 +23,24 @@ struct WorkoutDetail: View {
} }
Section(header: Text("Exercises")) { Section(header: Text("Exercises")) {
List { List {
ForEach(workout.exercises ForEach(workout.workoutItems
.sorted(by: { $0.position < $1.position})) { exercise in .sorted(by: { $0.position < $1.position})) { workoutItem in
ExerciseListItem(workout, exercise) switch workoutItem.workoutItemType {
} case .exercise:
.onDelete(perform: deleteExerciseFromWorkout) ExerciseListItem(workout, workoutItem)
.onMove(perform: move) case .set:
SetListItem(workout, workoutItem)
case .workout:
Text(workoutItem.name)
}
}
.onDelete(perform: deleteExerciseFromWorkout)
.onMove(perform: move)
} }
.environment(\.editMode, .constant(.active)) // Always active drag mode .environment(\.editMode, .constant(.active)) // Always active drag mode
AddItemButton(label: "Exercise", action: addExerciseToWorkout) AddItemButton(label: "Exercise", action: addWorkoutItemToWorkout)
} }
} }
.navigationBarTitle("Edit \(workout.name)") .navigationBarTitle("Edit \(workout.name)")
.toolbar { .toolbar {
ToolbarItem() { ToolbarItem() {
@@ -43,14 +49,14 @@ struct WorkoutDetail: View {
} }
.sheet(isPresented: $isPresenting) { .sheet(isPresented: $isPresenting) {
NavigationStack { NavigationStack {
AddExerciseToWorkout(workout: workout) AddWorkoutItemToWorkout(workout: workout)
} }
.presentationDetents([.medium, .large]) .presentationDetents([.medium, .large])
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
} }
} }
private func addExerciseToWorkout() { private func addWorkoutItemToWorkout() {
withAnimation { withAnimation {
isPresenting = true isPresenting = true
} }
@@ -69,54 +75,20 @@ struct WorkoutDetail: View {
private func deleteExerciseFromWorkout(offsets: IndexSet) { private func deleteExerciseFromWorkout(offsets: IndexSet) {
withAnimation { withAnimation {
for index in offsets { for index in offsets {
modelContext.delete(workout.exercises[index]) modelContext.delete(workout.workoutItems[index])
} }
try? modelContext.save() try? modelContext.save()
} }
} }
private func move(from source: IndexSet, to destination: Int) { private func move(from source: IndexSet, to destination: Int) {
workout.moveExercise(from: source, to: destination) workout.moveWorkoutItem(from: source, to: destination)
}
}
struct ExerciseListItem: View {
var workout: Workout
@State var exercise: WorkoutItem
init(_ workout: Workout, _ exercise: WorkoutItem ) {
self.workout = workout
self.exercise = exercise
}
var body: some View {
Button(action: {
// workout.addExercise(from: exercise)
}) {
HStack {
Text(String(exercise.reps))
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.white)
.frame(width: 20, height: 10)
.padding(8)
.background(Color.blue)
.clipShape(RoundedRectangle(cornerRadius: 8))
Text(exercise.name)
.foregroundStyle(.black)
Spacer()
Stepper(
value: $exercise.reps,
in: 0...100,
step: 1
) {}
}
}
} }
} }
#Preview { #Preview {
NavigationStack { NavigationStack {
WorkoutDetail(workout: Workout.sampleData.first!) WorkoutDetail(workout: Workout.sampleData)
.modelContainer(SampleData.shared.modelContainer) .modelContainer(SampleData.shared.modelContainer)
} }
} }
+17 -4
View File
@@ -12,18 +12,19 @@ import SwiftData
final class WorkoutItem: Identifiable { final class WorkoutItem: Identifiable {
var id = UUID() var id = UUID()
var name: String var name: String
var workoutItemType: WorkoutItemType
var workout: Workout? var workout: Workout?
var children: [WorkoutItem] = [] var workoutItemType: WorkoutItemType
var position: Int = 0 var position: Int = 0
var reps: Int = 8
// EXERCISE
var exercise: Exercise? { var exercise: Exercise? {
didSet { didSet {
self.name = exercise?.name ?? "self.name" self.name = exercise?.name ?? "self.name"
} }
} }
var reps: Int = 0
init(_ reps: Int, _ exercise: String) { init(_ reps: Int, _ exercise: String) {
self.workoutItemType = .exercise self.workoutItemType = .exercise
@@ -40,8 +41,20 @@ final class WorkoutItem: Identifiable {
self.exercise = exercise self.exercise = exercise
} }
// SET
var workoutItems: [WorkoutItem] = []
init(workoutItems: [WorkoutItem] = []) {
self.name = "Set"
self.workoutItemType = .set
self.reps = 3
workoutItems.forEach(addChild)
}
func addChild(_ child: WorkoutItem) { func addChild(_ child: WorkoutItem) {
self.children.append(child) if self.workoutItemType == .set {
self.workoutItems.append(child)
}
} }
} }