mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-29 07:27:16 +00:00
fix: single showSidebar latch for launch list, drop one-shot flag (#1064)
The launch conversation-list signal was a separate `AppChromeModel` one-shot, `presentsConversationListOnLaunch`, OR'd only into the people-sheet presentation binding. The competing root sheets/covers — the fingerprint sheet and the image pickers — gate on `!showSidebar && selectedPrivatePeerID == nil` and never subtracted that launch term, so when the launch flag raised the people sheet while `showSidebar` was still false, both the people sheet and (if `showingFingerprintFor != nil`) the fingerprint sheet evaluated their `isPresented` binding to true at once — a SwiftUI single-sheet conflict. Rather than thread the launch term through every competing binding, consume the launch signal once through the existing `showSidebar` latch. `showSidebar` moves from a `ContentView` `@State` local onto `AppChromeModel` (`ContentView` already observes it as an `@EnvironmentObject`, and `$appChromeModel.showSidebar` yields the same `Binding<Bool>` the child views were already handed), so non-view launch code in `AppRuntime` can raise it directly. Every competing sheet already keys off `showSidebar`, so with the launch signal folded into that one latch there is no separate flag to reconcile and the collision cannot occur. `presentsConversationListOnLaunch` and its every reference are deleted. Behavior-preserving: `showSidebar` had a single owning `ContentView` instance, so promoting it to the shared chrome model does not change its lifetime or semantics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0cc4e9821e
commit
e3acdfb599
@ -14,10 +14,15 @@ final class AppChromeModel: ObservableObject {
|
||||
@Published var bluetoothAlertMessage = ""
|
||||
@Published var bluetoothState: CBManagerState = .unknown
|
||||
@Published var showScreenshotPrivacyWarning = false
|
||||
/// #1064: set once at launch when the last-active conversation resolves to
|
||||
/// "present the conversation list" (first-ever launch or a stale DM peer).
|
||||
/// `ContentView` folds this into the people-sheet presentation binding.
|
||||
@Published var presentsConversationListOnLaunch = false
|
||||
/// Latch for the people / conversation-list sheet. Owned here (rather than as
|
||||
/// `ContentView` local `@State`) so non-view launch code can raise it: on launch
|
||||
/// `AppRuntime` sets this to `true` when the last-active conversation resolves to
|
||||
/// "present the conversation list" (first-ever launch or a stale/unrestorable DM
|
||||
/// peer). `ContentView` binds the people sheet directly to this, and every
|
||||
/// competing sheet/cover already gates on `!showSidebar`, so a single latch keeps
|
||||
/// the launch presentation from colliding with the fingerprint / image-picker
|
||||
/// sheets (#1064).
|
||||
@Published var showSidebar = false
|
||||
|
||||
private let chatViewModel: ChatViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@ -161,7 +161,7 @@ final class AppRuntime: ObservableObject {
|
||||
// the public mesh timeline when a restore target existed but could not
|
||||
// be opened.
|
||||
if Self.shouldPresentConversationList(for: presentation, didOpenDirectChat: didOpenDirectChat) {
|
||||
appChromeModel.presentsConversationListOnLaunch = true
|
||||
appChromeModel.showSidebar = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ struct ContentPeopleSheetView: View {
|
||||
}
|
||||
}
|
||||
.navigationDestination(isPresented: Binding(
|
||||
get: { appChromeModel.showingFingerprintFor != nil && (showSidebar || privateConversationModel.selectedPeerID != nil || appChromeModel.presentsConversationListOnLaunch) },
|
||||
get: { appChromeModel.showingFingerprintFor != nil && (showSidebar || privateConversationModel.selectedPeerID != nil) },
|
||||
set: { isPresented in
|
||||
if !isPresented {
|
||||
appChromeModel.clearFingerprint()
|
||||
@ -105,7 +105,7 @@ struct ContentPeopleSheetView: View {
|
||||
#endif
|
||||
#if os(iOS)
|
||||
.fullScreenCover(isPresented: Binding(
|
||||
get: { showImagePicker && (showSidebar || privateConversationModel.selectedPeerID != nil || appChromeModel.presentsConversationListOnLaunch) },
|
||||
get: { showImagePicker && (showSidebar || privateConversationModel.selectedPeerID != nil) },
|
||||
set: { newValue in
|
||||
if !newValue {
|
||||
showImagePicker = false
|
||||
|
||||
@ -41,7 +41,8 @@ struct ContentView: View {
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.appTheme) private var appTheme
|
||||
@State private var showSidebar = false
|
||||
// `showSidebar` (the people/conversation-list sheet latch) lives on
|
||||
// `AppChromeModel` so non-view launch code can raise it; see that property.
|
||||
@State private var selectedMessageSender: String?
|
||||
@State private var selectedMessageSenderID: PeerID?
|
||||
@FocusState private var isNicknameFieldFocused: Bool
|
||||
@ -97,16 +98,15 @@ struct ContentView: View {
|
||||
#endif
|
||||
.onChange(of: selectedPrivatePeerID) { newValue in
|
||||
if newValue != nil {
|
||||
showSidebar = true
|
||||
appChromeModel.showSidebar = true
|
||||
}
|
||||
}
|
||||
.sheet(
|
||||
isPresented: Binding(
|
||||
get: { showSidebar || selectedPrivatePeerID != nil || appChromeModel.presentsConversationListOnLaunch },
|
||||
get: { appChromeModel.showSidebar || selectedPrivatePeerID != nil },
|
||||
set: { isPresented in
|
||||
if !isPresented {
|
||||
showSidebar = false
|
||||
appChromeModel.presentsConversationListOnLaunch = false
|
||||
appChromeModel.showSidebar = false
|
||||
privateConversationModel.endConversation()
|
||||
}
|
||||
}
|
||||
@ -114,7 +114,7 @@ struct ContentView: View {
|
||||
) {
|
||||
#if os(iOS)
|
||||
ContentPeopleSheetView(
|
||||
showSidebar: $showSidebar,
|
||||
showSidebar: $appChromeModel.showSidebar,
|
||||
messageText: $messageText,
|
||||
selectedMessageSender: $selectedMessageSender,
|
||||
selectedMessageSenderID: $selectedMessageSenderID,
|
||||
@ -132,7 +132,7 @@ struct ContentView: View {
|
||||
)
|
||||
#else
|
||||
ContentPeopleSheetView(
|
||||
showSidebar: $showSidebar,
|
||||
showSidebar: $appChromeModel.showSidebar,
|
||||
messageText: $messageText,
|
||||
selectedMessageSender: $selectedMessageSender,
|
||||
selectedMessageSenderID: $selectedMessageSenderID,
|
||||
@ -153,7 +153,7 @@ struct ContentView: View {
|
||||
AppInfoView()
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
get: { appChromeModel.showingFingerprintFor != nil && !showSidebar && selectedPrivatePeerID == nil },
|
||||
get: { appChromeModel.showingFingerprintFor != nil && !appChromeModel.showSidebar && selectedPrivatePeerID == nil },
|
||||
set: { _ in appChromeModel.clearFingerprint() }
|
||||
)) {
|
||||
if let peerID = appChromeModel.showingFingerprintFor {
|
||||
@ -163,7 +163,7 @@ struct ContentView: View {
|
||||
}
|
||||
#if os(iOS)
|
||||
.fullScreenCover(isPresented: Binding(
|
||||
get: { showImagePicker && !showSidebar && selectedPrivatePeerID == nil },
|
||||
get: { showImagePicker && !appChromeModel.showSidebar && selectedPrivatePeerID == nil },
|
||||
set: { newValue in
|
||||
if !newValue {
|
||||
showImagePicker = false
|
||||
@ -179,7 +179,7 @@ struct ContentView: View {
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.sheet(isPresented: Binding(
|
||||
get: { showMacImagePicker && !showSidebar && selectedPrivatePeerID == nil },
|
||||
get: { showMacImagePicker && !appChromeModel.showSidebar && selectedPrivatePeerID == nil },
|
||||
set: { newValue in
|
||||
if !newValue {
|
||||
showMacImagePicker = false
|
||||
@ -268,7 +268,7 @@ struct ContentView: View {
|
||||
|
||||
private var headerView: some View {
|
||||
ContentHeaderView(
|
||||
showSidebar: $showSidebar,
|
||||
showSidebar: $appChromeModel.showSidebar,
|
||||
showVerifySheet: $showVerifySheet,
|
||||
showLocationNotes: $showLocationNotes,
|
||||
notesGeohash: $notesGeohash,
|
||||
@ -289,7 +289,7 @@ struct ContentView: View {
|
||||
imagePreviewURL: $imagePreviewURL,
|
||||
windowCountPublic: $windowCountPublic,
|
||||
windowCountPrivate: $windowCountPrivate,
|
||||
showSidebar: $showSidebar,
|
||||
showSidebar: $appChromeModel.showSidebar,
|
||||
isTextFieldFocused: $isTextFieldFocused
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user