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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user