wip
This commit is contained in:
@ -12,17 +12,31 @@ import SwiftUI
|
||||
struct ExerciseDoneCard: View {
|
||||
let elapsedSeconds: Int
|
||||
let onComplete: () -> Void
|
||||
let onOneMoreSet: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
VStack(spacing: 12) {
|
||||
Text("Exercise Complete!")
|
||||
.font(.headline)
|
||||
.foregroundColor(.green)
|
||||
|
||||
Button(action: onComplete) {
|
||||
Text("Done in \(10 - elapsedSeconds)s")
|
||||
.font(.headline)
|
||||
.font(.subheadline)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.green)
|
||||
.padding(.horizontal)
|
||||
|
||||
Button(action: onOneMoreSet) {
|
||||
Text("One More Set")
|
||||
.font(.subheadline)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.tint(.blue)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
@ -11,12 +11,13 @@ import SwiftUI
|
||||
|
||||
struct ExerciseRestCard: View {
|
||||
let elapsedSeconds: Int
|
||||
let afterSet: Int
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
Text("Resting for")
|
||||
.font(.title)
|
||||
.lineLimit(1)
|
||||
Text("Resting after set #\(afterSet)")
|
||||
.font(.title3)
|
||||
.lineLimit(2)
|
||||
.minimumScaleFactor(0.5)
|
||||
.layoutPriority(1)
|
||||
|
||||
|
@ -19,6 +19,7 @@ struct ExerciseProgressControlView: View {
|
||||
@State private var elapsedSeconds: Int = 0
|
||||
@State private var timer: Timer? = nil
|
||||
@State private var previousStateIndex: Int = 0
|
||||
@State private var hapticCounter: Int = 0
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $currentStateIndex) {
|
||||
@ -32,21 +33,40 @@ struct ExerciseProgressControlView: View {
|
||||
.tag(index)
|
||||
|
||||
} else if state.isRest {
|
||||
ExerciseRestCard(elapsedSeconds: elapsedSeconds)
|
||||
ExerciseRestCard(elapsedSeconds: elapsedSeconds, afterSet: state.afterSet ?? 0)
|
||||
.tag(index)
|
||||
|
||||
} else if state.isDone {
|
||||
ExerciseDoneCard(elapsedSeconds: elapsedSeconds, onComplete: completeExercise)
|
||||
ExerciseDoneCard(elapsedSeconds: elapsedSeconds, onComplete: completeExercise, onOneMoreSet: addOneMoreSet)
|
||||
.tag(index)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onEnded { value in
|
||||
// Detect swipe left when on the Done card (last index)
|
||||
if currentStateIndex == exerciseStates.count - 1 && value.translation.width < -50 {
|
||||
// User swiped left from Done card - add one more set
|
||||
addOneMoreSet()
|
||||
}
|
||||
}
|
||||
)
|
||||
.onChange(of: currentStateIndex) { oldValue, newValue in
|
||||
if oldValue != newValue {
|
||||
elapsedSeconds = 0
|
||||
moveToNextState()
|
||||
hapticCounter = 0 // Reset haptic pattern when changing phases
|
||||
// Update the log's current state but don't auto-advance
|
||||
log.currentStateIndex = currentStateIndex
|
||||
log.elapsedSeconds = elapsedSeconds
|
||||
try? modelContext.save()
|
||||
|
||||
// Update status based on current state
|
||||
if currentStateIndex > 0 && currentStateIndex < exerciseStates.count - 1 {
|
||||
log.status = .inProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@ -54,6 +74,12 @@ struct ExerciseProgressControlView: View {
|
||||
currentStateIndex = log.currentStateIndex ?? 0
|
||||
startTimer()
|
||||
}
|
||||
.onChange(of: log.sets) { oldValue, newValue in
|
||||
// Reconstruct exercise states if sets count changed
|
||||
if oldValue != newValue {
|
||||
setupExerciseStates()
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
@ -76,11 +102,11 @@ struct ExerciseProgressControlView: View {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
|
||||
elapsedSeconds += 1
|
||||
|
||||
// Check if we need to provide haptic feedback during rest periods
|
||||
// Check if we need to provide haptic feedback
|
||||
if currentStateIndex >= 0 && currentStateIndex < exerciseStates.count {
|
||||
let currentState = exerciseStates[currentStateIndex]
|
||||
if currentState.isRest {
|
||||
provideRestHapticFeedback()
|
||||
if currentState.isRest || currentState.isSet {
|
||||
provideHapticFeedback()
|
||||
} else if currentState.isDone && elapsedSeconds >= 10 {
|
||||
// Auto-complete after 10 seconds on the DONE state
|
||||
completeExercise()
|
||||
@ -94,34 +120,53 @@ struct ExerciseProgressControlView: View {
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func moveToNextState() {
|
||||
if currentStateIndex < exerciseStates.count - 1 {
|
||||
elapsedSeconds = 0
|
||||
withAnimation {
|
||||
currentStateIndex += 1
|
||||
log.currentStateIndex = currentStateIndex
|
||||
log.elapsedSeconds = elapsedSeconds
|
||||
log.status = .inProgress
|
||||
try? modelContext.save()
|
||||
|
||||
private func provideHapticFeedback() {
|
||||
// Provide haptic feedback every 15 seconds in a cycling pattern: 1 → 2 → 3 → long
|
||||
if elapsedSeconds % 15 == 0 && elapsedSeconds > 0 {
|
||||
hapticCounter += 1
|
||||
|
||||
switch hapticCounter % 4 {
|
||||
case 1:
|
||||
// First 15 seconds: single tap
|
||||
HapticFeedback.click()
|
||||
case 2:
|
||||
// Second 15 seconds: double tap
|
||||
HapticFeedback.doubleTap()
|
||||
case 3:
|
||||
// Third 15 seconds: triple tap
|
||||
HapticFeedback.tripleTap()
|
||||
case 0:
|
||||
// Fourth 15 seconds: long tap, then reset pattern
|
||||
HapticFeedback.longTap()
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// We've reached the end (DONE state)
|
||||
completeExercise()
|
||||
}
|
||||
}
|
||||
|
||||
private func provideRestHapticFeedback() {
|
||||
// Provide haptic feedback based on elapsed time
|
||||
if elapsedSeconds % 60 == 0 && elapsedSeconds > 0 {
|
||||
// Triple tap every 60 seconds
|
||||
HapticFeedback.tripleTap()
|
||||
} else if elapsedSeconds % 30 == 0 && elapsedSeconds > 0 {
|
||||
// Double tap every 30 seconds
|
||||
HapticFeedback.doubleTap()
|
||||
} else if elapsedSeconds % 10 == 0 && elapsedSeconds > 0 {
|
||||
// Single tap every 10 seconds
|
||||
HapticFeedback.success()
|
||||
}
|
||||
private func addOneMoreSet() {
|
||||
// Increment total sets
|
||||
log.sets += 1
|
||||
|
||||
// Reconstruct exercise states (will trigger onChange)
|
||||
setupExerciseStates()
|
||||
|
||||
// Calculate the state index for the additional set
|
||||
// States: intro(0) → set1(1) → rest1(2) → ... → setN(2N-1) → done(2N)
|
||||
// For the additional set, we want to go to setN which is at index 2N-1
|
||||
let additionalSetStateIndex = (log.sets * 2) - 1
|
||||
|
||||
log.status = .inProgress
|
||||
log.currentStateIndex = additionalSetStateIndex
|
||||
log.elapsedSeconds = 0
|
||||
elapsedSeconds = 0
|
||||
hapticCounter = 0
|
||||
|
||||
// Update the current state index for the TabView
|
||||
currentStateIndex = additionalSetStateIndex
|
||||
|
||||
try? modelContext.save()
|
||||
}
|
||||
|
||||
private func completeExercise() {
|
||||
|
Reference in New Issue
Block a user