add draft views for sets of exercises
This commit is contained in:
48
WorkoutsPlus/Components/ExerciseListItem.swift
Normal file
48
WorkoutsPlus/Components/ExerciseListItem.swift
Normal file
@@ -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
WorkoutsPlus/Components/SetListItem.swift
Normal file
72
WorkoutsPlus/Components/SetListItem.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@ import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
final class Exercise {
|
||||
final class Exercise: Identifiable {
|
||||
var id = UUID()
|
||||
static var systemImage = "figure.run"
|
||||
@Attribute(.unique) var name: String
|
||||
var metric: String = "reps"
|
||||
// var metric: String = "reps"
|
||||
// var exerciseDescription: ExerciseDescription?
|
||||
|
||||
var timestamp: Date = Date.now
|
||||
@@ -20,7 +21,9 @@ final class Exercise {
|
||||
init(_ name: String = "") {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Exercise {
|
||||
static let sampleData: [Exercise] = [
|
||||
Exercise("Dips"),
|
||||
Exercise("Chin-ups"),
|
||||
|
||||
@@ -39,9 +39,7 @@ class SampleData {
|
||||
context.insert(workoutItem)
|
||||
}
|
||||
|
||||
for workout in Workout.sampleData {
|
||||
context.insert(workout)
|
||||
}
|
||||
context.insert(Workout.sampleData)
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
|
||||
@@ -36,6 +36,6 @@ struct AddWorkout: View {
|
||||
#Preview {
|
||||
Color.clear
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
AddWorkout(workout: Workout.sampleData.first!)
|
||||
AddWorkout(workout: Workout.sampleData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct AddExerciseToWorkout: View {
|
||||
struct AddWorkoutItemToWorkout: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Query(sort: \Exercise.name) private var exercises: [Exercise]
|
||||
|
||||
@@ -16,18 +16,21 @@ struct AddExerciseToWorkout: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if !exercises.isEmpty {
|
||||
List {
|
||||
Section(header: Text("Excersises")) {
|
||||
ForEach(exercises) { exercise in
|
||||
AddExerciseToWorkoutListItem(exercise, workout)
|
||||
}
|
||||
}
|
||||
List {
|
||||
Section(header: Text("Utilities")) {
|
||||
AddExerciseToWorkoutListItem(WorkoutItem(workoutItems: []), workout)
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView {
|
||||
// TODO: Add Button that allows adding an exercise
|
||||
Label("No Exercises", systemImage: Exercise.systemImage)
|
||||
Section(header: Text("Excersises")) {
|
||||
if !exercises.isEmpty {
|
||||
ForEach(exercises) { exercise in
|
||||
|
||||
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 {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
var exercise: Exercise
|
||||
var workoutItem: WorkoutItem
|
||||
var workout: Workout
|
||||
|
||||
init(_ exercise: Exercise, _ workout: Workout) {
|
||||
self.exercise = exercise
|
||||
init(_ workoutItem: WorkoutItem, _ workout: Workout) {
|
||||
self.workoutItem = workoutItem
|
||||
self.workout = workout
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
workout.addExercise(exercise)
|
||||
workout.add(workoutItem: workoutItem)
|
||||
}) {
|
||||
HStack {
|
||||
Text(exercise.name)
|
||||
Text(workoutItem.name)
|
||||
.foregroundStyle(.black)
|
||||
Spacer()
|
||||
Image(systemName: "plus.circle.fill")
|
||||
@@ -61,11 +64,11 @@ struct AddExerciseToWorkoutListItem: View {
|
||||
}
|
||||
|
||||
#Preview("With Sample Data") {
|
||||
AddExerciseToWorkout(workout: Workout.sampleData.first!)
|
||||
AddWorkoutItemToWorkout(workout: Workout.sampleData)
|
||||
.modelContainer(SampleData.shared.modelContainer)
|
||||
}
|
||||
|
||||
#Preview("Empty Database") {
|
||||
AddExerciseToWorkout(workout: Workout.sampleData.first!)
|
||||
AddWorkoutItemToWorkout(workout: Workout.sampleData)
|
||||
.modelContainer(for: Exercise.self, inMemory: true)
|
||||
}
|
||||
@@ -17,36 +17,31 @@ final class Workout: Identifiable {
|
||||
// Other properties and methods
|
||||
var timestamp: Date = Date.now
|
||||
|
||||
@Relationship(deleteRule: .cascade) var exercises: [WorkoutItem] = []
|
||||
@Relationship(deleteRule: .cascade) var workoutItems: [WorkoutItem] = []
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func addExercise(_ exercise: Exercise) {
|
||||
self.exercises.append(WorkoutItem(from: exercise))
|
||||
func add(workoutItem: WorkoutItem) {
|
||||
self.workoutItems.append(workoutItem)
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
// func addExercise(_ exercise: WorkoutItem) {
|
||||
// self.exercises.append(exercise)
|
||||
// updateExercisePositions()
|
||||
// }
|
||||
|
||||
func addExercise(_ exercises: [WorkoutItem]) {
|
||||
for exercise in exercises {
|
||||
self.exercises.append(exercise)
|
||||
func add(workoutItems: [WorkoutItem]) {
|
||||
for workoutItem in workoutItems {
|
||||
self.workoutItems.append(workoutItem)
|
||||
}
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
func moveExercise(from source: IndexSet, to destination: Int) {
|
||||
exercises.move(fromOffsets: source, toOffset: destination)
|
||||
func moveWorkoutItem(from source: IndexSet, to destination: Int) {
|
||||
workoutItems.move(fromOffsets: source, toOffset: destination)
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
private func updateExercisePositions() {
|
||||
for (index, exercise) in exercises.enumerated() {
|
||||
for (index, exercise) in workoutItems.enumerated() {
|
||||
exercise.position = index
|
||||
}
|
||||
}
|
||||
@@ -56,11 +51,17 @@ final class Workout: Identifiable {
|
||||
extension Workout {
|
||||
private convenience init(name: String, exercises: [WorkoutItem]) {
|
||||
self.init(name: name)
|
||||
self.exercises = exercises
|
||||
self.workoutItems = exercises
|
||||
}
|
||||
|
||||
static let sampleData: [Workout] = [
|
||||
Workout(name: "Recommended Routine", exercises: WorkoutItem.sampleData),
|
||||
Workout(name: "Marathon Plan", exercises: WorkoutItem.sampleData)
|
||||
]
|
||||
static let sampleData: Workout = {
|
||||
var workout = Workout(name: "Recommended Routine")
|
||||
|
||||
for workoutItem in WorkoutItem.sampleData {
|
||||
workout.add(workoutItem: workoutItem)
|
||||
}
|
||||
|
||||
return workout
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
@@ -23,18 +23,24 @@ struct WorkoutDetail: View {
|
||||
}
|
||||
Section(header: Text("Exercises")) {
|
||||
List {
|
||||
ForEach(workout.exercises
|
||||
.sorted(by: { $0.position < $1.position})) { exercise in
|
||||
ExerciseListItem(workout, exercise)
|
||||
}
|
||||
.onDelete(perform: deleteExerciseFromWorkout)
|
||||
.onMove(perform: move)
|
||||
ForEach(workout.workoutItems
|
||||
.sorted(by: { $0.position < $1.position})) { workoutItem in
|
||||
switch workoutItem.workoutItemType {
|
||||
case .exercise:
|
||||
ExerciseListItem(workout, workoutItem)
|
||||
case .set:
|
||||
SetListItem(workout, workoutItem)
|
||||
case .workout:
|
||||
Text(workoutItem.name)
|
||||
}
|
||||
}
|
||||
.onDelete(perform: deleteExerciseFromWorkout)
|
||||
.onMove(perform: move)
|
||||
}
|
||||
.environment(\.editMode, .constant(.active)) // Always active drag mode
|
||||
AddItemButton(label: "Exercise", action: addExerciseToWorkout)
|
||||
AddItemButton(label: "Exercise", action: addWorkoutItemToWorkout)
|
||||
}
|
||||
}
|
||||
|
||||
.navigationBarTitle("Edit \(workout.name)")
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
@@ -43,14 +49,14 @@ struct WorkoutDetail: View {
|
||||
}
|
||||
.sheet(isPresented: $isPresenting) {
|
||||
NavigationStack {
|
||||
AddExerciseToWorkout(workout: workout)
|
||||
AddWorkoutItemToWorkout(workout: workout)
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
private func addExerciseToWorkout() {
|
||||
private func addWorkoutItemToWorkout() {
|
||||
withAnimation {
|
||||
isPresenting = true
|
||||
}
|
||||
@@ -69,54 +75,20 @@ struct WorkoutDetail: View {
|
||||
private func deleteExerciseFromWorkout(offsets: IndexSet) {
|
||||
withAnimation {
|
||||
for index in offsets {
|
||||
modelContext.delete(workout.exercises[index])
|
||||
modelContext.delete(workout.workoutItems[index])
|
||||
}
|
||||
try? modelContext.save()
|
||||
}
|
||||
}
|
||||
|
||||
private func move(from source: IndexSet, to destination: Int) {
|
||||
workout.moveExercise(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
|
||||
) {}
|
||||
}
|
||||
}
|
||||
workout.moveWorkoutItem(from: source, to: destination)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
WorkoutDetail(workout: Workout.sampleData.first!)
|
||||
WorkoutDetail(workout: Workout.sampleData)
|
||||
.modelContainer(SampleData.shared.modelContainer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,19 @@ import SwiftData
|
||||
final class WorkoutItem: Identifiable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
var workoutItemType: WorkoutItemType
|
||||
|
||||
|
||||
var workout: Workout?
|
||||
var children: [WorkoutItem] = []
|
||||
var workoutItemType: WorkoutItemType
|
||||
var position: Int = 0
|
||||
|
||||
var reps: Int = 8
|
||||
|
||||
// EXERCISE
|
||||
var exercise: Exercise? {
|
||||
didSet {
|
||||
self.name = exercise?.name ?? "self.name"
|
||||
}
|
||||
}
|
||||
var reps: Int = 0
|
||||
|
||||
init(_ reps: Int, _ exercise: String) {
|
||||
self.workoutItemType = .exercise
|
||||
@@ -40,8 +41,20 @@ final class WorkoutItem: Identifiable {
|
||||
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) {
|
||||
self.children.append(child)
|
||||
if self.workoutItemType == .set {
|
||||
self.workoutItems.append(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user