fix(apple): keep the snackbar above the approval overlay

The earlier approval-modal fix folded SnackbarHost and the approval modal into a
single OverlayLayer child; nested that way the approval host's full-bleed clear
layer covered the toast, so snackbars stopped appearing.

Split them: rename OverlayLayer to ApprovalLayer (approval modal only) and hoist
SnackbarHost to a top-most direct child of the root ZStack, observing the live
`graph.messages` directly. The toast now renders above the overlay again.
This commit is contained in:
2026-07-31 12:46:12 +02:00
parent c670dda0a9
commit fe97c21c7a

View File

@@ -56,11 +56,13 @@ struct RootView: View {
// StateObject. Deriving them in `init` bound the view to a throwaway // StateObject. Deriving them in `init` bound the view to a throwaway
// AppGraph rebuilt on every re-init, whose coordinator never receives // AppGraph rebuilt on every re-init, whose coordinator never receives
// core events so the approval modal never appeared. // core events so the approval modal never appeared.
OverlayLayer( ApprovalLayer(
approvals: graph.approvalCoordinator, approvals: graph.approvalCoordinator,
messages: graph.messages,
sendModel: sendModel sendModel: sendModel
) )
// Top-most so the toast is never covered by the approval overlay's
// full-bleed clear layer. Observes the live `graph.messages` directly.
SnackbarHost(controller: graph.messages)
} }
.overlay { .overlay {
// A small, unobtrusive indicator while the core finishes its async // A small, unobtrusive indicator while the core finishes its async
@@ -190,13 +192,12 @@ struct RootView: View {
} }
} }
/// Hosts the snackbar and the approval modal, observing the coordinator and message /// Hosts the approval modal, observing the coordinator passed in from the persisted
/// controller passed in from the persisted `AppGraph`. Kept as a child view so the /// `AppGraph`. Kept as a child view so the `@ObservedObject` subscription is
/// `@ObservedObject` subscriptions are established here (in `body`) against the live /// established here (in `body`) against the live instance, rather than in
/// instances, rather than in `RootView.init` against a throwaway graph. /// `RootView.init` against a throwaway graph.
private struct OverlayLayer: View { private struct ApprovalLayer: View {
@ObservedObject var approvals: ApprovalCoordinator @ObservedObject var approvals: ApprovalCoordinator
@ObservedObject var messages: UiMessageController
let sendModel: SendModel let sendModel: SendModel
/// Drives the approval sheet; toggled from the pending-approval `onChange` so the /// Drives the approval sheet; toggled from the pending-approval `onChange` so the
@@ -209,15 +210,12 @@ private struct OverlayLayer: View {
@State private var approvalAwaitingSheetDismiss = false @State private var approvalAwaitingSheetDismiss = false
var body: some View { var body: some View {
ZStack {
SnackbarHost(controller: messages)
ApprovalModalHost( ApprovalModalHost(
isPresented: $showApproval, isPresented: $showApproval,
state: approvals.state, state: approvals.state,
onAccept: approvals.accept, onAccept: approvals.accept,
onRefuse: approvals.refuse onRefuse: approvals.refuse
) )
}
// A pending approval is a blocking modal. Close any open share/QR sheet first // A pending approval is a blocking modal. Close any open share/QR sheet first
// (the detail-view panel *or* the list-level share sheet), then present the // (the detail-view panel *or* the list-level share sheet), then present the
// approval sheet: the approval is presented from the app root and neither // approval sheet: the approval is presented from the app root and neither