add always sorted [WorkoutItem], ContentUnavailableView to searches, SampleData import, refactor WorkoutItem init
This commit is contained in:
@@ -5,23 +5,24 @@ struct ActiveWorkoutSession: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
@Default(\.isWorkingOut) var isWorkingOut
|
||||
@State var isTimerRunning: Bool = true
|
||||
|
||||
@Query(sort: \WorkoutSession.name) private var workoutSessions: [WorkoutSession]
|
||||
@State private var activeWorkoutSession: WorkoutSession?
|
||||
@Query(sort: \WorkoutSession.name) var workoutSessions: [WorkoutSession]
|
||||
@State var activeWorkoutSession: WorkoutSession?
|
||||
@Default(\.activeWorkoutSessionId) var activeWorkoutSessionId
|
||||
|
||||
@Query(sort: \Workout.name) private var workouts: [Workout]
|
||||
@State private var activeWorkout: Workout?
|
||||
@Query(sort: \Workout.name) var workouts: [Workout]
|
||||
@State var activeWorkout: Workout?
|
||||
@Default(\.activeWorkoutId) var activeWorkoutId
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
Section(footer: Text(activeWorkoutSession?.creationDate.ISO8601Format() ?? "Unknown Date")) {
|
||||
Section(header: Text("Workout"), footer: Text(activeWorkoutSession?.creationDate.ISO8601Format() ?? "Unknown Date")) {
|
||||
NavigationLink(destination: {
|
||||
ItemPicker<Workout>(items: workouts, selectedItem: $activeWorkout)
|
||||
}) {
|
||||
Text(activeWorkout?.name ?? "Select your next Workout")
|
||||
Text(activeWorkout?.name ?? "Select Workout")
|
||||
}
|
||||
.onChange(of: activeWorkout) { _, newWorkout in
|
||||
if let workout = newWorkout {
|
||||
@@ -33,7 +34,7 @@ struct ActiveWorkoutSession: View {
|
||||
|
||||
if let activeWorkout = activeWorkout {
|
||||
Section(header: Text("Exercises")) {
|
||||
ForEach(activeWorkout.workoutItems.sorted(by: { $0.position < $1.position })) { workoutItem in
|
||||
ForEach(getActiveWorkoutItems(activeWorkout: activeWorkout)) { workoutItem in
|
||||
HStack {
|
||||
Text(String(workoutItem.reps))
|
||||
Text(workoutItem.name)
|
||||
@@ -47,65 +48,51 @@ struct ActiveWorkoutSession: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if true { // This condition should be more meaningful.
|
||||
VStack {
|
||||
HStack {
|
||||
Text(String(workoutSessions.count))
|
||||
Button(action: {
|
||||
// save(workoutSession: activeWorkoutSession)
|
||||
}) {
|
||||
Text("Save Session")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
} else {
|
||||
ContentUnavailableView {
|
||||
Label("Select a Workout", systemImage: "arrow.up")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- Workout Controls
|
||||
Group {
|
||||
if activeWorkoutSession?.workout != nil {
|
||||
if isWorkingOut {
|
||||
// MARK: -- Stop Workout
|
||||
VStack {
|
||||
ProgressView("", value: 10, total: 100)
|
||||
TimerView(isActive: $isWorkingOut)
|
||||
.font(.title)
|
||||
.bold()
|
||||
Button(action: {
|
||||
isWorkingOut = false
|
||||
activeWorkoutSession?.startWorkoutSession()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "stop.fill")
|
||||
Text("Stop Workout")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.bold()
|
||||
.tint(.red)
|
||||
}
|
||||
} else {
|
||||
// MARK: -- Start Workout
|
||||
Button(action: {
|
||||
isWorkingOut = true
|
||||
activeWorkoutSession?.stopWorkoutSession()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
Text("Start Workout")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.bold()
|
||||
.tint(.green)
|
||||
}
|
||||
if (isWorkingOut) {
|
||||
if activeWorkoutSession != nil {
|
||||
ActiveWorkoutSessionControls(
|
||||
session: Binding(
|
||||
get: { self.activeWorkoutSession! },
|
||||
set: { self.activeWorkoutSession = $0 }
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Workout Session")
|
||||
.navigationTitle("Session")
|
||||
.toolbar {
|
||||
if (isWorkingOut) {
|
||||
Button(action: {
|
||||
isWorkingOut = false
|
||||
activeWorkoutSession?.stop()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "stop.fill")
|
||||
Text("Stop")
|
||||
}
|
||||
}
|
||||
.bold()
|
||||
.tint(.red)
|
||||
} else {
|
||||
Button(action: {
|
||||
isWorkingOut = true
|
||||
activeWorkoutSession?.start()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: "play.fill")
|
||||
Text("Start")
|
||||
}
|
||||
}
|
||||
.bold()
|
||||
.tint(.green)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Load the active workout session and workout onAppear
|
||||
if let activeWorkoutSession = getItem(from: workoutSessions, by: activeWorkoutSessionId) {
|
||||
@@ -128,15 +115,22 @@ struct ActiveWorkoutSession: View {
|
||||
self.activeWorkoutSession = newWorkoutSession
|
||||
}
|
||||
|
||||
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 {
|
||||
#Preview("RR Selected") {
|
||||
@Previewable @State var activeWorkout = Workout.sampleData.first!
|
||||
NavigationStack {
|
||||
ActiveWorkoutSession()
|
||||
ActiveWorkoutSession(activeWorkout: activeWorkout)
|
||||
}
|
||||
.modelContainer(SampleData.shared.modelContainer)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// 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())
|
||||
}
|
||||
ProgressView("",
|
||||
value: session.getCurrentExerciseIndex() + 1,
|
||||
total: session.getTotalExerciseCount()
|
||||
)
|
||||
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()
|
||||
activeWorkoutSession.workout = 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
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("isWorkingOut = false") {
|
||||
@Previewable @State var activeWorkoutSession = WorkoutSession()
|
||||
|
||||
return ActiveWorkoutSessionControls(session: $activeWorkoutSession)
|
||||
.onAppear() {
|
||||
Defaults.shared.isWorkingOut = false
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,6 @@ struct ExerciseListItem: View {
|
||||
|
||||
#Preview {
|
||||
List {
|
||||
ExerciseListItem(Workout(name: "RR"), WorkoutItem(from: Exercise("Push-ups")))
|
||||
ExerciseListItem(Workout(name: "RR"), WorkoutItem(exercise: Exercise("Push-ups")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ struct SetListItem: View {
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
}
|
||||
ForEach(set.workoutItems) { workoutItem in
|
||||
ForEach(set.set) { workoutItem in
|
||||
ExerciseListItem(workout, workoutItem)
|
||||
.padding(.leading)
|
||||
}
|
||||
@@ -55,17 +55,17 @@ struct SetListItem: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
let set = WorkoutItem(workoutItems: [
|
||||
WorkoutItem(10, "Squat"),
|
||||
WorkoutItem(10, "Squat"),
|
||||
WorkoutItem(10, "Squat")])
|
||||
let set = WorkoutItem(set: [
|
||||
WorkoutItem(reps: 10, "Squat"),
|
||||
WorkoutItem(reps: 10, "Squat"),
|
||||
WorkoutItem(reps: 10, "Squat")])
|
||||
List {
|
||||
SetListItem(Workout(name: "RR"), set)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Empty Database") {
|
||||
let set = WorkoutItem(workoutItems: [])
|
||||
let set = WorkoutItem(set: [])
|
||||
List {
|
||||
SetListItem(Workout(name: "RR"), set)
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ struct StepperListItem: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
@Previewable @State var value = 8
|
||||
@Previewable @State var itemValue = 8
|
||||
|
||||
List {
|
||||
StepperListItem(itemName: "Short Name", itemValue: $value)
|
||||
StepperListItem(itemName: "Very very very very long name with whitespace", itemValue: $value)
|
||||
StepperListItem(itemName: "Veryveryverylonglonglonglongnamewithoutwithwhitespace", itemValue: $value)
|
||||
StepperListItem(itemName: "Short Name", itemValue: $itemValue)
|
||||
StepperListItem(itemName: "Very very very very long name with whitespace", itemValue: $itemValue)
|
||||
StepperListItem(itemName: "Veryveryverylonglonglonglongnamewithoutwithwhitespace", itemValue: $itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,18 @@ import SwiftUI
|
||||
struct TimerView: View {
|
||||
@Binding var isActive: Bool
|
||||
@State private var time = 0
|
||||
|
||||
var startDate: Date?
|
||||
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
|
||||
var body: some View {
|
||||
Text("\(time)")
|
||||
.onReceive(timer) { _ in
|
||||
if isActive {
|
||||
self.time += 1
|
||||
if let startDate = startDate {
|
||||
self.time = Int(Date.now.timeIntervalSince(startDate))
|
||||
} else {
|
||||
self.time += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
@@ -26,6 +30,9 @@ struct TimerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TimerView(isActive: .constant(true), startDate: Date().addingTimeInterval(-3600)) // Example startDate 1 hour ago
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TimerView(isActive: .constant(true))
|
||||
|
||||
@@ -59,8 +59,6 @@ struct ContentView: View {
|
||||
NavigationLink(destination: WorkoutLog()) {
|
||||
Label("Workout Log", systemImage: "calendar.badge.clock")
|
||||
}
|
||||
}
|
||||
Section {
|
||||
NavigationLink(destination: WorkoutLibrary()) {
|
||||
Label("Workouts", systemImage: "figure.run.square.stack")
|
||||
}
|
||||
@@ -69,12 +67,14 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
Section {
|
||||
|
||||
Label("Add Food", systemImage: "plus")
|
||||
Label("Food Log", systemImage: "list.bullet.clipboard")
|
||||
Label("Food Library", systemImage: "fork.knife")
|
||||
}
|
||||
Section {
|
||||
NavigationLink(destination: Settings()) {
|
||||
Label("Settings", systemImage: "gear")
|
||||
}
|
||||
}
|
||||
Section(header: Text("Debug")) {
|
||||
NavigationLink(destination: DebugList()) {
|
||||
Label("Debug", systemImage: "hammer")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct DebugList: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
@Query(sort: \Exercise.name) private var exercises: [Exercise]
|
||||
@Query(sort: \Workout.name) private var workouts: [Workout]
|
||||
@Query(sort: \WorkoutItem.name) private var workoutItems: [WorkoutItem]
|
||||
@@ -16,6 +18,9 @@ struct DebugList: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Button(action: {SampleData.insertSampleData(into: modelContext)} ) {
|
||||
Text("Insert Sample Data")
|
||||
}
|
||||
Section(header: Text("Exercises")) {
|
||||
ForEach(exercises) { exercise in
|
||||
DebugListItem(item: exercise)
|
||||
|
||||
@@ -24,13 +24,15 @@ class SampleData {
|
||||
|
||||
do {
|
||||
modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
|
||||
insertSampleData()
|
||||
SampleData.insertSampleData(into: context)
|
||||
} catch {
|
||||
fatalError("Could not create ModelContainer: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func insertSampleData() {
|
||||
}
|
||||
|
||||
extension SampleData {
|
||||
static func insertSampleData(into context: ModelContext) {
|
||||
for exercise in Exercise.sampleDataRecommendedRoutine {
|
||||
context.insert(exercise)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ struct ExerciseLibrary: View {
|
||||
ExerciseDetail(exercise: exercise)
|
||||
} label: {
|
||||
Text(exercise.name)
|
||||
// TODO: show exercise.metric in gray (eg Dips = reps, Intervall = time or = distance?)
|
||||
}
|
||||
}
|
||||
.onDelete(perform: deleteExercise)
|
||||
@@ -49,15 +50,24 @@ struct ExerciseLibrary: View {
|
||||
.textInputAutocapitalization(.words)
|
||||
.focused($isInputFieldFocused)
|
||||
}
|
||||
if filteredItems.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
}
|
||||
AddItemButton(label: "Exercise", action: addExercise)
|
||||
}
|
||||
.searchable(text: $searchText)
|
||||
|
||||
}
|
||||
.navigationTitle("Exercises")
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
EditButton()
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ final class Workout: Nameable, Hashable {
|
||||
// Other properties and methods
|
||||
var timestamp = Date.now
|
||||
|
||||
@Relationship(deleteRule: .cascade) var workoutItems: [WorkoutItem] = []
|
||||
@Relationship(deleteRule: .cascade) private var workoutItems: [WorkoutItem] = []
|
||||
func getWorkoutItems() -> [WorkoutItem] {
|
||||
return workoutItems.sorted { $0.position < $1.position }
|
||||
}
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
@@ -113,9 +116,9 @@ extension Workout {
|
||||
}
|
||||
|
||||
extension Workout {
|
||||
private convenience init(name: String, exercises: [WorkoutItem]) {
|
||||
private convenience init(name: String, workoutItems: [WorkoutItem]) {
|
||||
self.init(name: name)
|
||||
self.workoutItems = exercises
|
||||
self.workoutItems = workoutItems
|
||||
}
|
||||
|
||||
static let sampleData: [Workout] = {
|
||||
|
||||
@@ -17,7 +17,8 @@ final class WorkoutItem: Nameable, Positionable {
|
||||
var workoutItemType: WorkoutItemType
|
||||
var position: Int = 0
|
||||
|
||||
var reps: Int = 8
|
||||
var reps: Int = 0
|
||||
var duration: Int = 0
|
||||
|
||||
// EXERCISE
|
||||
var exercise: Exercise?
|
||||
@@ -28,36 +29,49 @@ final class WorkoutItem: Nameable, Positionable {
|
||||
|
||||
// TODO: Deload items -> Think about how to model deload/sick/rest/holiday week
|
||||
|
||||
init(_ reps: Int, _ exercise: String) {
|
||||
// Exercise
|
||||
init(reps: Int, _ exercise: String) {
|
||||
self.workoutItemType = .exercise
|
||||
|
||||
self.name = exercise
|
||||
self.reps = reps
|
||||
self.exercise = Exercise(exercise)
|
||||
}
|
||||
|
||||
init(from exercise: Exercise) {
|
||||
init(duration: Int, _ exercise: String) {
|
||||
self.workoutItemType = .exercise
|
||||
self.name = exercise
|
||||
self.duration = duration
|
||||
self.exercise = Exercise(exercise)
|
||||
}
|
||||
|
||||
init(exercise: Exercise) {
|
||||
self.workoutItemType = .exercise
|
||||
|
||||
self.name = exercise.name
|
||||
self.exercise = exercise
|
||||
}
|
||||
|
||||
// SET
|
||||
var workoutItems: [WorkoutItem] = []
|
||||
var set: [WorkoutItem] = []
|
||||
|
||||
init(workoutItems: [WorkoutItem] = []) {
|
||||
self.name = "Set"
|
||||
init(set: [WorkoutItem] = []) {
|
||||
self.workoutItemType = .set
|
||||
self.name = "Set"
|
||||
self.reps = 3
|
||||
workoutItems.forEach(addChild)
|
||||
set.forEach(addChild)
|
||||
}
|
||||
|
||||
func addChild(_ child: WorkoutItem) {
|
||||
if self.workoutItemType == .set {
|
||||
self.workoutItems.append(child)
|
||||
self.set.append(child)
|
||||
}
|
||||
}
|
||||
|
||||
// PAUSE
|
||||
init (rest: Int) {
|
||||
self.workoutItemType = .rest
|
||||
self.name = "Rest"
|
||||
self.duration = rest
|
||||
}
|
||||
}
|
||||
|
||||
extension WorkoutItem {
|
||||
@@ -65,6 +79,7 @@ extension WorkoutItem {
|
||||
case set
|
||||
case workout
|
||||
case exercise
|
||||
case rest
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +88,7 @@ extension WorkoutItem {
|
||||
var exercises = [WorkoutItem]()
|
||||
|
||||
for exercise in Exercise.sampleDataRecommendedRoutine {
|
||||
exercises.append(WorkoutItem(from: exercise))
|
||||
exercises.append(WorkoutItem(exercise: exercise))
|
||||
}
|
||||
|
||||
// var set = WorkoutItem(workoutItems: [
|
||||
@@ -89,7 +104,7 @@ extension WorkoutItem {
|
||||
var exercises = [WorkoutItem]()
|
||||
|
||||
for exercise in Exercise.sampleDataRings {
|
||||
exercises.append(WorkoutItem(from: exercise))
|
||||
exercises.append(WorkoutItem(exercise: exercise))
|
||||
}
|
||||
|
||||
return exercises
|
||||
|
||||
@@ -18,30 +18,62 @@ final class WorkoutSession: Nameable {
|
||||
}
|
||||
}
|
||||
|
||||
// State
|
||||
// var isPaused: Bool
|
||||
// var isCancelled: Bool
|
||||
// var isDeleted: Bool
|
||||
// var isSynced: Bool
|
||||
|
||||
// Time
|
||||
var creationDate = Date.now
|
||||
private var startDate: Date? = nil
|
||||
private var stopDate: Date? = nil
|
||||
private var duration: TimeInterval? = nil
|
||||
var startDate: Date? = nil
|
||||
var stopDate: Date? = nil
|
||||
var duration: TimeInterval? = nil
|
||||
var isCompleted: Bool = false
|
||||
|
||||
// Exercise Progress
|
||||
var currentExercise = 0
|
||||
|
||||
init () { }
|
||||
|
||||
func isActive() -> Bool {
|
||||
return startDate != nil && stopDate == nil
|
||||
}
|
||||
|
||||
func startWorkoutSession() {
|
||||
self.startDate = Date.now
|
||||
// MARK: -- Workout Controls
|
||||
func start() {
|
||||
startDate = Date.now
|
||||
}
|
||||
|
||||
func stopWorkoutSession() {
|
||||
guard let startDate else { return }
|
||||
self.stopDate = Date.now
|
||||
self.duration = self.stopDate!.timeIntervalSince(startDate)
|
||||
func pause() {
|
||||
// TODO: Implement proper Pause
|
||||
}
|
||||
|
||||
func elapsedTime(since startDate: Date) -> String {
|
||||
// Call stop() to terminate the workout.
|
||||
func stop() {
|
||||
guard let startDate = startDate else { return }
|
||||
isCompleted = true
|
||||
stopDate = Date.now
|
||||
duration = stopDate!.timeIntervalSince(startDate)
|
||||
}
|
||||
|
||||
func prevExercise() {
|
||||
guard workout != nil else { return }
|
||||
if currentExercise > 0 {
|
||||
currentExercise -= 1
|
||||
}
|
||||
}
|
||||
|
||||
func nextExercise() {
|
||||
guard let workout = workout else { return }
|
||||
if currentExercise < workout.getWorkoutItems().count - 1 {
|
||||
currentExercise += 1
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- Workout Information
|
||||
func getFormattedDuration() -> String {
|
||||
guard let startDate = startDate else { return "00:00:00" }
|
||||
let elapsedTime = Date.now.timeIntervalSince(startDate)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.allowedUnits = [.hour, .minute, .second]
|
||||
@@ -49,10 +81,26 @@ final class WorkoutSession: Nameable {
|
||||
return formatter.string(from: elapsedTime) ?? "00:00:00"
|
||||
}
|
||||
|
||||
// var isCompleted: Bool
|
||||
// var isPaused: Bool
|
||||
// var isCancelled: Bool
|
||||
// var isDeleted: Bool
|
||||
// var isSynced: Bool
|
||||
init () { }
|
||||
func getTotalExerciseCount() -> Double {
|
||||
guard let workout = workout else { return 0 }
|
||||
return Double(workout.getWorkoutItems().count)
|
||||
}
|
||||
|
||||
func getCurrentExerciseIndex() -> Double {
|
||||
return Double(currentExercise)
|
||||
}
|
||||
|
||||
func getCurrentTodo() -> String {
|
||||
return getCurrentExerciseMetric() + " " + getCurrentExerciseName()
|
||||
}
|
||||
|
||||
func getCurrentExerciseName() -> String {
|
||||
guard let workout = workout else { return "Unknown Workout" }
|
||||
return workout.getWorkoutItems()[Int(currentExercise)].name
|
||||
}
|
||||
|
||||
func getCurrentExerciseMetric() -> String {
|
||||
guard let workout = workout else { return "Unknown Workout" }
|
||||
return String(workout.getWorkoutItems()[Int(currentExercise)].reps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
- Progression-Fotos: als eigene App? -> Generalisierung zu "Foto-Track"
|
||||
|
||||
## Workouts
|
||||
- time-based: 60 s
|
||||
- rep-based: 3x -> TODO: Russian System (333 3333 33333, 444 4444 444444, usw -> https://www.youtube.com/watch?v=GZmtjlPTU1g) abbilden (ob System flexibel genug ist)
|
||||
|
||||
- TODO: Russian System (333 3333 33333, 444 4444 444444, usw -> https://www.youtube.com/watch?v=GZmtjlPTU1g) abbilden (ob System flexibel genug ist)
|
||||
- Loops
|
||||
|
||||
## Trainingspläne
|
||||
|
||||
@@ -28,8 +28,7 @@ struct WorkoutDetail: View {
|
||||
Section(
|
||||
header: Text("Exercises"),
|
||||
footer: Text("Drag and drop to re-arrange or swipe to delete exercises.")) {
|
||||
ForEach(workout.workoutItems
|
||||
.sorted(by: { $0.position < $1.position})) { workoutItem in
|
||||
ForEach(workout.getWorkoutItems()) { workoutItem in
|
||||
switch workoutItem.workoutItemType {
|
||||
case .exercise:
|
||||
ExerciseListItem(workout, workoutItem)
|
||||
@@ -37,6 +36,8 @@ struct WorkoutDetail: View {
|
||||
SetListItem(workout, workoutItem)
|
||||
case .workout:
|
||||
Text(workoutItem.name)
|
||||
case .rest:
|
||||
Text(workoutItem.name)
|
||||
}
|
||||
}
|
||||
.onDelete(perform: deleteWorkoutItem)
|
||||
@@ -75,7 +76,7 @@ struct WorkoutDetail: View {
|
||||
private func deleteWorkoutItem(offsets: IndexSet) {
|
||||
withAnimation {
|
||||
for index in offsets {
|
||||
modelContext.delete(workout.workoutItems[index])
|
||||
modelContext.delete(workout.getWorkoutItems()[index])
|
||||
}
|
||||
try? modelContext.save()
|
||||
}
|
||||
|
||||
@@ -21,17 +21,20 @@ struct WorkoutItemLibrarySheet: View {
|
||||
List {
|
||||
Section(header: Text("Utilities")) {
|
||||
AddItemButton(label: "Set") {
|
||||
addWorkoutItemtoWorkout(WorkoutItem(workoutItems: [
|
||||
WorkoutItem(from: Exercise("Set item 1")),
|
||||
WorkoutItem(from: Exercise("Set item 2"))
|
||||
addWorkoutItemtoWorkout(WorkoutItem(set: [
|
||||
WorkoutItem(exercise: Exercise("Set item 1")),
|
||||
WorkoutItem(exercise: Exercise("Set item 2"))
|
||||
]))
|
||||
}
|
||||
AddItemButton(label: "Rest") {
|
||||
addWorkoutItemtoWorkout(WorkoutItem(rest: 45))
|
||||
}
|
||||
}
|
||||
Section(header: Text("Excersises")) {
|
||||
if !exercises.isEmpty {
|
||||
ForEach(exercises) { exercise in
|
||||
AddItemButton(label: exercise.name) {
|
||||
let workoutItem = WorkoutItem(from: exercise)
|
||||
let workoutItem = WorkoutItem(exercise: exercise)
|
||||
addWorkoutItemtoWorkout(workoutItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ struct WorkoutLibrary: View {
|
||||
}
|
||||
}
|
||||
.onDelete(perform: deleteWorkout)
|
||||
if filteredItems.isEmpty {
|
||||
ContentUnavailableView.search(text: searchText)
|
||||
}
|
||||
if isAddingWorkout {
|
||||
// TODO: On tap-out of the text field, it should lose focus
|
||||
TextField("New Workout", text: $newWorkoutName, onCommit: {
|
||||
|
||||
Reference in New Issue
Block a user