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 showingAddSheet = false
|
||||||
@State private var showingEndOptions = false
|
@State private var showingEndOptions = false
|
||||||
@State private var showingActionMenu = false
|
|
||||||
@State private var pendingMenuAction: MenuAction?
|
|
||||||
@State private var logToDelete: WorkoutLogDocument?
|
@State private var logToDelete: WorkoutLogDocument?
|
||||||
@State private var addedLog: LogRoute?
|
@State private var addedLog: LogRoute?
|
||||||
@State private var logToEdit: LogRoute?
|
@State private var logToEdit: LogRoute?
|
||||||
@@ -40,11 +38,6 @@ struct WorkoutLogListView: View {
|
|||||||
/// double-fire the way a value-based `navigationDestination(for:)` would.
|
/// double-fire the way a value-based `navigationDestination(for:)` would.
|
||||||
private struct LogRoute: Identifiable, Hashable { let id: String }
|
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) {
|
init(workout: Workout) {
|
||||||
self.workout = workout
|
self.workout = workout
|
||||||
_doc = State(initialValue: WorkoutDocument(from: workout))
|
_doc = State(initialValue: WorkoutDocument(from: workout))
|
||||||
@@ -116,6 +109,15 @@ struct WorkoutLogListView: View {
|
|||||||
}
|
}
|
||||||
.onMove(perform: moveLog)
|
.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)
|
doc = WorkoutDocument(from: workout)
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button {
|
Button {
|
||||||
showingActionMenu = true
|
showingAddSheet = true
|
||||||
} label: {
|
} 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) {
|
.sheet(isPresented: $showingAddSheet) {
|
||||||
SplitExercisePickerSheet(
|
SplitExercisePickerSheet(
|
||||||
split: split,
|
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
|
/// 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
|
/// 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).
|
/// (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
|
// MARK: - Split Exercise Picker Sheet
|
||||||
|
|
||||||
struct SplitExercisePickerSheet: View {
|
struct SplitExercisePickerSheet: View {
|
||||||
|
|||||||
Reference in New Issue
Block a user