change folders to the "feature" mindset

This commit is contained in:
Felix Förtsch
2024-10-21 15:03:47 +02:00
parent 97ecbcc6f4
commit d1a87957f6
34 changed files with 121 additions and 139 deletions

View File

@@ -49,19 +49,19 @@ struct SetListItem: View {
} }
} }
#Preview { //#Preview {
@Previewable @State var set = WorkoutItem(set: [ // @Previewable @State var set = WorkoutItem(set: [
WorkoutItem(Exercise("Squat")), // WorkoutItem(Exercise("Squat")),
WorkoutItem(Exercise("Squat")), // WorkoutItem(Exercise("Squat")),
WorkoutItem(Exercise("Squat"))]) // WorkoutItem(Exercise("Squat"))])
List { // List {
SetListItem(workout: Workout(name: "RR"), set: $set) // SetListItem(workout: Workout(name: "RR"), set: $set)
} // }
} //}
//
#Preview("Empty Database") { //#Preview("Empty Database") {
@Previewable @State var set = WorkoutItem(set: []) // @Previewable @State var set = WorkoutItem(set: [])
List { // List {
SetListItem(workout: Workout(name: "RR"), set: $set) // SetListItem(workout: Workout(name: "RR"), set: $set)
} // }
} //}

View File

@@ -49,8 +49,7 @@ struct ContentView: View {
Section { Section {
NavigationLink( NavigationLink(
destination: ActiveWorkoutSession( destination: ActiveWorkoutSession(
activeWorkoutSession: $activeWorkoutSession, activeWorkoutSession: $activeWorkoutSession)
activeWorkout: activeWorkoutSession.workout)
) { ) {
HStack { HStack {
Label("Back to Session", systemImage: "memories") Label("Back to Session", systemImage: "memories")

View File

@@ -75,7 +75,7 @@ struct WorkoutLog: View {
ForEach(workoutSessions) { session in ForEach(workoutSessions) { session in
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(session.startDate.ISO8601Format()) Text(session.startDate.ISO8601Format())
Text(session.workout.name) Text(session.name)
.font(.subheadline) .font(.subheadline)
} }
}.onDelete(perform: deleteWorkoutSession) }.onDelete(perform: deleteWorkoutSession)

View File

@@ -37,14 +37,14 @@ final class WorkoutItem: Nameable, Positionable {
var id = UUID() var id = UUID()
var name: String var name: String
var workout: Workout? var workout: Workout?
// TODO: Make WorkoutItem a protocol so the type can be distinguished by the class type
var workoutItemType: WorkoutItemType // Differentiates between exercise/rest/set var workoutItemType: WorkoutItemType // Differentiates between exercise/rest/set
var position: Int = 0 var position: Int = 0
var set: [WorkoutItem] = [] var set: [WorkoutItem] = []
// Exercise has to be optional to allow Rest and Set to be a WorkoutItem (without being an Exercise). var exercise: Exercise // Do Push-up | Run Marathon
var exerciseData: Exercise? // Do Push-up | Run Marathon var plannedReps: Int // 8 times | 1 time
var plannedReps: Int // 8 times | 1 time var plannedValue: Double // With 10 | 42,187
var plannedValue: Double // With 10 | 42,187
var metric: ExerciseMetric? // kg (weight) | km (distance) var metric: ExerciseMetric? // kg (weight) | km (distance)
enum WorkoutItemType: Codable { enum WorkoutItemType: Codable {
@@ -54,7 +54,7 @@ final class WorkoutItem: Nameable, Positionable {
} }
init(_ exercise: Exercise) { init(_ exercise: Exercise) {
self.exerciseData = exercise self.exercise = exercise
self.workoutItemType = .exercise self.workoutItemType = .exercise
// Push-up // Push-up
@@ -67,27 +67,25 @@ final class WorkoutItem: Nameable, Positionable {
self.metric = exercise.metric self.metric = exercise.metric
} }
init(set: [WorkoutItem] = []) { // init(set: [WorkoutItem] = []) {
self.workoutItemType = .set // self.workoutItemType = .set
self.name = "Set" // self.name = "Set"
self.plannedReps = 3 // self.plannedReps = 3
self.plannedValue = 0 // self.plannedValue = 0
set.forEach(addChild) // set.forEach(addChild)
} // }
// init(rest: Double) {
// self.workoutItemType = .rest
// self.name = "Rest"
// self.plannedReps = 1
// self.plannedValue = rest
// self.metric = .time
// }
func addChild(_ child: WorkoutItem) { func addChild(_ child: WorkoutItem) {
if self.workoutItemType == .set { if self.workoutItemType == .set {
self.set.append(child) self.set.append(child)
} }
} }
init(rest: Double) {
self.workoutItemType = .rest
self.name = "Rest"
self.plannedReps = 1
self.plannedValue = rest
self.metric = .time
}
} }
extension WorkoutItem { extension WorkoutItem {

View File

@@ -19,17 +19,17 @@ struct WorkoutItemLibrarySheet: View {
var body: some View { var body: some View {
Group { Group {
List { List {
Section(header: Text("Utilities")) { // Section(header: Text("Utilities")) {
AddItemButton(label: "Set") { // AddItemButton(label: "Set") {
addWorkoutItemtoWorkout(WorkoutItem(set: [ // addWorkoutItemtoWorkout(WorkoutItem(set: [
WorkoutItem(Exercise("Set item 1")), // WorkoutItem(Exercise("Set item 1")),
WorkoutItem(Exercise("Set item 2")) // WorkoutItem(Exercise("Set item 2"))
])) // ]))
} // }
AddItemButton(label: "Rest") { // AddItemButton(label: "Rest") {
addWorkoutItemtoWorkout(WorkoutItem(rest: 45)) // addWorkoutItemtoWorkout(WorkoutItem(rest: 45))
} // }
} // }
Section(header: Text("Excersises")) { Section(header: Text("Excersises")) {
if !exercises.isEmpty { if !exercises.isEmpty {
ForEach(exercises) { exercise in ForEach(exercises) { exercise in

View File

@@ -34,18 +34,20 @@ struct WorkoutLibrary: View {
NavigationLink { NavigationLink {
WorkoutDetail(activeWorkoutSession: $activeWorkoutSession, workout: workout) WorkoutDetail(activeWorkoutSession: $activeWorkoutSession, workout: workout)
} label: { } label: {
Button(action: { Image(systemName: workout.workoutIconSystemName)
activeWorkoutSession = workout.start() .foregroundStyle(workout.workoutIconColorName.color)
}) {
Image(systemName: "play.fill")
.foregroundStyle(.green)
}
// TODO: Decide if icon should appear here/create custom view
// Image(systemName: workout.workoutIconSystemName)
// .foregroundStyle(workout.workoutIconColorName.color)
Text(workout.name) Text(workout.name)
} }
.swipeActions(edge: .leading) {
Button {
activeWorkoutSession = workout.start()
} label: {
Label("Start", systemImage: "play")
.tint(.green)
}
}
} }
.onDelete(perform: deleteWorkout) .onDelete(perform: deleteWorkout)
if filteredItems.isEmpty { if filteredItems.isEmpty {
ContentUnavailableView.search(text: searchText) ContentUnavailableView.search(text: searchText)

View File

@@ -37,12 +37,12 @@ struct WorkoutListItem: View {
#Preview { #Preview {
List { List {
WorkoutListItem(Workout(name: "RR"), WorkoutItem(set: [ // WorkoutListItem(Workout(name: "RR"), WorkoutItem(set: [
WorkoutItem(Exercise("Squat")), // WorkoutItem(Exercise("Squat")),
WorkoutItem(Exercise("Squat")), // WorkoutItem(Exercise("Squat")),
WorkoutItem(Exercise("Squat"))])) // WorkoutItem(Exercise("Squat"))]))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups"))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups")))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(rest: 15)) // WorkoutListItem(Workout(name: "RR"), WorkoutItem(rest: 15))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups"))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Push-ups")))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Sprint", .distance))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Sprint", .distance)))
WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Run", .time))) WorkoutListItem(Workout(name: "RR"), WorkoutItem(Exercise("Run", .time)))

View File

@@ -1,6 +1,7 @@
import SwiftUI import SwiftUI
import SwiftData import SwiftData
// This view can only be viewed, if there exists a WorkoutSession that can be considered active (a person working out).
struct ActiveWorkoutSession: View { struct ActiveWorkoutSession: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
@@ -10,17 +11,14 @@ struct ActiveWorkoutSession: View {
@Query(sort: \WorkoutSession.name) var workoutSessions: [WorkoutSession] @Query(sort: \WorkoutSession.name) var workoutSessions: [WorkoutSession]
@Binding var activeWorkoutSession: WorkoutSession? @Binding var activeWorkoutSession: WorkoutSession?
@Query(sort: \Workout.name) var workouts: [Workout]
@State var activeWorkout: Workout
var body: some View { var body: some View {
VStack { VStack {
List { List {
Section(header: Text("Workout")) { Section(header: Text("Workout")) {
Text(activeWorkout.name) Text(activeWorkoutSession!.name)
} }
Section(header: Text("Exercises")) { Section(header: Text("Exercises")) {
ForEach(getActiveWorkoutItems(activeWorkout: activeWorkout)) { workoutItem in ForEach(activeWorkoutSession!.workoutSessionItems) { workoutItem in
ActiveWorkoutSessionListItem(workoutItem: workoutItem) ActiveWorkoutSessionListItem(workoutItem: workoutItem)
} }
} }
@@ -28,7 +26,6 @@ struct ActiveWorkoutSession: View {
} }
.navigationTitle("Session") .navigationTitle("Session")
.toolbar { .toolbar {
if (isWorkingOut) {
Button(action: { Button(action: {
isWorkingOut = false isWorkingOut = false
activeWorkoutSession?.stop() activeWorkoutSession?.stop()
@@ -41,21 +38,6 @@ struct ActiveWorkoutSession: View {
.bold() .bold()
.fontDesign(.rounded) .fontDesign(.rounded)
.tint(.red) .tint(.red)
}
else {
Button(action: {
isWorkingOut = true
activeWorkoutSession = activeWorkout.start()
}) {
HStack {
Image(systemName: "play.fill")
Text("Start")
}
}
.bold()
.fontDesign(.rounded)
.tint(.green)
}
} }
} }
@@ -76,7 +58,7 @@ struct ActiveWorkoutSession: View {
@Previewable @State var workout = Workout.sampleData.first! @Previewable @State var workout = Workout.sampleData.first!
NavigationStack { NavigationStack {
ActiveWorkoutSession(activeWorkoutSession: $activeWorkoutSession, activeWorkout: workout) ActiveWorkoutSession(activeWorkoutSession: $activeWorkoutSession)
} }
.onAppear { .onAppear {
Defaults.shared.isWorkingOut = false Defaults.shared.isWorkingOut = false

View File

@@ -8,14 +8,10 @@
import SwiftUI import SwiftUI
struct ActiveWorkoutSessionListItem: View { struct ActiveWorkoutSessionListItem: View {
var workoutItem: WorkoutItem var workoutItem: WorkoutSessionItem
var body: some View { var body: some View {
HStack { HStack {
switch workoutItem.workoutItemType {
case .set:
Text(workoutItem.name)
case .exercise:
Text(workoutItem.name) Text(workoutItem.name)
Spacer() Spacer()
Button(action: { Button(action: {
@@ -24,17 +20,15 @@ struct ActiveWorkoutSessionListItem: View {
Image(systemName: "info.circle") Image(systemName: "info.circle")
.foregroundColor(.blue) .foregroundColor(.blue)
} }
case .rest:
Text("Pause")
}
} }
} }
} }
#Preview { #Preview {
@Previewable @State var workoutSession = WorkoutSession(start: Workout.sampleData.first!)
List { List {
ForEach(WorkoutItem.sampleDataRecommendedRoutine) { item in ForEach(WorkoutItem.sampleDataRecommendedRoutine) { item in
ActiveWorkoutSessionListItem(workoutItem: item) ActiveWorkoutSessionListItem(workoutItem: WorkoutSessionItem(workoutSession: workoutSession, planned: item))
} }
} }
} }

View File

@@ -11,16 +11,16 @@ import SwiftData
@Model @Model
final class WorkoutSession: Nameable { final class WorkoutSession: Nameable {
var id = UUID() var id = UUID()
var name = "" var name: String
// The Workout is what *should* happen // TODO: Think about if a refrence to the workout makes sense; a Workout could change.
var workout: Workout { // var workout: Workout
didSet { var workoutSessionItems: [WorkoutSessionItem] = []
self.name = workout?.name ?? "Unknown Workout"
}
}
init(start with: Workout) { init(start workout: Workout) {
self.workout = with self.name = workout.name
for workoutItem in workout.getWorkoutItems() {
workoutSessionItems.append(WorkoutSessionItem(workoutSession: self, planned: workoutItem))
}
} }
// State // State
@@ -69,9 +69,7 @@ final class WorkoutSession: Nameable {
} }
func nextExercise() { func nextExercise() {
if currentExercise < workout.getWorkoutItems().count - 1 {
currentExercise += 1
}
} }
// MARK: -- Workout Information // MARK: -- Workout Information
@@ -84,7 +82,7 @@ final class WorkoutSession: Nameable {
} }
func getTotalExerciseCount() -> Double { func getTotalExerciseCount() -> Double {
return Double(workout.getWorkoutItems().count) return 100.0
} }
func getCurrentExerciseIndex() -> Double { func getCurrentExerciseIndex() -> Double {
@@ -96,10 +94,10 @@ final class WorkoutSession: Nameable {
} }
func getCurrentExerciseName() -> String { func getCurrentExerciseName() -> String {
return workout.getWorkoutItems()[Int(currentExercise)].name return "Hello"
} }
func getCurrentExerciseMetric() -> String { func getCurrentExerciseMetric() -> String {
return String(workout.getWorkoutItems()[Int(currentExercise)].plannedReps) return "Hello"
} }
} }

View File

@@ -0,0 +1,34 @@
//
// WorkoutSessionItem.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 15.10.24.
//
import Foundation
import SwiftData
@Model
final class WorkoutSessionItem: Nameable, Positionable {
var id = UUID()
var position: Int
var name: String
var workoutSession: WorkoutSession
var plannedReps: Int // 8 times | 1 time
var plannedValue: Double // With 10 | 42,187
var metric: ExerciseMetric? // kg (weight) | km (distance)
var actualReps: Int?
var actualValue: Double?
init(workoutSession: WorkoutSession, planned: WorkoutItem) {
self.workoutSession = workoutSession
self.name = planned.exercise.name
self.position = planned.position
self.plannedReps = planned.plannedReps
self.plannedValue = planned.plannedValue
self.metric = planned.metric
}
}

View File

@@ -1,25 +0,0 @@
//
// WorkoutSessionItem.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 15.10.24.
//
import Foundation
import SwiftData
@Model
final class WorkoutSessionItem {
var id = UUID()
var workoutSession: WorkoutSession
var exerciseData: WorkoutItem
var actualReps: Int?
var actualValue: Double?
init(workoutSession: WorkoutSession, planned: WorkoutItem) {
self.workoutSession = workoutSession
self.exerciseData = planned
}
}

View File

@@ -3,10 +3,10 @@ erDiagram
Exercise 1 .. 0+ Equipment : uses Exercise 1 .. 0+ Equipment : uses
Exercise 1 -- 0+ WorkoutItem : "provides data for" Exercise 1 -- 0+ WorkoutItem : "provides data for"
Workout 1 .. 0+ WorkoutItem : collects Workout 1 .. 0+ WorkoutItem : collects
Workout 1 .. 0+ ViewModel : "provides data for" Workout 1 .. 0+ WorkoutSession : "starts"
WorkoutItem 1 -- 1 WorkoutSessionItem : "provides data for" WorkoutItem 1 -- 1 WorkoutSessionItem : "provides data for"
WorkoutSession 1 -- 1+ WorkoutSessionItem : collects WorkoutSession 1 -- 1+ WorkoutSessionItem : collects
ViewModel 1 -- 1+ WorkoutSession : "creates, starts" ContentView 1 -- 1 WorkoutSession : "holds onto"
Exercise { Exercise {
string name string name
@@ -29,7 +29,7 @@ erDiagram
int actualReps int actualReps
double actualValue double actualValue
} }
ViewModel { ContentView {
Workout workout Workout workout
WorkoutSession workoutSession WorkoutSession workoutSession
} }