retain order of the exercises during persistence process

This commit is contained in:
Felix Förtsch
2024-08-30 16:01:38 +02:00
parent 4afc656374
commit 22b0ba372e
10 changed files with 244 additions and 135 deletions

View 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: {})
}

View File

@@ -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")
}
}
}
}

View 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()
}

View File

@@ -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]()

View File

@@ -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)
}

View 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()
}

View File

@@ -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] = [

View File

@@ -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
) {}
}
}
}

View File

@@ -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
}
}

View 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()
}