add draft views for sets of exercises
This commit is contained in:
@@ -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")))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
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"),
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-19
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user