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

@@ -0,0 +1,78 @@
import SwiftUI
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 {
@Environment(\.modelContext) private var modelContext
@Default(\.isWorkingOut) var isWorkingOut
@State var isTimerRunning: Bool = true
@Query(sort: \WorkoutSession.name) var workoutSessions: [WorkoutSession]
@Binding var activeWorkoutSession: WorkoutSession?
var body: some View {
VStack {
List {
Section(header: Text("Workout")) {
Text(activeWorkoutSession!.name)
}
Section(header: Text("Exercises")) {
ForEach(activeWorkoutSession!.workoutSessionItems) { workoutItem in
ActiveWorkoutSessionListItem(workoutItem: workoutItem)
}
}
}
}
.navigationTitle("Session")
.toolbar {
Button(action: {
isWorkingOut = false
activeWorkoutSession?.stop()
}) {
HStack {
Image(systemName: "stop.fill")
Text("Stop")
}
}
.bold()
.fontDesign(.rounded)
.tint(.red)
}
}
private func getActiveWorkoutItems(activeWorkout: Workout?) -> [WorkoutItem] {
guard let activeWorkout else { return [] }
return activeWorkout.getWorkoutItems()
}
// TODO: Put this somewhere general
private func getItem<Item: Nameable>(from array: [Item], by id: String) -> Item? {
let filteredItems = array.filter { $0.id == UUID(uuidString: id) }
return filteredItems.count == 1 ? filteredItems.first : nil
}
}
#Preview {
@Previewable @State var activeWorkoutSession: WorkoutSession?
@Previewable @State var workout = Workout.sampleData.first!
NavigationStack {
ActiveWorkoutSession(activeWorkoutSession: $activeWorkoutSession)
}
.onAppear {
Defaults.shared.isWorkingOut = false
}
.modelContainer(SampleData.shared.modelContainer)
}
//#Preview("Empty modelContainer") {
// @Previewable @State var activeWorkoutSession: WorkoutSession?
//
// NavigationStack {
// ActiveWorkoutSession(activeWorkoutSession: $activeWorkoutSession)
// }
// .onAppear {
// Defaults.shared.isWorkingOut = false
// }
//}

View File

@@ -0,0 +1,73 @@
//
// ActiveWorkoutSessionControls.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 16.09.24.
//
import SwiftUI
struct ActiveWorkoutSessionControls: View {
@Binding var session: WorkoutSession
var body: some View {
VStack {
HStack {
Text(session.getCurrentTodo())
.font(.system(size: 24, weight: .bold, design: .rounded))
}
ProgressView("",
value: session.getCurrentExerciseIndex(),
total: session.getTotalExerciseCount() - 1
)
HStack {
Button(action: {
session.prevExercise()
}) {
HStack {
Image(systemName: "backward.end.fill")
Text("Prev")
}
}
.buttonStyle(.borderedProminent)
.bold()
.tint(.primary)
Button(action: {
// TODO: Implement proper Pausing
session.pause()
}) {
HStack {
Image(systemName: "pause.fill")
Text("Pause")
}
}
.buttonStyle(.borderedProminent)
.bold()
.tint(.gray)
.disabled(true)
Button(action: {
session.nextExercise()
}) {
HStack {
Text("Next")
Image(systemName: "forward.end.fill")
}
}
.buttonStyle(.borderedProminent)
.bold()
.tint(.primary)
}
}
}
}
#Preview("isWorkingOut = true") {
@Previewable @State var activeWorkoutSession = WorkoutSession(start: Workout.sampleData.first!)
// For some reason the return keyword is required here to avoid the error "Type of expression is ambiguous without a type annotation"
return ActiveWorkoutSessionControls(session: $activeWorkoutSession)
.onAppear() {
Defaults.shared.isWorkingOut = true
}
}

View File

@@ -0,0 +1,34 @@
//
// ActiveWorkoutSessionListItem.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 19.09.24.
//
import SwiftUI
struct ActiveWorkoutSessionListItem: View {
var workoutItem: WorkoutSessionItem
var body: some View {
HStack {
Text(workoutItem.name)
Spacer()
Button(action: {
// TODO: Implement a sheet view; don't use ExerciseDetail since its purpose is editing
}) {
Image(systemName: "info.circle")
.foregroundColor(.blue)
}
}
}
}
#Preview {
@Previewable @State var workoutSession = WorkoutSession(start: Workout.sampleData.first!)
List {
ForEach(WorkoutItem.sampleDataRecommendedRoutine) { item in
ActiveWorkoutSessionListItem(workoutItem: WorkoutSessionItem(workoutSession: workoutSession, planned: item))
}
}
}

View File

@@ -0,0 +1,103 @@
//
// WorkoutSession.swift
// WorkoutsPlus
//
// Created by Felix Förtsch on 07.09.24.
//
import Foundation
import SwiftData
@Model
final class WorkoutSession: Nameable {
var id = UUID()
var name: String
// TODO: Think about if a refrence to the workout makes sense; a Workout could change.
// var workout: Workout
var workoutSessionItems: [WorkoutSessionItem] = []
init(start workout: Workout) {
self.name = workout.name
for workoutItem in workout.getWorkoutItems() {
workoutSessionItems.append(WorkoutSessionItem(workoutSession: self, planned: workoutItem))
}
}
// State
// var isPaused: Bool
// var isCancelled: Bool
// var isDeleted: Bool
// var isSynced: Bool
// My workout session started at:
var startDate: Date = Date.now
// My workout session was completed at:
var stopDate: Date? = nil
// My workout session took me x seconds.
var duration: TimeInterval? = nil
// My workout session is completed and moved to the workout log.
var isCompleted: Bool = false
// Exercise Progress
var currentExercise = 0
func isActive() -> Bool {
return stopDate == nil
}
// MARK: -- Workout Controls
// func start(with workout: Workout) {
// self.workout = workout
// startDate = Date.now
// }
func pause() {
// TODO: Implement proper Pause
}
// Call stop() to terminate the workout.
func stop() {
isCompleted = true
stopDate = Date.now
duration = stopDate!.timeIntervalSince(startDate)
}
func prevExercise() {
if currentExercise > 0 {
currentExercise -= 1
}
}
func nextExercise() {
}
// MARK: -- Workout Information
func getFormattedDuration() -> String {
let elapsedTime = Date.now.timeIntervalSince(startDate)
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .positional
return formatter.string(from: elapsedTime) ?? "00:00:00"
}
func getTotalExerciseCount() -> Double {
return 100.0
}
func getCurrentExerciseIndex() -> Double {
return Double(currentExercise)
}
func getCurrentTodo() -> String {
return getCurrentExerciseMetric() + " " + getCurrentExerciseName()
}
func getCurrentExerciseName() -> String {
return "Hello"
}
func getCurrentExerciseMetric() -> String {
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
}
}