Prevent Bluetooth alerts from interrupting modal warnings

This commit is contained in:
jack
2026-07-26 13:56:11 +02:00
committed by jack
parent 3b4f3e384d
commit be8dd52bda
3 changed files with 94 additions and 4 deletions
+4
View File
@@ -9,11 +9,13 @@ import AppKit
struct ContentPeopleSheetModalPresentationState { struct ContentPeopleSheetModalPresentationState {
var isImagePreviewPresented = false var isImagePreviewPresented = false
var isVerificationSheetPresented = false var isVerificationSheetPresented = false
var legacyPrivateMediaConsentRequest: LegacyPrivateMediaConsentRequest? = nil
var isMediaPickerPresented = false var isMediaPickerPresented = false
var hasPresentation: Bool { var hasPresentation: Bool {
isImagePreviewPresented isImagePreviewPresented
|| isVerificationSheetPresented || isVerificationSheetPresented
|| legacyPrivateMediaConsentRequest != nil
|| isMediaPickerPresented || isMediaPickerPresented
} }
} }
@@ -59,6 +61,8 @@ struct ContentPeopleSheetView: View {
return ContentPeopleSheetModalPresentationState( return ContentPeopleSheetModalPresentationState(
isImagePreviewPresented: imagePreviewURL != nil, isImagePreviewPresented: imagePreviewURL != nil,
isVerificationSheetPresented: showVerifySheet, isVerificationSheetPresented: showVerifySheet,
legacyPrivateMediaConsentRequest:
conversationUIModel.legacyPrivateMediaConsentRequest,
isMediaPickerPresented: isMediaPickerPresented isMediaPickerPresented: isMediaPickerPresented
).hasPresentation ).hasPresentation
} }
+32 -4
View File
@@ -23,6 +23,7 @@ struct ContentRootModalPresentationState {
var isImagePreviewPresented = false var isImagePreviewPresented = false
var isVerificationSheetPresented = false var isVerificationSheetPresented = false
var isVoiceAlertPresented = false var isVoiceAlertPresented = false
var isScreenshotPrivacyAlertPresented = false
var isMediaPickerPresented = false var isMediaPickerPresented = false
var hasPresentation: Bool { var hasPresentation: Bool {
@@ -34,10 +35,40 @@ struct ContentRootModalPresentationState {
|| isImagePreviewPresented || isImagePreviewPresented
|| isVerificationSheetPresented || isVerificationSheetPresented
|| isVoiceAlertPresented || isVoiceAlertPresented
|| isScreenshotPrivacyAlertPresented
|| isMediaPickerPresented || isMediaPickerPresented
} }
} }
extension ContentRootModalPresentationState {
@MainActor
init(
appChromeModel: AppChromeModel,
isPeopleSheetPresented: Bool = false,
isImagePreviewPresented: Bool = false,
isVerificationSheetPresented: Bool = false,
isVoiceAlertPresented: Bool = false,
isMediaPickerPresented: Bool = false
) {
self.init(
isPeopleSheetPresented: isPeopleSheetPresented,
isAppInfoPresented: appChromeModel.isAppInfoPresented,
isFingerprintPresented:
appChromeModel.showingFingerprintFor != nil,
isLocationChannelsSheetPresented:
appChromeModel.isLocationChannelsSheetPresented,
isNoticesSheetPresented:
appChromeModel.isNoticesSheetPresented,
isImagePreviewPresented: isImagePreviewPresented,
isVerificationSheetPresented: isVerificationSheetPresented,
isVoiceAlertPresented: isVoiceAlertPresented,
isScreenshotPrivacyAlertPresented:
appChromeModel.showScreenshotPrivacyWarning,
isMediaPickerPresented: isMediaPickerPresented
)
}
}
/// On macOS 14+, disables the default system focus ring on TextFields. /// On macOS 14+, disables the default system focus ring on TextFields.
/// On earlier macOS versions and on iOS this is a no-op. /// On earlier macOS versions and on iOS this is a no-op.
struct FocusEffectDisabledModifier: ViewModifier { struct FocusEffectDisabledModifier: ViewModifier {
@@ -117,11 +148,8 @@ struct ContentView: View {
#endif #endif
return ContentRootModalPresentationState( return ContentRootModalPresentationState(
appChromeModel: appChromeModel,
isPeopleSheetPresented: isPeopleSheetPresented, isPeopleSheetPresented: isPeopleSheetPresented,
isAppInfoPresented: appChromeModel.isAppInfoPresented,
isFingerprintPresented: appChromeModel.showingFingerprintFor != nil,
isLocationChannelsSheetPresented: appChromeModel.isLocationChannelsSheetPresented,
isNoticesSheetPresented: appChromeModel.isNoticesSheetPresented,
isImagePreviewPresented: imagePreviewURL != nil, isImagePreviewPresented: imagePreviewURL != nil,
isVerificationSheetPresented: showVerifySheet, isVerificationSheetPresented: showVerifySheet,
isVoiceAlertPresented: voiceRecordingVM.showAlert, isVoiceAlertPresented: voiceRecordingVM.showAlert,
+58
View File
@@ -579,6 +579,64 @@ struct ViewSmokeTests {
) )
} }
@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 @Test
func geohashAndTextMessageViews_renderCoreBranches() { func geohashAndTextMessageViews_renderCoreBranches() {
let (viewModel, _, _) = makeSmokeViewModel() let (viewModel, _, _) = makeSmokeViewModel()