Keep open DMs alive across Bluetooth settings (#1460)

Presenting the root Bluetooth alert while the people/DM sheet was up forced SwiftUI to dismiss the sheet, whose binding setter called endConversation() — destroying the open DM. The root alert is now gated behind a modal-presentation guard (scene active, no competing modal), a second copy presents from inside the sheet, and the sheet-dismissal setter only ends the conversation when the dismissal is genuinely explicit.

Includes review fix: the voice-recording error alert (mic permission denied inside an open DM) destroyed the DM through the exact same path — it now participates in the same guard and gating pattern at both root and sheet level.
This commit is contained in:
jack
2026-07-26 14:50:03 +02:00
committed by GitHub
parent c72bb4ca2e
commit 660632ef6b
3 changed files with 351 additions and 7 deletions
+97
View File
@@ -554,6 +554,103 @@ struct ViewSmokeTests {
#expect(featureModels.privateConversationModel.selectedHeaderState?.headerPeerID == peerID)
}
@Test("Root Bluetooth alert waits for location and notices sheets")
func rootBluetoothAlertGuard_includesHeaderSheets() {
#expect(!ContentRootModalPresentationState().hasPresentation)
#expect(
ContentRootModalPresentationState(
isLocationChannelsSheetPresented: true
).hasPresentation
)
#expect(
ContentRootModalPresentationState(
isNoticesSheetPresented: true
).hasPresentation
)
}
@Test("People-sheet Bluetooth alert waits for local verification sheet")
func peopleSheetBluetoothAlertGuard_includesVerificationSheet() {
#expect(!ContentPeopleSheetModalPresentationState().hasPresentation)
#expect(
ContentPeopleSheetModalPresentationState(
isVerificationSheetPresented: true
).hasPresentation
)
}
@Test("Bluetooth alerts wait for the voice recording error alert")
func bluetoothAlertGuards_includeVoiceAlert() {
#expect(
ContentRootModalPresentationState(
isVoiceAlertPresented: true
).hasPresentation
)
#expect(
ContentPeopleSheetModalPresentationState(
isVoiceAlertPresented: true
).hasPresentation
)
}
@Test("Root Bluetooth alert waits for screenshot privacy alert")
@MainActor
func rootBluetoothAlertGuard_tracksScreenshotPrivacyState() {
let (viewModel, _, _) = makeSmokeViewModel()
let featureModels = makeSmokeFeatureModels(for: viewModel)
#expect(
!ContentRootModalPresentationState(
appChromeModel: featureModels.appChromeModel
).hasPresentation
)
featureModels.appChromeModel.showScreenshotPrivacyWarning = true
#expect(
ContentRootModalPresentationState(
appChromeModel: featureModels.appChromeModel
).hasPresentation
)
}
@Test("People-sheet Bluetooth alert waits for legacy media consent")
@MainActor
func peopleSheetBluetoothAlertGuard_tracksLegacyConsentState() async {
let (viewModel, _, _) = makeSmokeViewModel()
let featureModels = makeSmokeFeatureModels(for: viewModel)
#expect(
!ContentPeopleSheetModalPresentationState(
legacyPrivateMediaConsentRequest:
featureModels.conversationUIModel
.legacyPrivateMediaConsentRequest
).hasPresentation
)
viewModel.enqueueLegacyPrivateMediaConsent(
for: PeerID(str: "5152535455565758"),
transferId: "legacy-consent-transfer",
messageID: "legacy-consent-message"
) { _ in }
defer { viewModel.cancelAllLegacyPrivateMediaConsents() }
let consentPropagated = await TestHelpers.waitUntil {
featureModels.conversationUIModel
.legacyPrivateMediaConsentRequest != nil
}
#expect(
consentPropagated
)
#expect(
ContentPeopleSheetModalPresentationState(
legacyPrivateMediaConsentRequest:
featureModels.conversationUIModel
.legacyPrivateMediaConsentRequest
).hasPresentation
)
}
@Test
func geohashAndTextMessageViews_renderCoreBranches() {
let (viewModel, _, _) = makeSmokeViewModel()