Replace the overflow menu with a + button and an End Workout row

The toolbar overflow affordance didn't feel native to iOS 26. Restore a direct + button (primaryAction placement) that opens the exercise picker, and move End Workout to a dedicated action row at the bottom of the exercise list. Removes the intermediate actions sheet and its menu/relay plumbing.

Claude-Session: https://claude.ai/code/session_01SCv7zvGFcKy47KSTnTLxRe
This commit is contained in:
2026-06-22 22:49:41 -04:00
parent 59490d3195
commit 721158895e
@@ -27,8 +27,6 @@ struct WorkoutLogListView: View {
@State private var showingAddSheet = false
@State private var showingEndOptions = false
@State private var showingActionMenu = false
@State private var pendingMenuAction: MenuAction?
@State private var logToDelete: WorkoutLogDocument?
@State private var addedLog: LogRoute?
@State private var logToEdit: LogRoute?
@@ -40,11 +38,6 @@ struct WorkoutLogListView: View {
/// double-fire the way a value-based `navigationDestination(for:)` would.
private struct LogRoute: Identifiable, Hashable { let id: String }
/// The overflow actions surfaced by the toolbar's "" button. The chosen action is
/// stashed here and run from the sheet's `onDismiss`, so we never try to present a
/// second sheet/dialog while the menu sheet is still on screen.
private enum MenuAction { case addExercise, endWorkout }
init(workout: Workout) {
self.workout = workout
_doc = State(initialValue: WorkoutDocument(from: workout))
@@ -116,6 +109,15 @@ struct WorkoutLogListView: View {
}
.onMove(perform: moveLog)
}
Section {
Button {
showingEndOptions = true
} label: {
Text("End Workout")
.frame(maxWidth: .infinity)
}
}
}
}
}
@@ -137,28 +139,15 @@ struct WorkoutLogListView: View {
doc = WorkoutDocument(from: workout)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarItem(placement: .primaryAction) {
Button {
showingActionMenu = true
showingAddSheet = true
} label: {
Image(systemName: "ellipsis.circle")
Image(systemName: "plus")
}
.accessibilityLabel("Workout Options")
.accessibilityLabel("Add Exercise")
}
}
.sheet(isPresented: $showingActionMenu, onDismiss: runPendingMenuAction) {
WorkoutActionsSheet(
canEndWorkout: !sortedLogs.isEmpty,
onAddExercise: {
pendingMenuAction = .addExercise
showingActionMenu = false
},
onEndWorkout: {
pendingMenuAction = .endWorkout
showingActionMenu = false
}
)
}
.sheet(isPresented: $showingAddSheet) {
SplitExercisePickerSheet(
split: split,
@@ -197,18 +186,6 @@ struct WorkoutLogListView: View {
}
}
/// Run the action chosen in the overflow sheet, once that sheet has finished
/// dismissing presenting these follow-on sheets/dialogs while the menu is still
/// up would collide, since SwiftUI supports only one sheet at a time.
private func runPendingMenuAction() {
switch pendingMenuAction {
case .addExercise: showingAddSheet = true
case .endWorkout: showingEndOptions = true
case .none: break
}
pendingMenuAction = nil
}
/// The paged run flow, fully wired into the live channel: it broadcasts this device's
/// human transitions to the watch, follows the watch's, and marks this run as open inline
/// (so the propped-phone mirror cover doesn't stack on top of it).
@@ -364,34 +341,6 @@ struct WorkoutLogListView: View {
}
}
// MARK: - Workout Actions Sheet
/// Compact, detented bottom sheet that replaces the toolbar's overflow `Menu`. Under
/// iOS 26 a toolbar `Menu` anchors to its button at the top of the screen; presenting
/// the same actions as a small sheet keeps them at the bottom within thumb reach, and
/// lets us pin the height via `presentationDetents`.
private struct WorkoutActionsSheet: View {
let canEndWorkout: Bool
let onAddExercise: () -> Void
let onEndWorkout: () -> Void
var body: some View {
List {
Button(action: onAddExercise) {
Label("Add Exercise", systemImage: "plus")
}
if canEndWorkout {
Button(action: onEndWorkout) {
Label("End Workout", systemImage: "flag.checkered")
}
}
}
// Sized to fit just the row(s); bump these if the row metrics change.
.presentationDetents([.height(canEndWorkout ? 200 : 140)])
.presentationDragIndicator(.visible)
}
}
// MARK: - Split Exercise Picker Sheet
struct SplitExercisePickerSheet: View {