diff --git a/bitchat/App/AppChromeModel.swift b/bitchat/App/AppChromeModel.swift index 70a6c28c..f07d9fc6 100644 --- a/bitchat/App/AppChromeModel.swift +++ b/bitchat/App/AppChromeModel.swift @@ -18,6 +18,8 @@ final class AppChromeModel: ObservableObject { @Published var bluetoothAlertMessage = "" @Published var bluetoothState: CBManagerState = .unknown @Published var showScreenshotPrivacyWarning = false + /// Confirmation gate for the triple-tap logo panic shortcut (#152). + @Published var showPanicConfirmation = false private let chatViewModel: ChatViewModel private let onPanicWipe: () -> Void @@ -111,6 +113,20 @@ final class AppChromeModel: ObservableObject { prepareForPanic = preparation } + /// Triple-tap (and any other undiscoverable shortcut) must confirm first. + func requestPanicWipe() { + showPanicConfirmation = true + } + + func confirmPanicWipe() { + showPanicConfirmation = false + panicClearAllData() + } + + func cancelPanicWipe() { + showPanicConfirmation = false + } + func panicClearAllData() { prepareForPanic?() onPanicWipe() diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index cb99fbac..204689ba 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -120,7 +120,7 @@ struct AppInfoView: View { static let dangerTitle = String(localized: "app_info.settings.danger.title", defaultValue: "DANGER ZONE", comment: "Section header (uppercase) for destructive actions in settings") static let panicButton = String(localized: "app_info.settings.danger.panic_button", defaultValue: "panic wipe", comment: "Button in the settings danger zone that erases all local data after confirmation") - static let panicNote = String(localized: "app_info.settings.danger.panic_note", defaultValue: "erases all messages, keys, and identity. triple-tapping the bitchat/ logo does the same, instantly.", comment: "Caption under the panic wipe button explaining what it does and the triple-tap shortcut") + static let panicNote = String(localized: "app_info.settings.danger.panic_note", defaultValue: "erases all messages, keys, and identity. triple-tapping the bitchat/ logo asks for the same confirmation.", comment: "Caption under the panic wipe button explaining what it does and the triple-tap shortcut") static let panicConfirmTitle = String(localized: "app_info.settings.danger.panic_confirm_title", defaultValue: "wipe all data?", comment: "Title of the confirmation dialog before a panic wipe") static let panicConfirmAction = String(localized: "app_info.settings.danger.panic_confirm_action", defaultValue: "wipe everything", comment: "Destructive confirmation button that performs the panic wipe") } diff --git a/bitchat/Views/ContentHeaderView.swift b/bitchat/Views/ContentHeaderView.swift index bd3af7f2..22504219 100644 --- a/bitchat/Views/ContentHeaderView.swift +++ b/bitchat/Views/ContentHeaderView.swift @@ -51,14 +51,15 @@ struct ContentHeaderView: View { // cluster at priority 3 never gives up width. .layoutPriority(2) .onTapGesture(count: 3) { - appChromeModel.panicClearAllData() + appChromeModel.requestPanicWipe() } .onTapGesture(count: 1) { appChromeModel.presentAppInfo() } // This is the only entry point to App Info, but it reads as // static text; surface the tap. (The triple-tap panic wipe - // stays undiscoverable on purpose — it's destructive.) + // stays undiscoverable on purpose — it's destructive — but + // still requires confirmation before erasing anything.) .accessibilityAddTraits(.isButton) .accessibilityHint( String(localized: "content.accessibility.app_info_hint", comment: "Accessibility hint on the bitchat/ logo explaining a tap opens app info") @@ -66,6 +67,21 @@ struct ContentHeaderView: View { .accessibilityAction { appChromeModel.presentAppInfo() } + .confirmationDialog( + String(localized: "app_info.settings.danger.panic_confirm_title", defaultValue: "wipe all data?", comment: "Title of the confirmation dialog before a panic wipe"), + isPresented: $appChromeModel.showPanicConfirmation, + titleVisibility: .visible + ) { + Button( + String(localized: "app_info.settings.danger.panic_confirm_action", defaultValue: "wipe everything", comment: "Destructive confirmation button that performs the panic wipe"), + role: .destructive + ) { + appChromeModel.confirmPanicWipe() + } + Button("common.cancel", role: .cancel) { + appChromeModel.cancelPanicWipe() + } + } HStack(spacing: 0) { Text(verbatim: "@")