Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions Meshtastic/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ struct ContentView: View {
/// non-blocking state (.none, .unlocked, .lockNowAcknowledged).
private var isLockdownGateActive: Bool { lockdown.isBlockingSession }

/// Plain view-state mirror of `isLockdownGateActive`, kept in sync from the
/// coordinator via `.onChange`. Presenting the lockdown `fullScreenCover`
/// through a *computed* `Binding` — getter reading the `lockdown`
/// `@EnvironmentObject`, setter a no-op — produced a presentation binding that
/// could never converge, which iOS 17's SwiftUI resolved by re-entering the
/// attribute graph until it tripped `_assertionFailure` at first layout (a
/// launch crash unique to iOS 17; iOS 18+ tolerated it). Driving the cover
/// from real `@State` breaks that cycle. `LockdownSheet` has no dismiss
/// affordance, so this stays non-dismissable — only the coordinator leaving a
/// blocking state clears it.
@State private var isShowingLockdownGate: Bool = false

init(appState: AppState, router: Router) {
self.appState = appState
self.router = router
Expand All @@ -38,16 +50,23 @@ struct ContentView: View {
DeviceOnboarding()
}
)
.fullScreenCover(isPresented: Binding(
get: { isLockdownGateActive },
set: { _ in /* non-dismissable; coordinator state controls visibility */ }
)) {
.fullScreenCover(isPresented: $isShowingLockdownGate) {
LockdownSheet()
}
.onAppear {
if UserDefaults.firstLaunch {
isShowingDeviceOnboardingFlow = true
}
// Present the gate if the device is already in a blocking state when
// this view appears.
isShowingLockdownGate = isLockdownGateActive
}
.onChange(of: isLockdownGateActive) { _, active in
// Follow the coordinator's blocking state. The gate never closes from
// user interaction (fullScreenCover has no interactive dismiss and
// LockdownSheet exposes no dismiss control), so this is the only path
// that shows or hides it.
isShowingLockdownGate = active
}
.onChange(of: UserDefaults.showDeviceOnboarding) {_, newValue in
isShowingDeviceOnboardingFlow = newValue
Expand Down
Loading