Remove the buried Start This Split button from the exercise editor
ExerciseListView is reached only via Settings > Splits > split > Edit >
Exercises, so its toolbar start button was effectively undiscoverable and
duplicated the home-screen split picker (the sole remaining start path).
Strip the button and its now-dead start flow: the active-workout prompt,
end-and-restart handling, started-workout navigation, and the AppServices
dependency. Also drop the changelog entry for the watch-launch wiring of
that button (f01e149) since the button no longer exists and never shipped.
Claude-Session: https://claude.ai/code/session_01PVNBVKp5bcq52X722uMjwT
This commit is contained in:
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
A new Cardio exercise and matching starter split let you log aerobic sessions, tracked on your Apple Watch as a cardio workout.
|
A new Cardio exercise and matching starter split let you log aerobic sessions, tracked on your Apple Watch as a cardio workout.
|
||||||
|
|
||||||
Starting a workout with a split's Start This Split button now brings it up on your Apple Watch too, matching how starting one from the main list already worked.
|
|
||||||
|
|
||||||
A new Auto-Advance option lets a split flow hands-free from one exercise to the next, resting automatically between them.
|
A new Auto-Advance option lets a split flow hands-free from one exercise to the next, resting automatically between them.
|
||||||
|
|
||||||
Each split can now set its own rest time, overriding the app-wide default in Settings.
|
Each split can now set its own rest time, overriding the app-wide default in Settings.
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import SwiftData
|
|||||||
|
|
||||||
struct ExerciseListView: View {
|
struct ExerciseListView: View {
|
||||||
@Environment(SyncEngine.self) private var sync
|
@Environment(SyncEngine.self) private var sync
|
||||||
@Environment(AppServices.self) private var services
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
// Resolve the split by id, not a captured entity: editing a seed's exercise from
|
// Resolve the split by id, not a captured entity: editing a seed's exercise from
|
||||||
@@ -25,15 +24,6 @@ struct ExerciseListView: View {
|
|||||||
@State private var showingAddSheet: Bool = false
|
@State private var showingAddSheet: Bool = false
|
||||||
@State private var itemToEdit: Exercise? = nil
|
@State private var itemToEdit: Exercise? = nil
|
||||||
@State private var itemToDelete: Exercise? = nil
|
@State private var itemToDelete: Exercise? = nil
|
||||||
/// ID of the just-created workout; drives programmatic navigation once the
|
|
||||||
/// cache observer delivers the entity a beat after the file write (see
|
|
||||||
/// `navigatesToStartedWorkout`).
|
|
||||||
@State private var pendingWorkoutID: String? = nil
|
|
||||||
|
|
||||||
@Query(sort: \Workout.start, order: .reverse)
|
|
||||||
private var workouts: [Workout]
|
|
||||||
|
|
||||||
@State private var showingActivePrompt = false
|
|
||||||
@AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb
|
@AppStorage("weightUnit") private var weightUnit: WeightUnit = .lb
|
||||||
|
|
||||||
init(split: Split) {
|
init(split: Split) {
|
||||||
@@ -45,10 +35,6 @@ struct ExerciseListView: View {
|
|||||||
return splits.first { $0.id == id }
|
return splits.first { $0.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private var activeWorkouts: [Workout] {
|
|
||||||
workouts.filter { $0.status == .inProgress || $0.status == .notStarted }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let split {
|
if let split {
|
||||||
@@ -58,8 +44,6 @@ struct ExerciseListView: View {
|
|||||||
.task { dismiss() }
|
.task { dismiss() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Navigate into the workout's log screen once the entity appears in the cache.
|
|
||||||
.navigatesToStartedWorkout(pendingWorkoutID: $pendingWorkoutID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -103,14 +87,6 @@ struct ExerciseListView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(split.name)
|
.navigationTitle(split.name)
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
|
||||||
Button("Start This Split") {
|
|
||||||
confirmAndStart()
|
|
||||||
}
|
|
||||||
.disabled(split.exercisesArray.isEmpty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $showingAddSheet) {
|
.sheet(isPresented: $showingAddSheet) {
|
||||||
ExercisePickerView(onExerciseSelected: { exerciseNames in
|
ExercisePickerView(onExerciseSelected: { exerciseNames in
|
||||||
addExercises(names: exerciseNames)
|
addExercises(names: exerciseNames)
|
||||||
@@ -138,53 +114,10 @@ struct ExerciseListView: View {
|
|||||||
} message: { item in
|
} message: { item in
|
||||||
Text("Remove \"\(item.name)\" from this split?")
|
Text("Remove \"\(item.name)\" from this split?")
|
||||||
}
|
}
|
||||||
.confirmationDialog(
|
|
||||||
activePromptTitle,
|
|
||||||
isPresented: $showingActivePrompt,
|
|
||||||
titleVisibility: .visible
|
|
||||||
) {
|
|
||||||
Button("End Current & Start New") { endActiveThenStart() }
|
|
||||||
Button("Start in Parallel") { start() }
|
|
||||||
Button("Cancel", role: .cancel) { showingActivePrompt = false }
|
|
||||||
} message: {
|
|
||||||
Text(activePromptMessage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private var activePromptTitle: String {
|
|
||||||
activeWorkouts.count == 1 ? "Workout in Progress" : "\(activeWorkouts.count) Workouts in Progress"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var activePromptMessage: String {
|
|
||||||
let n = activeWorkouts.count
|
|
||||||
let those = n == 1 ? "it" : "them"
|
|
||||||
return "You already have \(n == 1 ? "a workout" : "\(n) workouts") going. End \(those) first, or run this one alongside."
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prompt before starting if other workouts are still going; otherwise start straight away.
|
|
||||||
private func confirmAndStart() {
|
|
||||||
if activeWorkouts.isEmpty {
|
|
||||||
start()
|
|
||||||
} else {
|
|
||||||
showingActivePrompt = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End every in-flight workout (keeping its progress), then start this split.
|
|
||||||
private func endActiveThenStart() {
|
|
||||||
let toEnd = activeWorkouts.map { WorkoutDocument(from: $0) }
|
|
||||||
showingActivePrompt = false
|
|
||||||
Task {
|
|
||||||
for var doc in toEnd {
|
|
||||||
doc.endKeepingProgress()
|
|
||||||
await sync.save(workout: doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func moveExercises(from source: IndexSet, to destination: Int) {
|
private func moveExercises(from source: IndexSet, to destination: Int) {
|
||||||
guard let split else { return }
|
guard let split else { return }
|
||||||
var exercises = split.exercisesArray
|
var exercises = split.exercisesArray
|
||||||
@@ -199,34 +132,6 @@ struct ExerciseListView: View {
|
|||||||
Task { await sync.save(split: doc) }
|
Task { await sync.save(split: doc) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func start() {
|
|
||||||
guard let split else { return }
|
|
||||||
let startDate = Date()
|
|
||||||
let logs = split.exercisesArray.enumerated().map { i, ex in
|
|
||||||
WorkoutLogDocument(planFrom: ExerciseDocument(from: ex), order: i, date: startDate)
|
|
||||||
}
|
|
||||||
let doc = WorkoutDocument(
|
|
||||||
schemaVersion: WorkoutDocument.currentSchemaVersion,
|
|
||||||
id: ULID.make(),
|
|
||||||
splitID: split.id,
|
|
||||||
splitName: split.name,
|
|
||||||
start: startDate,
|
|
||||||
end: nil,
|
|
||||||
status: WorkoutStatus.notStarted.rawValue,
|
|
||||||
createdAt: startDate,
|
|
||||||
updatedAt: startDate,
|
|
||||||
logs: logs,
|
|
||||||
restSeconds: split.restSeconds, autoAdvance: split.autoAdvance
|
|
||||||
)
|
|
||||||
Task {
|
|
||||||
await sync.save(workout: doc)
|
|
||||||
pendingWorkoutID = doc.id
|
|
||||||
}
|
|
||||||
// Bring the Apple Watch up into the session so the user can run it from the wrist,
|
|
||||||
// tagged with the split's activity type — mirroring the split-picker start path.
|
|
||||||
services.workoutLauncher.launchWatchWorkout(activityType: split.activityTypeEnum.hkActivityType)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addExercises(names: [String]) {
|
private func addExercises(names: [String]) {
|
||||||
guard let split else { return }
|
guard let split else { return }
|
||||||
var doc = SplitDocument(from: split)
|
var doc = SplitDocument(from: split)
|
||||||
|
|||||||
Reference in New Issue
Block a user