change folders to the "feature" mindset
This commit is contained in:
@@ -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
|
||||
// }
|
||||
//}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
103
WorkoutsPlus/Features/WorkoutSession/WorkoutSession.swift
Normal file
103
WorkoutsPlus/Features/WorkoutSession/WorkoutSession.swift
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user