retain order of the exercises during persistence process
This commit is contained in:
30
WorkoutsPlus/Components/AddItemButton.swift
Normal file
30
WorkoutsPlus/Components/AddItemButton.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// AddItemButton.swift
|
||||
// WorkoutsPlus
|
||||
//
|
||||
// Created by Felix Förtsch on 29.08.24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AddItemButton: View {
|
||||
var label: String
|
||||
var action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
action()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Text(label)
|
||||
.foregroundStyle(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AddItemButton(label: "Add Item", action: {})
|
||||
}
|
||||
@@ -13,83 +13,7 @@ struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
NavigationView() {
|
||||
List() {
|
||||
NavigationLink(destination: Text("WorkoutLogDetails")) {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "figure.run")
|
||||
.padding(.trailing)
|
||||
.padding(.top)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("23.01.1988")
|
||||
Text("14:37 Uhr")
|
||||
Spacer()
|
||||
}
|
||||
Text("Marathon")
|
||||
.fontWeight(.semibold)
|
||||
HStack {
|
||||
Text("34 km/42 km")
|
||||
Text("•")
|
||||
Text("5:12 min/km")
|
||||
}.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Text("1 h")
|
||||
Text("•")
|
||||
Text("1.337 kcal")
|
||||
}
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: Text("WorkoutLogDetails")) {
|
||||
HStack {
|
||||
Image(systemName: "figure.run")
|
||||
.padding(.trailing)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("23.01.1988")
|
||||
Text("14:37 Uhr")
|
||||
Spacer()
|
||||
|
||||
}
|
||||
Text("Recommended Routine")
|
||||
.fontWeight(.semibold)
|
||||
HStack {
|
||||
Text("6/12 sets")
|
||||
Text("•")
|
||||
Text("50 %")
|
||||
}.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Text("57 m")
|
||||
Text("•")
|
||||
Text("357 kcal")
|
||||
}
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitle("Workout Logs")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
EditButton()
|
||||
}
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabItem {
|
||||
Image(systemName: "pencil.and.list.clipboard")
|
||||
Text("Log")
|
||||
}
|
||||
Text("Training Plans")
|
||||
.tabItem {
|
||||
Image(systemName: "calendar.badge.clock")
|
||||
Text("Plans & Goals")
|
||||
}
|
||||
WorkoutLog()
|
||||
WorkoutLibrary()
|
||||
.tabItem {
|
||||
Image(systemName: "figure.run.square.stack")
|
||||
@@ -100,11 +24,16 @@ struct ContentView: View {
|
||||
Image(systemName: "figure.run")
|
||||
Text("Exercises")
|
||||
}
|
||||
// Text("Settings")
|
||||
// .tabItem {
|
||||
// Image(systemName: "gear")
|
||||
// Text("Settings")
|
||||
// }
|
||||
DebugExerciseList()
|
||||
.tabItem {
|
||||
Image(systemName: "hammer")
|
||||
Text("Debug")
|
||||
}
|
||||
Settings()
|
||||
.tabItem {
|
||||
Image(systemName: "gear")
|
||||
Text("Settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
WorkoutsPlus/DebugExerciseList.swift
Normal file
29
WorkoutsPlus/DebugExerciseList.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// DebugExerciseList.swift
|
||||
// WorkoutsPlus
|
||||
//
|
||||
// Created by Felix Förtsch on 30.08.24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct DebugExerciseList: View {
|
||||
@Query(sort: \Exercise.name) private var exercises: [Exercise]
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(exercises) { exercise in
|
||||
Text("\(exercise.name), pos: \(exercise.position)")
|
||||
}
|
||||
}
|
||||
.tabItem {
|
||||
Image(systemName: "figure.run.square.stack")
|
||||
Text("Exercise Debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DebugExerciseList()
|
||||
}
|
||||
@@ -9,13 +9,18 @@ import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
class Exercise {
|
||||
final class Exercise: Identifiable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
|
||||
var workout: Workout?
|
||||
var position: Int = 0
|
||||
|
||||
var exerciseTemplate: ExerciseTemplate? {
|
||||
didSet {
|
||||
self.name = exerciseTemplate?.name ?? "self.name"
|
||||
}}
|
||||
}
|
||||
}
|
||||
var reps: Int = 0
|
||||
|
||||
init(_ reps: Int, _ exercise: String) {
|
||||
@@ -29,7 +34,9 @@ class Exercise {
|
||||
self.name = exerciseTemplate.name
|
||||
self.exerciseTemplate = exerciseTemplate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Exercise {
|
||||
static let sampleData: [Exercise] = {
|
||||
var exercises = [Exercise]()
|
||||
|
||||
|
||||
@@ -49,15 +49,7 @@ struct ExerciseLibrary: View {
|
||||
.textInputAutocapitalization(.words)
|
||||
.focused($isInputFieldFocused)
|
||||
}
|
||||
Button(action: {
|
||||
addExerciseTemplate()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Text("Add Exercise")
|
||||
}
|
||||
}
|
||||
AddItemButton(label: "Exercise", action: addExerciseTemplate)
|
||||
}
|
||||
.searchable(text: $searchText)
|
||||
}
|
||||
|
||||
18
WorkoutsPlus/Settings.swift
Normal file
18
WorkoutsPlus/Settings.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Settings.swift
|
||||
// WorkoutsPlus
|
||||
//
|
||||
// Created by Felix Förtsch on 30.08.24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Settings: View {
|
||||
var body: some View {
|
||||
Text("Settings")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
Settings()
|
||||
}
|
||||
@@ -9,40 +9,54 @@ import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
final class Workout {
|
||||
var isEdited = false
|
||||
|
||||
var name: String {
|
||||
didSet { isEdited = true }
|
||||
}
|
||||
final class Workout: Identifiable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
static var systemImage = "figure.run.square.stack"
|
||||
|
||||
// Other properties and methods
|
||||
var timestamp: Date = Date.now
|
||||
|
||||
var exercises: [Exercise] = []
|
||||
@Relationship(deleteRule: .cascade) var exercises: [Exercise] = []
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
init(name: String, exercises: [Exercise]) {
|
||||
self.name = name
|
||||
self.exercises = exercises
|
||||
}
|
||||
|
||||
func addExercise(from exerciseTemplate: ExerciseTemplate) {
|
||||
self.exercises.append(Exercise(from: exerciseTemplate))
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
func addExercise(_ exercise: Exercise) {
|
||||
self.exercises.append(exercise)
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
func addExercise(_ exercises: [Exercise]) {
|
||||
for exercise in exercises {
|
||||
self.exercises.append(exercise)
|
||||
}
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
func moveExercise(from source: IndexSet, to destination: Int) {
|
||||
exercises.move(fromOffsets: source, toOffset: destination)
|
||||
updateExercisePositions()
|
||||
}
|
||||
|
||||
private func updateExercisePositions() {
|
||||
for (index, exercise) in exercises.enumerated() {
|
||||
exercise.position = index
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Workout {
|
||||
private convenience init(name: String, exercises: [Exercise]) {
|
||||
self.init(name: name)
|
||||
self.exercises = exercises
|
||||
}
|
||||
|
||||
static let sampleData: [Workout] = [
|
||||
|
||||
@@ -23,16 +23,15 @@ struct WorkoutDetail: View {
|
||||
}
|
||||
Section(header: Text("Exercises")) {
|
||||
List {
|
||||
ForEach(workout.exercises) { exercise in
|
||||
ForEach(workout.exercises
|
||||
.sorted(by: { $0.position < $1.position})) { exercise in
|
||||
ExerciseListItem(workout, exercise)
|
||||
}
|
||||
.onDelete(perform: deleteExerciseFromWorkout)
|
||||
.onMove(perform: move)
|
||||
}
|
||||
.environment(\.editMode, .constant(.active)) // Always active drag mode
|
||||
Button(action: addExerciseToWorkout) {
|
||||
Text("Add Exercise")
|
||||
}
|
||||
AddItemButton(label: "Exercise", action: addExerciseToWorkout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ struct WorkoutDetail: View {
|
||||
}
|
||||
|
||||
private func move(from source: IndexSet, to destination: Int) {
|
||||
workout.exercises.move(fromOffsets: source, toOffset: destination)
|
||||
workout.moveExercise(from: source, to: destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,19 +94,7 @@ struct ExerciseListItem: View {
|
||||
// workout.addExercise(from: exercise)
|
||||
}) {
|
||||
HStack {
|
||||
|
||||
// TextField("Enter Reps", text: $exercise.reps)
|
||||
// .keyboardType(.numberPad) // Set the keyboard to number pad
|
||||
// .padding()
|
||||
// .frame(width: 100, height: 50)
|
||||
// .background(Color(.systemGray6))
|
||||
// .cornerRadius(8)
|
||||
// .multilineTextAlignment(.center) // Center the text
|
||||
// .overlay(
|
||||
// RoundedRectangle(cornerRadius: 8)
|
||||
// .stroke(Color.gray, lineWidth: 1)
|
||||
// )
|
||||
Text(String(workout.exercises.filter { $0 == exercise }.count))
|
||||
Text(String(exercise.reps))
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 20, height: 10)
|
||||
@@ -117,7 +104,11 @@ struct ExerciseListItem: View {
|
||||
Text(exercise.name)
|
||||
.foregroundStyle(.black)
|
||||
Spacer()
|
||||
Image(systemName: "info.circle")
|
||||
Stepper(
|
||||
value: $exercise.reps,
|
||||
in: 0...100,
|
||||
step: 1
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,17 @@ struct WorkoutLibrary: View {
|
||||
}
|
||||
.onDelete(perform: deleteWorkout)
|
||||
if isAddingWorkout {
|
||||
// TODO: On tap-out of the text field, it should lose focus
|
||||
TextField("New Workout", text: $newWorkoutName, onCommit: {
|
||||
newWorkout.name = newWorkoutName
|
||||
saveWorkout(workout: newWorkout)
|
||||
isAddingWorkout = false
|
||||
})
|
||||
.textInputAutocapitalization(.words)
|
||||
.focused($isInputFieldFocused)
|
||||
}
|
||||
Button(action: {
|
||||
if isAddingWorkout {
|
||||
saveWorkout(workout: newWorkout)
|
||||
}
|
||||
addWorkout()
|
||||
}) {
|
||||
HStack {
|
||||
@@ -59,11 +61,11 @@ struct WorkoutLibrary: View {
|
||||
}
|
||||
.searchable(text: $searchText)
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("Workouts")
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
EditButton()
|
||||
.navigationBarTitle("Workouts")
|
||||
.toolbar {
|
||||
ToolbarItem() {
|
||||
EditButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,9 +81,13 @@ struct WorkoutLibrary: View {
|
||||
|
||||
// TODO: Brauchen wir das?
|
||||
private func saveWorkout(workout: Workout) {
|
||||
if !workout.name.isEmpty {
|
||||
modelContext.insert(workout)
|
||||
try? modelContext.save()
|
||||
withAnimation {
|
||||
newWorkout.name = newWorkoutName
|
||||
if !workout.name.isEmpty {
|
||||
modelContext.insert(workout)
|
||||
try? modelContext.save()
|
||||
}
|
||||
isAddingWorkout = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
93
WorkoutsPlus/WorkoutLog.swift
Normal file
93
WorkoutsPlus/WorkoutLog.swift
Normal file
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// WorkoutLog.swift
|
||||
// WorkoutsPlus
|
||||
//
|
||||
// Created by Felix Förtsch on 30.08.24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct WorkoutLog: View {
|
||||
var body: some View {
|
||||
NavigationView() {
|
||||
List() {
|
||||
NavigationLink(destination: Text("WorkoutLogDetails")) {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "figure.run")
|
||||
.padding(.trailing)
|
||||
.padding(.top)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("23.01.1988")
|
||||
Text("14:37 Uhr")
|
||||
Spacer()
|
||||
}
|
||||
Text("Marathon")
|
||||
.fontWeight(.semibold)
|
||||
HStack {
|
||||
Text("34 km/42 km")
|
||||
Text("•")
|
||||
Text("5:12 min/km")
|
||||
}.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Text("1 h")
|
||||
Text("•")
|
||||
Text("1.337 kcal")
|
||||
}
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: Text("WorkoutLogDetails")) {
|
||||
HStack {
|
||||
Image(systemName: "figure.run")
|
||||
.padding(.trailing)
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("23.01.1988")
|
||||
Text("14:37 Uhr")
|
||||
Spacer()
|
||||
}
|
||||
Text("Recommended Routine")
|
||||
.fontWeight(.semibold)
|
||||
HStack {
|
||||
Text("6/12 sets")
|
||||
Text("•")
|
||||
Text("50 %")
|
||||
}.foregroundStyle(.gray)
|
||||
HStack {
|
||||
Text("57 m")
|
||||
Text("•")
|
||||
Text("357 kcal")
|
||||
}
|
||||
.foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitle("Workout Logs")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
EditButton()
|
||||
}
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabItem {
|
||||
Image(systemName: "pencil.and.list.clipboard")
|
||||
Text("Log")
|
||||
}
|
||||
Text("Training Plans")
|
||||
.tabItem {
|
||||
Image(systemName: "calendar.badge.clock")
|
||||
Text("Plans & Goals")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
WorkoutLog()
|
||||
}
|
||||
Reference in New Issue
Block a user