From d18af9c56959a91766023ee7ff36876cb53d2de7 Mon Sep 17 00:00:00 2001 From: Taksh Date: Mon, 27 Jul 2026 10:07:57 +0300 Subject: [PATCH 1/6] fix: confirm before triple-tap panic wipe The settings danger-zone already confirms; the logo triple-tap shortcut wiped instantly. Gate it behind the same confirmation dialog so an accidental triple-tap cannot erase identity. --- bitchat/App/AppChromeModel.swift | 16 ++++++++++++++++ bitchat/Views/AppInfoView.swift | 2 +- bitchat/Views/ContentHeaderView.swift | 20 ++++++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) 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: "@") From 3f85bb454f27865eef16f8211d904f6dcabfb789 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 11:37:13 +0300 Subject: [PATCH 2/6] fix: make logo panic confirmation an opt-in setting Keep the triple-tap seizure path instant by default. People who fear accidental taps can enable confirmation; confirm-enabled copy lives in new catalog keys so the existing instant panic_note translations stay accurate. --- bitchat/App/AppChromeModel.swift | 9 +- bitchat/Localizable.xcstrings | 558 ++++++++++++++++++ bitchat/Services/PanicWipeSettings.swift | 40 ++ bitchat/ViewModels/ChatViewModel.swift | 1 + bitchat/Views/AppInfoView.swift | 22 +- bitchat/Views/ContentHeaderView.swift | 4 +- .../Services/PanicWipeSettingsTests.swift | 26 + 7 files changed, 654 insertions(+), 6 deletions(-) create mode 100644 bitchat/Services/PanicWipeSettings.swift create mode 100644 bitchatTests/Services/PanicWipeSettingsTests.swift diff --git a/bitchat/App/AppChromeModel.swift b/bitchat/App/AppChromeModel.swift index f07d9fc6..15459134 100644 --- a/bitchat/App/AppChromeModel.swift +++ b/bitchat/App/AppChromeModel.swift @@ -113,9 +113,14 @@ final class AppChromeModel: ObservableObject { prepareForPanic = preparation } - /// Triple-tap (and any other undiscoverable shortcut) must confirm first. + /// Triple-tap entry point. Instant by default (seizure path); confirms + /// only when `PanicWipeSettings.confirmLogoShortcut` is on. func requestPanicWipe() { - showPanicConfirmation = true + if PanicWipeSettings.confirmLogoShortcut { + showPanicConfirmation = true + } else { + panicClearAllData() + } } func confirmPanicWipe() { diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index f869ab85..d0dc3231 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -13589,6 +13589,378 @@ } } }, + "app_info.settings.danger.confirm_logo.subtitle" : { + "comment" : "Subtitle explaining the seizure-path trade-off of confirming before the logo panic wipe", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "معطّل افتراضيًا: النقر الثلاثي يمسح فورًا ليعمل إن سُحب الهاتف من يدك. فعّله فقط إن كانت النقرات العرضية تقلقك أكثر من فقدان تلك الثواني." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "ডিফল্টে বন্ধ: তিনবার ট্যাপ তাৎক্ষণিক মুছে দেয় যাতে কেউ ফোন কেড়ে নিলেও কাজ করে। শুধু তখন চালু করুন যখন দুর্ঘটনাবশত ট্যাপ সেই কয়েক সেকেন্ড হারানোর চেয়ে বেশি ভাবায়।" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "standardmäßig aus: dreifachtipp löscht sofort, damit es noch greift, wenn jemand das telefon greift. nur einschalten, wenn versehentliche tipps dir wichtiger sind als diese sekunden." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "off by default: triple-tap wipes instantly so it still works if someone grabs the phone. turn on only if accidental taps worry you more than losing those seconds." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "desactivado por defecto: un triple toque borra al instante para que siga sirviendo si alguien te quita el teléfono. actívalo solo si te preocupan más los toques accidentales que perder esos segundos." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "به‌طور پیش‌فرض خاموش: ضربهٔ سه‌تایی فوراً پاک می‌کند تا اگر کسی گوشی را بگیرد هنوز کار کند. فقط اگر ضربه‌های تصادفی بیش از از دست دادن آن چند ثانیه نگران‌تان می‌کند روشن کنید." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "naka-off bilang default: ang triple-tap ay agad na magwi-wipe para gumana pa rin kung may kumuha ng telepono. i-on lang kung mas ikinababahala mo ang aksidenteng tap kaysa mawala ang ilang segundo." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "désactivé par défaut : un triple appui efface immédiatement pour que ça marche encore si quelqu’un saisit le téléphone. active seulement si les appuis accidentels t’inquiètent plus que ces secondes perdues." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "כבוי כברירת מחדל: לחיצה משולשת מוחקת מיד כדי שעדיין יעבוד אם מישהו חוטף את הטלפון. הפעל רק אם לחיצות בטעות מדאיגות יותר מאובדן השניות האלה." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "डिफ़ॉल्ट रूप से बंद: तिहरा टैप तुरंत मिटाता है ताकि कोई फ़ोन छीन ले तो भी काम करे। केवल तभी चालू करें जब आकस्मिक टैप उन सेकंड खोने से ज़्यादा चिंतित करें।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "mati secara bawaan: ketuk tiga kali menghapus seketika agar tetap berguna jika seseorang merebut ponsel. nyalakan hanya jika ketukan tak sengaja lebih mengkhawatirkan daripada kehilangan hitungan detik itu." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "disattivo di default: il triplo tap cancella subito, così funziona ancora se qualcuno ti prende il telefono. attiva solo se i tap accidentali ti preoccupano più di quei secondi." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "既定はオフ: トリプルタップですぐ消去するので、誰かに端末を掴まれても間に合います。誤タップの方がその数秒より心配なときだけオンにしてください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "기본 꺼짐: 세 번 탭하면 즉시 삭제되어 누가 폰을 낚아채도 동작합니다. 실수 탭이 그 몇 초보다 더 걱정될 때만 켜세요." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "mati secara lalai: ketik tiga kali memadam serta-merta supaya masih berguna jika seseorang merampas telefon. hidupkan hanya jika ketikan tidak sengaja lebih membimbangkan daripada kehilangan saat-saat itu." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "पूर्वनिर्धारित रूपमा बन्द: तीनपटक ट्यापले तुरुन्त मेटाउँछ ताकि कसैले फोन खोसे पनि काम होस्। मात्र अन गर्नुहोस् यदि आकस्मिक ट्याप ती सेकेन्ड गुमाउनुभन्दा बढी चिन्ताको विषय हो।" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "standaard uit: driedubbeltik wist meteen zodat het nog werkt als iemand de telefoon pakt. zet alleen aan als per ongeluk tikken je meer zorgen baart dan die seconden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "domyślnie wyłączone: potrójne stuknięcie kasuje natychmiast, żeby zadziałało, gdy ktoś chwyci telefon. włącz tylko jeśli przypadkowe stuknięcia martwią cię bardziej niż te sekundy." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligado por predefinição: um toque triplo apaga de imediato para continuar a servir se alguém agarrar o telefone. liga só se toques acidentais te preocuparem mais do que esses segundos." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligado por padrão: um toque triplo apaga na hora para ainda funcionar se alguém agarrar o telefone. ligue só se toques acidentais preocuparem mais do que esses segundos." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "по умолчанию выкл.: тройной тап стирает сразу, чтобы сработало, если телефон выхватят. включай только если случайные нажатия пугают больше, чем эти секунды." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "av som standard: trippelklick raderar direkt så det fortfarande fungerar om någon tar telefonen. slå på bara om oavsiktliga tryck oroar dig mer än de sekunderna." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "இயல்பாக அணைக்கப்பட்டுள்ளது: மும்முறை தட்டுதல் உடனே அழிக்கும், யாராவது தொலைபேசியைப் பிடித்தாலும் வேலை செய்யும். தவறுதலான தட்டுதல்கள் அந்த விநாடிகளை இழப்பதை விட அதிகம் கவலைப்படுத்தினால் மட்டும் இயக்கவும்." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปิดเป็นค่าเริ่มต้น: แตะสามครั้งล้างทันทีเพื่อให้ยังทันหากมีคนคว้าโทรศัพท์ เปิดเฉพาะเมื่อกังวลการแตะพลาดมากกว่าการเสียไม่กี่วินาทีนั้น" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "varsayılan kapalı: üç kez dokunuş anında siler, böylece biri telefonu kaparsa hâlâ işe yarar. yalnızca yanlışlıkla dokunuşlar o saniyeleri kaybetmekten daha çok kaygılandırıyorsa aç." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "типово вимкнено: потрійне торкання стирає миттєво, щоб спрацювало, якщо хтось вихопить телефон. вмикай лише якщо випадкові торкання турбують більше за ці секунди." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "بطورِ اصل بند: تین بار ٹیپ فوراً مٹا دیتا ہے تاکہ کوئی فون چھینے تو بھی کام کرے۔ صرف تب آن کریں جب غلطی سے ٹیپ ان سیکنڈز کے ضیاع سے زیادہ پریشان کرے۔" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tắt mặc định: chạm ba lần xóa ngay để vẫn kịp nếu ai đó giật điện thoại. chỉ bật nếu lo chạm nhầm hơn là mất vài giây đó." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认关闭:三击立即清除,以便有人抢手机时仍来得及。仅在更担心误触、而不是那几秒时再打开。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "預設關閉:三擊立即清除,以便有人搶手機時仍來得及。僅在更擔心誤觸、而不是那幾秒時再打開。" + } + } + } + }, + "app_info.settings.danger.confirm_logo.title" : { + "comment" : "Title of the setting that requires confirmation before the triple-tap logo panic wipe", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تأكيد قبل المسح عبر الشعار" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "লোগো দিয়ে মুছার আগে নিশ্চিত করুন" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "vor logo-löschung bestätigen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirm before logo wipe" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar antes de borrar con el logo" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "تأیید پیش از پاک‌سازی با لوگو" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "kumpirmahin bago mag-wipe sa logo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmer avant l’effacement via le logo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אשר לפני מחיקה דרך הלוגו" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो से मिटाने से पहले पुष्टि करें" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "konfirmasi sebelum hapus lewat logo" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "conferma prima della cancellazione dal logo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ロゴ消去の前に確認する" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "로고 삭제 전에 확인" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "sahkan sebelum padam melalui logo" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो मार्फत मेटाउनु अघि पुष्टि गर्नुहोस्" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "bevestigen vóór logo-wissen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "potwierdź przed wymazaniem przez logo" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar antes de apagar pelo logo" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar antes de apagar pelo logo" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "подтверждать перед очисткой через логотип" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "bekräfta före logotyp-radering" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "லோகோ அழிப்புக்கு முன் உறுதிப்படுத்து" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ยืนยันก่อนล้างด้วยโลโก้" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo silmeden önce onayla" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "підтверджувати перед очищенням через логотип" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "لوگو سے مٹانے سے پہلے تصدیق کریں" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xác nhận trước khi xóa bằng logo" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过标志清除前先确认" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "透過標誌清除前先確認" + } + } + } + }, "app_info.settings.danger.panic_button" : { "extractionState" : "manual", "localizations" : { @@ -14329,6 +14701,192 @@ } } }, + "app_info.settings.danger.panic_note_confirm" : { + "comment" : "Caption under the panic wipe button when confirm-before-logo-wipe is on", + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يمسح كل الرسائل والمفاتيح والهوية. النقر الثلاثي على شعار bitchat/ يطلب نفس التأكيد." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "সব বার্তা, কী এবং পরিচয় মুছে দেয়। bitchat/ লোগোতে তিনবার ট্যাপ একই নিশ্চিতকরণ চায়।" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "löscht alle nachrichten, schlüssel und identität. dreimaliges tippen auf das bitchat/-logo fragt nach derselben bestätigung." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "erases all messages, keys, and identity. triple-tapping the bitchat/ logo asks for the same confirmation." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "borra todos los mensajes, claves e identidad. un triple toque en el logo bitchat/ pide la misma confirmación." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "همهٔ پیام‌ها، کلیدها و هویت را پاک می‌کند. ضربهٔ سه‌تایی روی لوگوی bitchat/ همان تأیید را می‌پرسد." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "binubura ang lahat ng mensahe, key, at identity. ang triple-tap sa bitchat/ logo ay humihingi ng parehong kumpirmasyon." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "efface tous les messages, clés et l’identité. un triple appui sur le logo bitchat/ demande la même confirmation." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מוחק את כל ההודעות, המפתחות והזהות. לחיצה משולשת על לוגו bitchat/ מבקשת את אותו אישור." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "सभी संदेश, कुंजियाँ और पहचान मिटाता है। bitchat/ लोगो पर तिहरा टैप भी वही पुष्टि माँगता है।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "menghapus semua pesan, kunci, dan identitas. ketuk tiga kali logo bitchat/ meminta konfirmasi yang sama." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "cancella tutti i messaggi, le chiavi e l’identità. un triplo tap sul logo bitchat/ chiede la stessa conferma." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてのメッセージ、鍵、アイデンティティを消去します。bitchat/ ロゴのトリプルタップでも同じ確認を求めます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모든 메시지, 키, 신원을 지웁니다. bitchat/ 로고를 세 번 탭해도 같은 확인을 묻습니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "memadam semua mesej, kunci dan identiti. ketik tiga kali logo bitchat/ meminta pengesahan yang sama." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "सबै सन्देश, कुञ्जी र पहिचान मेटाउँछ। bitchat/ लोगोमा तीनपटक ट्यापले पनि उही पुष्टि माग्छ।" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wist alle berichten, sleutels en identiteit. driedubbeltikken op het bitchat/-logo vraagt dezelfde bevestiging." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "kasuje wszystkie wiadomości, klucze i tożsamość. potrójne stuknięcie logo bitchat/ prosi o to samo potwierdzenie." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "apaga todas as mensagens, chaves e identidade. um toque triplo no logo bitchat/ pede a mesma confirmação." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "apaga todas as mensagens, chaves e identidade. um toque triplo no logo bitchat/ pede a mesma confirmação." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирает все сообщения, ключи и личность. тройной тап по логотипу bitchat/ запрашивает то же подтверждение." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "raderar alla meddelanden, nycklar och identitet. trippelklick på bitchat/-logotypen ber om samma bekräftelse." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "அனைத்து செய்திகள், சாவிகள் மற்றும் அடையாளத்தையும் அழிக்கும். bitchat/ லோகோவை மும்முறை தட்டினாலும் அதே உறுதிப்படுத்தலைக் கேட்கும்." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ลบข้อความ กุญแจ และตัวตนทั้งหมด การแตะโลโก้ bitchat/ สามครั้งจะขอการยืนยันเดียวกัน" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "tüm mesajları, anahtarları ve kimliği siler. bitchat/ logosuna üç kez dokunmak aynı onayı ister." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирає всі повідомлення, ключі та ідентичність. потрійне торкання логотипу bitchat/ просить те саме підтвердження." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "تمام پیغامات، کلیدیں اور شناخت مٹا دیتا ہے۔ bitchat/ لوگو پر تین بار ٹیپ بھی وہی تصدیق مانگتا ہے۔" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xóa mọi tin nhắn, khóa và danh tính. chạm ba lần vào logo bitchat/ sẽ hỏi xác nhận tương tự." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有消息、密钥和身份。三击 bitchat/ 标志也会要求同样的确认。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有訊息、金鑰與身分。三擊 bitchat/ 標誌也會要求同樣的確認。" + } + } + } + }, "app_info.settings.danger.title" : { "extractionState" : "manual", "localizations" : { diff --git a/bitchat/Services/PanicWipeSettings.swift b/bitchat/Services/PanicWipeSettings.swift new file mode 100644 index 00000000..eead4526 --- /dev/null +++ b/bitchat/Services/PanicWipeSettings.swift @@ -0,0 +1,40 @@ +// +// PanicWipeSettings.swift +// bitchat +// +// This is free and unencumbered software released into the public domain. +// For more information, see +// + +import Foundation + +/// Controls whether the undiscoverable `bitchat/` logo triple-tap asks before +/// wiping. +/// +/// The shortcut exists for the seizure path — someone taking the phone out of +/// your hand — so it defaults to an instant wipe. People who fear accidental +/// taps can opt into a confirmation step; that trade-off costs the seconds the +/// feature is built around. +enum PanicWipeSettings { + private static let confirmLogoShortcutKey = "panic.confirmLogoShortcut" + + /// When `true`, triple-tapping the logo shows the same confirmation dialog + /// as the settings panic button. Defaults to `false` (instant wipe). + static var confirmLogoShortcut: Bool { + get { confirmLogoShortcut(in: .standard) } + set { setConfirmLogoShortcut(newValue, in: .standard) } + } + + static func confirmLogoShortcut(in defaults: UserDefaults) -> Bool { + defaults.object(forKey: confirmLogoShortcutKey) as? Bool ?? false + } + + static func setConfirmLogoShortcut(_ confirm: Bool, in defaults: UserDefaults) { + defaults.set(confirm, forKey: confirmLogoShortcutKey) + } + + /// Panic-wipe hook. Removing the key restores the instant default. + static func reset(in defaults: UserDefaults = .standard) { + defaults.removeObject(forKey: confirmLogoShortcutKey) + } +} diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 12adfda9..b20ae303 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -1637,6 +1637,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, SynchronousMessage MeshSightingsTracker.shared.clear() MeshEchoSettings.reset() NotificationPrivacySettings.reset() + PanicWipeSettings.reset() // A hand-added relay names an operator someone chose to route through, // which is the kind of trace a wipe should not leave behind. NostrRelaySettings.reset() diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index 204689ba..759d066c 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -22,6 +22,7 @@ struct AppInfoView: View { @State private var liveVoiceEnabled = PTTSettings.liveVoiceEnabled @State private var locationNotesEnabled = LocationNotesSettings.enabled @State private var hideMessagePreviews = NotificationPrivacySettings.hideMessagePreviews + @State private var confirmLogoPanic = PanicWipeSettings.confirmLogoShortcut @State private var customRelays = NostrRelaySettings.customRelays() @State private var relayInput = "" @State private var relayError: String? @@ -120,7 +121,10 @@ 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 asks for the same confirmation.", 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 does the same, instantly.", comment: "Caption under the panic wipe button when logo confirmation is off (default)") + static let panicNoteConfirm = String(localized: "app_info.settings.danger.panic_note_confirm", defaultValue: "erases all messages, keys, and identity. triple-tapping the bitchat/ logo asks for the same confirmation.", comment: "Caption under the panic wipe button when confirm-before-logo-wipe is on") + static let confirmLogoTitle = String(localized: "app_info.settings.danger.confirm_logo.title", defaultValue: "confirm before logo wipe", comment: "Title of the setting that requires confirmation before the triple-tap logo panic wipe") + static let confirmLogoSubtitle = String(localized: "app_info.settings.danger.confirm_logo.subtitle", defaultValue: "off by default: triple-tap wipes instantly so it still works if someone grabs the phone. turn on only if accidental taps worry you more than losing those seconds.", comment: "Subtitle explaining the seizure-path trade-off of confirming before the logo panic wipe") 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") } @@ -569,10 +573,24 @@ struct AppInfoView: View { Button("common.cancel", role: .cancel) {} } - Text(Strings.Settings.panicNote) + Text(confirmLogoPanic ? Strings.Settings.panicNoteConfirm : Strings.Settings.panicNote) .bitchatFont(size: 11) .foregroundColor(secondaryTextColor) .fixedSize(horizontal: false, vertical: true) + + settingsCard { + settingToggle( + title: Text(verbatim: Strings.Settings.confirmLogoTitle), + subtitle: Text(verbatim: Strings.Settings.confirmLogoSubtitle), + isOn: Binding( + get: { confirmLogoPanic }, + set: { newValue in + confirmLogoPanic = newValue + PanicWipeSettings.confirmLogoShortcut = newValue + } + ) + ) + } } } } diff --git a/bitchat/Views/ContentHeaderView.swift b/bitchat/Views/ContentHeaderView.swift index 22504219..d3936990 100644 --- a/bitchat/Views/ContentHeaderView.swift +++ b/bitchat/Views/ContentHeaderView.swift @@ -58,8 +58,8 @@ struct ContentHeaderView: View { } // 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 — but - // still requires confirmation before erasing anything.) + // stays undiscoverable on purpose — it's destructive — and + // wipes instantly unless confirm-before-logo-wipe is on.) .accessibilityAddTraits(.isButton) .accessibilityHint( String(localized: "content.accessibility.app_info_hint", comment: "Accessibility hint on the bitchat/ logo explaining a tap opens app info") diff --git a/bitchatTests/Services/PanicWipeSettingsTests.swift b/bitchatTests/Services/PanicWipeSettingsTests.swift new file mode 100644 index 00000000..836e50d6 --- /dev/null +++ b/bitchatTests/Services/PanicWipeSettingsTests.swift @@ -0,0 +1,26 @@ +import Testing +import Foundation + +struct PanicWipeSettingsTests { + private func isolatedDefaults() -> UserDefaults { + UserDefaults(suiteName: "PanicWipeSettingsTests.\(UUID().uuidString)")! + } + + @Test func defaultsToInstantWipe() { + let defaults = isolatedDefaults() + #expect(!PanicWipeSettings.confirmLogoShortcut(in: defaults)) + } + + @Test func persistsOptInConfirmation() { + let defaults = isolatedDefaults() + PanicWipeSettings.setConfirmLogoShortcut(true, in: defaults) + #expect(PanicWipeSettings.confirmLogoShortcut(in: defaults)) + } + + @Test func resetRestoresInstantDefault() { + let defaults = isolatedDefaults() + PanicWipeSettings.setConfirmLogoShortcut(true, in: defaults) + PanicWipeSettings.reset(in: defaults) + #expect(!PanicWipeSettings.confirmLogoShortcut(in: defaults)) + } +} From ad7f4b27f268e60593939019f0af5a9f072d89c9 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 11:46:46 +0300 Subject: [PATCH 3/6] fix: import bitchat in PanicWipeSettingsTests Without @testable import the suite cannot see PanicWipeSettings and CI fails at compile time. --- bitchatTests/Services/PanicWipeSettingsTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/bitchatTests/Services/PanicWipeSettingsTests.swift b/bitchatTests/Services/PanicWipeSettingsTests.swift index 836e50d6..0269b0ce 100644 --- a/bitchatTests/Services/PanicWipeSettingsTests.swift +++ b/bitchatTests/Services/PanicWipeSettingsTests.swift @@ -1,5 +1,6 @@ import Testing import Foundation +@testable import bitchat struct PanicWipeSettingsTests { private func isolatedDefaults() -> UserDefaults { From 0f2bbac9ce103c27a7408a77b38491ae2b750349 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 18:06:53 +0300 Subject: [PATCH 4/6] Address Jack review: one three-way logo panic mode (#1575). Fold confirm (#1509) and disable into PanicWipeSettings.LogoShortcutMode (instant / confirm / off). AppChromeModel.requestPanicWipe is the single choke point; turning the gesture off asks for confirmation and a disabled triple-tap shows an alert instead of a silent no-op. --- bitchat/App/AppChromeModel.swift | 20 +- bitchat/Localizable.xcstrings | 1851 +++++++++++++++++ bitchat/Services/PanicWipeSettings.swift | 60 +- bitchat/Views/AppInfoView.swift | 89 +- bitchat/Views/ContentHeaderView.swift | 20 +- .../Services/PanicWipeSettingsTests.swift | 46 +- 6 files changed, 2035 insertions(+), 51 deletions(-) diff --git a/bitchat/App/AppChromeModel.swift b/bitchat/App/AppChromeModel.swift index 15459134..79152ba3 100644 --- a/bitchat/App/AppChromeModel.swift +++ b/bitchat/App/AppChromeModel.swift @@ -20,6 +20,9 @@ final class AppChromeModel: ObservableObject { @Published var showScreenshotPrivacyWarning = false /// Confirmation gate for the triple-tap logo panic shortcut (#152). @Published var showPanicConfirmation = false + /// Triple-tap while the gesture is set to off — silent no-op would be + /// worse in a panic, so surface that nothing was wiped. + @Published var showPanicGestureDisabledAlert = false private let chatViewModel: ChatViewModel private let onPanicWipe: () -> Void @@ -113,13 +116,16 @@ final class AppChromeModel: ObservableObject { prepareForPanic = preparation } - /// Triple-tap entry point. Instant by default (seizure path); confirms - /// only when `PanicWipeSettings.confirmLogoShortcut` is on. + /// Triple-tap entry point. Mode is decided here (not at the gesture site): + /// instant wipe by default, optional confirm, or an explicit disabled alert. func requestPanicWipe() { - if PanicWipeSettings.confirmLogoShortcut { - showPanicConfirmation = true - } else { + switch PanicWipeSettings.logoShortcutMode { + case .instant: panicClearAllData() + case .confirm: + showPanicConfirmation = true + case .off: + showPanicGestureDisabledAlert = true } } @@ -132,6 +138,10 @@ final class AppChromeModel: ObservableObject { showPanicConfirmation = false } + func acknowledgePanicGestureDisabled() { + showPanicGestureDisabledAlert = false + } + func panicClearAllData() { prepareForPanic?() onPanicWipe() diff --git a/bitchat/Localizable.xcstrings b/bitchat/Localizable.xcstrings index d0dc3231..5e1b9ff5 100644 --- a/bitchat/Localizable.xcstrings +++ b/bitchat/Localizable.xcstrings @@ -13961,6 +13961,1671 @@ } } }, + "app_info.settings.danger.logo_disable_confirm_action" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إيقاف" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "বন্ধ করুন" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "ausschalten" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "turn off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagar" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "خاموش کن" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "i-off" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "désactiver" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "כבה" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "बंद करें" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "matikan" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "disattiva" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オフにする" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "끄기" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "matikan" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "बन्द गर्नुहोस्" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "uitzetten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wyłącz" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligar" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligar" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "выключить" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "stäng av" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "அணை" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปิด" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "kapat" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "вимкнути" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "بند کریں" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tắt" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + } + } + }, + "app_info.settings.danger.logo_disable_confirm_title" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إيقاف المسح بالشعار؟" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "লোগো মুছা বন্ধ করবেন?" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo-löschen ausschalten?" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "turn off logo wipe?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿apagar el borrado del logo?" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "پاک‌کردن با لوگو خاموش شود؟" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "i-off ang logo wipe?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "désactiver l’effacement logo ?" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לכבות מחיקת לוגו?" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो मिटाना बंद करें?" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "matikan hapus logo?" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "disattivare la cancellazione dal logo?" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ロゴ消去をオフにしますか?" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "로고 지우기를 끌까요?" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "matikan padam logo?" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो मेटाउने बन्द गर्ने?" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo-wissen uitzetten?" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wyłączyć czyszczenie logo?" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligar o apagar pelo logo?" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligar o apagar pelo logo?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "выключить стирание логотипом?" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "stäng av radering via loggan?" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "லோகோ அழிப்பை அணைக்கவா?" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปิดการลบด้วยโลโก้?" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo silmeyi kapat?" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "вимкнути стирання логотипом?" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "لوگو مٹانا بند کریں؟" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tắt xóa bằng logo?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭标志清除?" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉標誌清除?" + } + } + } + }, + "app_info.settings.danger.logo_disabled_message" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لم يُمسح شيء. أعد تشغيل إيماءة الشعار في الإعدادات، أو استخدم زر المسح الطارئ هناك." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "কিছুই মুছেনি। সেটিংসে লোগো জেসচার আবার চালু করুন, অথবা সেখানকার প্যানিক ওয়াইপ বোতাম ব্যবহার করুন।" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "nichts wurde gelöscht. schalte die logo-geste in den einstellungen wieder ein, oder nutze dort den panic-wipe-knopf." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "nothing was wiped. turn the logo gesture back on in settings, or use the panic wipe button there." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "no se borró nada. vuelve a activar el gesto del logo en ajustes, o usa el botón de borrado de pánico allí." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "چیزی پاک نشد. ژست لوگو را در تنظیمات دوباره روشن کنید، یا از دکمه پاک‌سازی اضطراری آنجا استفاده کنید." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "walang nabura. i-on muli ang logo gesture sa settings, o gamitin ang panic wipe button doon." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "rien n’a été effacé. réactivez le geste logo dans les réglages, ou utilisez le bouton d’effacement panique." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "דבר לא נמחק. הפעל מחדש את מחוות הלוגו בהגדרות, או השתמש בכפתור המחיקה שם." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "कुछ नहीं मिटा। सेटिंग्स में लोगो जेस्चर वापस चालू करें, या वहाँ का पैनिक वाइप बटन इस्तेमाल करें।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "tidak ada yang dihapus. nyalakan lagi gestur logo di pengaturan, atau gunakan tombol hapus panik di sana." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "non è stato cancellato nulla. riattiva il gesto del logo nelle impostazioni, oppure usa lì il pulsante di cancellazione panico." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "何も消去されていません。設定でロゴジェスチャを戻すか、そこのパニック消去ボタンを使ってください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "지워진 것이 없습니다. 설정에서 로고 제스처를 다시 켜거나 그곳의 패닉 지우기 버튼을 사용하세요." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "tiada yang dipadam. hidupkan semula gestur logo dalam tetapan, atau gunakan butang padam panik di situ." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "केही मेटिएन। सेटिङमा लोगो जेस्चर फेरि खोल्नुहोस्, वा त्यहाँको प्यानिक वाइप बटन प्रयोग गर्नुहोस्।" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "er is niets gewist. zet het logo-gebaar weer aan in instellingen, of gebruik daar de panic-wipeknop." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "nic nie zostało wyczyszczone. włącz ponownie gest logo w ustawieniach albo użyj tam przycisku panic wipe." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "nada foi apagado. volte a ligar o gesto do logo nas definições, ou use o botão de apagar em pânico." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "nada foi apagado. ligue de novo o gesto do logo nas configurações, ou use o botão de apagar em pânico." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "ничего не стёрто. снова включите жест логотипа в настройках или нажмите там кнопку экстренного стирания." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "ingenting raderades. sätt på logggesten igen i inställningar, eller använd panic wipe-knappen där." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "எதுவும் அழியவில்லை. அமைப்புகளில் லோகோ சைகையை மீண்டும் இயக்கவும், அல்லது அங்குள்ள பேனிக் அழி பொத்தானைப் பயன்படுத்தவும்." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ไม่ได้ลบอะไร เปิดท่าทางโลโก้ในตั้งค่าอีกครั้ง หรือใช้ปุ่มลบฉุกเฉินที่นั่น" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "hiçbir şey silinmedi. ayarlardan logo jestini yeniden açın veya oradaki panik sil düğmesini kullanın." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "нічого не стерто. знову ввімкніть жест логотипу в параметрах або натисніть там кнопку екстреного стирання." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "کچھ نہیں مٹا۔ سیٹنگز میں لوگو جیسچر دوبارہ چالو کریں، یا وہاں کا پینک وائپ بٹن استعمال کریں۔" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "không có gì bị xóa. bật lại cử chỉ logo trong cài đặt, hoặc dùng nút xóa khẩn cấp ở đó." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有清除任何内容。请在设置中重新打开标志手势,或使用那里的紧急清除按钮。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "沒有清除任何內容。請在設定中重新打開標誌手勢,或使用那裡的緊急清除按鈕。" + } + } + } + }, + "app_info.settings.danger.logo_disabled_title" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مسح الشعار متوقف" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "লোগো মুছা বন্ধ" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo-löschen ist aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo wipe is off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "borrado del logo apagado" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "پاک‌کردن لوگو خاموش است" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "naka-off ang logo wipe" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "effacement logo désactivé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מחיקת לוגו כבויה" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो मिटाना बंद है" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "hapus logo mati" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "cancellazione logo disattivata" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ロゴ消去はオフです" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "로고 지우기가 꺼져 있습니다" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "padam logo mati" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो मेटाउने बन्द छ" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo-wissen staat uit" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "czyszczenie logo wyłączone" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagar pelo logo desligado" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagar pelo logo desligado" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирание логотипом выключено" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "radering via loggan är av" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "லோகோ அழிப்பு அணைக்கப்பட்டுள்ளது" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "การลบด้วยโลโก้ปิดอยู่" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo silme kapalı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирання логотипом вимкнено" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "لوگو مٹانا بند ہے" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xóa bằng logo đang tắt" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标志清除已关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "標誌清除已關閉" + } + } + } + }, + "app_info.settings.danger.logo_mode.confirm" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "أكد أولاً" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "আগে নিশ্চিত করুন" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "erst bestätigen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirm first" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar antes" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "ابتدا تأیید" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "kumpirmahin muna" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmer d’abord" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "אשר קודם" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "पहले पुष्टि करें" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "konfirmasi dulu" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "conferma prima" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "先に確認" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "먼저 확인" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "sahkan dahulu" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "पहिले पुष्टि" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "eerst bevestigen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "najpierw potwierdź" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar primeiro" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "confirmar primeiro" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "сначала подтвердить" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "bekräfta först" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "முதலில் உறுதிப்படுத்து" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ยืนยันก่อน" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "önce onayla" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "спочатку підтвердити" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "پہلے تصدیق" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xác nhận trước" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "先确认" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "先確認" + } + } + } + }, + "app_info.settings.danger.logo_mode.instant" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فوري" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "তাৎক্ষণিক" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "sofort" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "instant" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantáneo" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "فوری" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "agad" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "immédiat" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מיידי" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "तत्काल" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "instan" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "immediato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "即座" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "즉시" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "segera" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "तुरुन्त" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "direct" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "natychmiast" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantâneo" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantâneo" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "сразу" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "direkt" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "உடனடி" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ทันที" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "anında" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "одразу" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "فوری" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "ngay" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "立即" + } + } + } + }, + "app_info.settings.danger.logo_mode.off" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إيقاف" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "বন্ধ" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagado" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "خاموش" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "off" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "désactivé" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "כבוי" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "बंद" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "mati" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "disattivato" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "オフ" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "끔" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "mati" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "बन्द" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "uit" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wyłączone" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligado" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "desligado" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "выкл." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "av" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "அணை" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปิด" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "kapalı" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "вимк." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "بند" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "tắt" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關" + } + } + } + }, + "app_info.settings.danger.logo_mode.subtitle" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فوري افتراضيًا لمسار المصادرة. أكّد إن أقلقتك النقرات العرضية. الإيقاف يزيل الإيماءة — والإيقاف يطلب تأكيدًا لأن النقر الثلاثي المعطّل يفشل بصمت في الذعر." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "বাজেয়াপ্তির পথে ডিফল্ট তাৎক্ষণিক। ভুল ট্যাপ চিন্তা হলে নিশ্চিত করুন। বন্ধ জেসচার সরায় — বন্ধ করতে নিশ্চিতকরণ চায় কারণ মৃত ট্রিপল-ট্যাপ আতঙ্কে চুপচাপ ব্যর্থ হয়।" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "standardmäßig sofort für den beschlagnahme-pfad. bestätigen, wenn versehentliche tipps sorgen. aus entfernt die geste — das ausschalten fragt nach, weil ein deaktivierter dreifachtipp in der panik still scheitert." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "instant by default for the seizure path. confirm if accidental taps worry you. off removes the gesture — turning off asks for confirmation because a disabled triple-tap is a silent failure in a panic." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantáneo por defecto para el camino de confiscación. confirma si te preocupan toques accidentales. apagado quita el gesto — al apagar se pide confirmación porque un triple toque muerto falla en silencio en un pánico." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "پیش‌فرض فوری برای مسیر ضبط. اگر ضربه تصادفی نگران‌تان است تأیید کنید. خاموش ژست را برمی‌دارد — خاموش‌کردن تأیید می‌خواهد چون ضربه سه‌تایی مرده در وحشت بی‌صدا شکست می‌خورد." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "agad bilang default para sa seizure path. kumpirmahin kung nakakaalarma ang aksidenteng tap. off inaalis ang gesture — ang pag-off ay humihingi ng kumpirmasyon dahil ang patay na triple-tap ay tahimik na nabibigo sa panic." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "immédiat par défaut pour la saisie. confirmer si les taps accidentels inquiètent. désactivé retire le geste — l’extinction demande confirmation car un triple tap mort est un échec silencieux en panique." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מיידי כברירת מחדל לנתיב תפיסה. אשר אם הקשות בטעות מדאיגות. כבוי מסיר את המחווה — הכיבוי מבקש אישור כי הקשה משולשת מתה נכשלת בשקט בפאניקה." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "ज़ब्ती पथ के लिए डिफ़ॉल्ट तत्काल। आकस्मिक टैप चिंता हो तो पुष्टि। बंद जेस्चर हटाता है — बंद करते समय पुष्टि क्योंकि मृत ट्रिपल-टैप पैनिक में चुपचाप विफल होता है।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "instan secara default untuk jalur penyitaan. konfirmasi jika ketukan tak sengaja mengkhawatirkan. mati menghapus gestur — mematikan meminta konfirmasi karena ketuk tiga mati gagal diam-diam saat panik." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "immediato di default per il percorso di sequestro. conferma se temi tocchi accidentali. disattivato rimuove il gesto — spegnerlo chiede conferma perché un triplo tap morto fallisce in silenzio nel panico." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "押収時は既定で即座。誤タップが心配なら確認。オフはジェスチャを外します。オフにするときは確認します。無効なトリプルタップはパニック時に無言で失敗するためです。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "압수 경로에서는 기본이 즉시입니다. 실수 탭이 걱정되면 확인. 끔은 제스처를 제거합니다. 끌 때는 확인합니다. 꺼진 세 번 탭은 패닉 때 조용히 실패하기 때문입니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "segera secara lalai untuk laluan rampasan. sahkan jika ketikan tidak sengaja merisaukan. mati mengalih gestur — mematikan meminta pengesahan kerana ketik tiga mati gagal senyap semasa panik." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "जफत मार्गका लागि पूर्वनिर्धारित तुरुन्त। आकस्मिक ट्याप चिन्ता भए पुष्टि। बन्दले जेस्चर हटाउँछ — बन्द गर्दा पुष्टि सोधिन्छ किनकि मृत ट्रिपल-ट्याप आतंकमा चुपचाप असफल हुन्छ।" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "standaard direct voor het inbeslagnamepad. bevestig als per ongeluk tippen je zorgen baart. uit verwijdert het gebaar — uitzetten vraagt bevestiging omdat een dode driedubbele tip stil faalt in paniek." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "domyślnie natychmiast na ścieżce zajęcia. potwierdź, jeśli martwią przypadkowe stuknięcia. wyłączone usuwa gest — wyłączenie pyta o potwierdzenie, bo martwy potrójny tap milcząco zawodzi w panice." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantâneo por omissão para o caminho de apreensão. confirme se toques acidentais preocupam. desligado remove o gesto — desligar pede confirmação porque um toque triplo morto falha em silêncio num pânico." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "instantâneo por padrão para o caminho de apreensão. confirme se toques acidentais preocupam. desligado remove o gesto — desligar pede confirmação porque um toque triplo morto falha em silêncio num pânico." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "по умолчанию сразу для сценария изъятия. подтверждение, если страшны случайные тапы. выкл. убирает жест — выключение спрашивает подтверждение, потому что мёртвый тройной тап тихо проваливается в панике." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "direkt som standard för beslagtagandespåret. bekräfta om oavsiktliga tryck oroar. av tar bort gesten — att stänga av frågar eftersom en död trippelklick tyst misslyckas i panik." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "பறிமுதல் பாதைக்கு இயல்பு உடனடி. தவறுதலான தட்டு கவலைப்பட்டால் உறுதிப்படுத்து. அணைத்தால் சைகை நீங்கும் — அணைக்கும்போது உறுதி கேட்கும்; செயலிழந்த மூன்று தட்டு பீதியில் அமைதியாக தோல்வியடையும்." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ค่าเริ่มต้นคือทันทีสำหรับเส้นทางยึด หากกังวลการแตะพลาดให้ยืนยัน ปิดจะเอาท่าทางออก — การปิดจะถามยืนยัน เพราะการแตะสามครั้งที่ปิดจะล้มเหลวเงียบตอนตื่นตระหนก" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "el koyma yolu için varsayılan anında. yanlışlıkla dokunmalar kaygılandırıyorsa onaylayın. kapalı jesti kaldırır — kapatmak onay ister çünkü ölü üç dokunuş panikte sessizce başarısız olur." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "за замовчуванням одразу для сценарію вилучення. підтвердження, якщо лякають випадкові дотики. вимк. прибирає жест — вимкнення питає підтвердження, бо мертвий потрійний дотик тихо провалюється в паніці." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "ضبطی راستے کے لیے ڈیفالٹ فوری۔ اگر غلط ٹیپ پریشان کرے تو تصدیق۔ بند جیسچر ہٹا دیتا ہے — بند کرتے وقت تصدیق کیونکہ مردہ ٹرپل-ٹیپ گھبراہٹ میں خاموشی سے ناکام ہوتا ہے۔" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "mặc định ngay cho đường tịch thu. xác nhận nếu chạm nhầm gây lo. tắt gỡ cử chỉ — tắt sẽ hỏi xác nhận vì chạm ba lần chết sẽ thất bại im lặng khi hoảng." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "查扣场景默认立即清除。担心误触可选确认。关闭会去掉手势——关闭时会再确认,因为禁用的三连点在紧急时会无声失败。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "查扣場景預設立即清除。擔心誤觸可選確認。關閉會去掉手勢——關閉時會再確認,因為停用的三連點在緊急時會無聲失敗。" + } + } + } + }, + "app_info.settings.danger.logo_mode.title" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "مسح بنقر شعار ثلاثي" + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "লোগো ট্রিপল-ট্যাপ মুছা" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo-dreifachtipp-löschen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo triple-tap wipe" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "borrado por triple toque del logo" + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "پاک کردن با ضربه سه‌تایی لوگو" + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "burahin sa triple-tap ng logo" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "effacement triple tap logo" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מחיקה בהקשה משולשת על הלוגו" + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो ट्रिपल-टैप मिटान" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "hapus ketuk tiga logo" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "cancellazione triplo tap logo" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ロゴ三回タップで消去" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "로고 세 번 탭으로 지우기" + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "padam ketik tiga logo" + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "लोगो ट्रिपल-ट्याप मेटाउने" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo driedubbele tip wissen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "czyszczenie potrójnym stuknięciem logo" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagar com toque triplo no logo" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "apagar com toque triplo no logo" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирание тройным тапом логотипа" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "radera med trippelklick på loggan" + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "லோகோ மூன்று தட்டில் அழி" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ลบด้วยแตะโลโก้สามครั้ง" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "logo üç dokunuşla sil" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирання потрійним дотиком логотипу" + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "لوگو ٹرپل-ٹیپ مٹانا" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xóa bằng chạm ba lần logo" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "标志三连点清除" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "標誌三連點清除" + } + } + } + }, "app_info.settings.danger.panic_button" : { "extractionState" : "manual", "localizations" : { @@ -14887,6 +16552,192 @@ } } }, + "app_info.settings.danger.panic_note_disabled" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يمسح كل الرسائل والمفاتيح والهوية. النقر الثلاثي على شعار bitchat/ متوقف — هذا الزر فقط يمسح." + } + }, + "bn" : { + "stringUnit" : { + "state" : "translated", + "value" : "সব বার্তা, কী এবং পরিচয় মুছে ফেলে। bitchat/ লোগো ট্রিপল-ট্যাপ বন্ধ — শুধু এই বোতাম মুছে।" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "löscht alle nachrichten, schlüssel und die identität. der bitchat/-logo-dreifachtipp ist aus — nur dieser knopf wischt." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "erases all messages, keys, and identity. the bitchat/ logo triple-tap is off — only this button wipes." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "borra todos los mensajes, claves e identidad. el triple toque del logo bitchat/ está apagado: solo este botón borra." + } + }, + "fa" : { + "stringUnit" : { + "state" : "translated", + "value" : "همه پیام‌ها، کلیدها و هویت را پاک می‌کند. ضربه سه‌تایی لوگوی bitchat/ خاموش است — فقط این دکمه پاک می‌کند." + } + }, + "fil" : { + "stringUnit" : { + "state" : "translated", + "value" : "binubura ang lahat ng mensahe, key, at identity. naka-off ang triple-tap sa bitchat/ logo — button na ito lang ang magbubura." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "efface tous les messages, clés et l’identité. le triple tap sur le logo bitchat/ est désactivé — seul ce bouton efface." + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "מוחק את כל ההודעות, המפתחות והזהות. הקשה משולשת על לוגו bitchat/ כבויה — רק הכפתור הזה מוחק." + } + }, + "hi" : { + "stringUnit" : { + "state" : "translated", + "value" : "सभी संदेश, कुंजियाँ और पहचान मिटाता है। bitchat/ लोगो ट्रिपल-टैप बंद है — केवल यह बटन मिटाता है।" + } + }, + "id" : { + "stringUnit" : { + "state" : "translated", + "value" : "menghapus semua pesan, kunci, dan identitas. ketuk tiga kali logo bitchat/ dimatikan — hanya tombol ini yang menghapus." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "cancella tutti i messaggi, le chiavi e l’identità. il triplo tap sul logo bitchat/ è disattivato — solo questo pulsante cancella." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すべてのメッセージ、鍵、身元を消去します。bitchat/ ロゴのトリプルタップはオフです。消すのはこのボタンだけです。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "모든 메시지, 키, 신원을 지웁니다. bitchat/ 로고 세 번 탭은 꺼져 있습니다. 이 버튼만 지웁니다." + } + }, + "ms" : { + "stringUnit" : { + "state" : "translated", + "value" : "memadam semua mesej, kunci dan identiti. ketik tiga kali logo bitchat/ dimatikan — hanya butang ini memadam." + } + }, + "ne" : { + "stringUnit" : { + "state" : "translated", + "value" : "सबै सन्देश, कुञ्जी र पहिचान मेटाउँछ। bitchat/ लोगो ट्रिपल-ट्याप बन्द छ — यो बटन मात्र मेटाउँछ।" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "wist alle berichten, sleutels en identiteit. de driedubbele tip op het bitchat/-logo staat uit — alleen deze knop wist." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "usuwa wszystkie wiadomości, klucze i tożsamość. potrójne stuknięcie logo bitchat/ jest wyłączone — czyści tylko ten przycisk." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "apaga todas as mensagens, chaves e identidade. o toque triplo no logo bitchat/ está desligado — só este botão apaga." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "apaga todas as mensagens, chaves e identidade. o toque triplo no logo bitchat/ está desligado — só este botão apaga." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирает все сообщения, ключи и личность. тройной тап по логотипу bitchat/ выключен — стирает только эта кнопка." + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "raderar alla meddelanden, nycklar och identitet. trippelklick på bitchat/-loggan är av — bara den här knappen raderar." + } + }, + "ta" : { + "stringUnit" : { + "state" : "translated", + "value" : "எல்லா செய்திகள், விசைகள், அடையாளத்தையும் அழிக்கும். bitchat/ லோகோ மூன்று தட்டு அணைக்கப்பட்டுள்ளது — இந்த பொத்தான் மட்டும் அழிக்கும்." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ลบข้อความ กุญแจ และตัวตนทั้งหมด การแตะโลโก้ bitchat/ สามครั้งปิดอยู่ — มีแค่ปุ่มนี้ที่ลบ" + } + }, + "tr" : { + "stringUnit" : { + "state" : "translated", + "value" : "tüm mesajları, anahtarları ve kimliği siler. bitchat/ logosuna üç kez dokunma kapalı — yalnızca bu düğme siler." + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "стирає всі повідомлення, ключі та особу. потрійний дотик логотипу bitchat/ вимкнено — стирає лише ця кнопка." + } + }, + "ur" : { + "stringUnit" : { + "state" : "translated", + "value" : "تمام پیغامات، کلیدیں اور شناخت مٹا دیتا ہے۔ bitchat/ لوگو ٹرپل-ٹیپ بند ہے — صرف یہ بٹن مٹاتا ہے۔" + } + }, + "vi" : { + "stringUnit" : { + "state" : "translated", + "value" : "xóa mọi tin nhắn, khóa và danh tính. chạm ba lần logo bitchat/ đang tắt — chỉ nút này mới xóa." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有消息、密钥和身份。bitchat/ 标志三连点已关闭——只有此按钮会清除。" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "清除所有訊息、金鑰與身分。bitchat/ 標誌三連點已關閉——只有此按鈕會清除。" + } + } + } + }, + "app_info.settings.danger.title" : { "extractionState" : "manual", "localizations" : { diff --git a/bitchat/Services/PanicWipeSettings.swift b/bitchat/Services/PanicWipeSettings.swift index eead4526..fc38706f 100644 --- a/bitchat/Services/PanicWipeSettings.swift +++ b/bitchat/Services/PanicWipeSettings.swift @@ -8,33 +8,59 @@ import Foundation -/// Controls whether the undiscoverable `bitchat/` logo triple-tap asks before -/// wiping. +/// Controls how the undiscoverable `bitchat/` logo triple-tap behaves. /// /// The shortcut exists for the seizure path — someone taking the phone out of -/// your hand — so it defaults to an instant wipe. People who fear accidental -/// taps can opt into a confirmation step; that trade-off costs the seconds the -/// feature is built around. +/// your hand — so it defaults to an **instant** wipe. People who fear +/// accidental taps can opt into a confirmation step, or turn the gesture off +/// entirely (the settings panic button still wipes after its own confirm). +/// +/// One type, one `reset()`, one danger-zone control — do not split this across +/// parallel settings enums. enum PanicWipeSettings { - private static let confirmLogoShortcutKey = "panic.confirmLogoShortcut" + private static let modeKey = "panic.logoShortcutMode" + /// Legacy keys from the confirm-only (#1509) and enable-only (#1575) shapes. + private static let legacyConfirmKey = "panic.confirmLogoShortcut" + private static let legacyEnabledKey = "panic.logoShortcutEnabled" - /// When `true`, triple-tapping the logo shows the same confirmation dialog - /// as the settings panic button. Defaults to `false` (instant wipe). - static var confirmLogoShortcut: Bool { - get { confirmLogoShortcut(in: .standard) } - set { setConfirmLogoShortcut(newValue, in: .standard) } + enum LogoShortcutMode: String, CaseIterable, Identifiable { + case instant + case confirm + case off + + var id: String { rawValue } } - static func confirmLogoShortcut(in defaults: UserDefaults) -> Bool { - defaults.object(forKey: confirmLogoShortcutKey) as? Bool ?? false + static var logoShortcutMode: LogoShortcutMode { + get { logoShortcutMode(in: .standard) } + set { setLogoShortcutMode(newValue, in: .standard) } } - static func setConfirmLogoShortcut(_ confirm: Bool, in defaults: UserDefaults) { - defaults.set(confirm, forKey: confirmLogoShortcutKey) + static func logoShortcutMode(in defaults: UserDefaults) -> LogoShortcutMode { + if let raw = defaults.string(forKey: modeKey), + let mode = LogoShortcutMode(rawValue: raw) { + return mode + } + // Migrate older single-boolean shapes without losing the person's choice. + if defaults.object(forKey: legacyEnabledKey) as? Bool == false { + return .off + } + if defaults.object(forKey: legacyConfirmKey) as? Bool == true { + return .confirm + } + return .instant } - /// Panic-wipe hook. Removing the key restores the instant default. + static func setLogoShortcutMode(_ mode: LogoShortcutMode, in defaults: UserDefaults) { + defaults.set(mode.rawValue, forKey: modeKey) + defaults.removeObject(forKey: legacyConfirmKey) + defaults.removeObject(forKey: legacyEnabledKey) + } + + /// Panic-wipe hook. Removing the keys restores the instant default. static func reset(in defaults: UserDefaults = .standard) { - defaults.removeObject(forKey: confirmLogoShortcutKey) + defaults.removeObject(forKey: modeKey) + defaults.removeObject(forKey: legacyConfirmKey) + defaults.removeObject(forKey: legacyEnabledKey) } } diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift index 759d066c..89e5f6da 100644 --- a/bitchat/Views/AppInfoView.swift +++ b/bitchat/Views/AppInfoView.swift @@ -22,7 +22,9 @@ struct AppInfoView: View { @State private var liveVoiceEnabled = PTTSettings.liveVoiceEnabled @State private var locationNotesEnabled = LocationNotesSettings.enabled @State private var hideMessagePreviews = NotificationPrivacySettings.hideMessagePreviews - @State private var confirmLogoPanic = PanicWipeSettings.confirmLogoShortcut + @State private var logoShortcutMode = PanicWipeSettings.logoShortcutMode + @State private var pendingLogoShortcutMode: PanicWipeSettings.LogoShortcutMode? + @State private var showDisableLogoShortcutConfirm = false @State private var customRelays = NostrRelaySettings.customRelays() @State private var relayInput = "" @State private var relayError: String? @@ -121,10 +123,16 @@ 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 when logo confirmation is off (default)") - static let panicNoteConfirm = String(localized: "app_info.settings.danger.panic_note_confirm", defaultValue: "erases all messages, keys, and identity. triple-tapping the bitchat/ logo asks for the same confirmation.", comment: "Caption under the panic wipe button when confirm-before-logo-wipe is on") - static let confirmLogoTitle = String(localized: "app_info.settings.danger.confirm_logo.title", defaultValue: "confirm before logo wipe", comment: "Title of the setting that requires confirmation before the triple-tap logo panic wipe") - static let confirmLogoSubtitle = String(localized: "app_info.settings.danger.confirm_logo.subtitle", defaultValue: "off by default: triple-tap wipes instantly so it still works if someone grabs the phone. turn on only if accidental taps worry you more than losing those seconds.", comment: "Subtitle explaining the seizure-path trade-off of confirming before the logo panic wipe") + static let panicNoteInstant = 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 when logo gesture is instant (default)") + static let panicNoteConfirm = String(localized: "app_info.settings.danger.panic_note_confirm", defaultValue: "erases all messages, keys, and identity. triple-tapping the bitchat/ logo asks for the same confirmation.", comment: "Caption under the panic wipe button when logo gesture confirms first") + static let panicNoteDisabled = String(localized: "app_info.settings.danger.panic_note_disabled", defaultValue: "erases all messages, keys, and identity. the bitchat/ logo triple-tap is off — only this button wipes.", comment: "Caption under the panic wipe button when logo gesture is disabled") + static let logoModeTitle = String(localized: "app_info.settings.danger.logo_mode.title", defaultValue: "logo triple-tap wipe", comment: "Title of the setting that chooses instant / confirm / off for the logo panic gesture") + static let logoModeSubtitle = String(localized: "app_info.settings.danger.logo_mode.subtitle", defaultValue: "instant by default for the seizure path. confirm if accidental taps worry you. off removes the gesture — turning off asks for confirmation because a disabled triple-tap is a silent failure in a panic.", comment: "Subtitle explaining the three logo panic gesture modes") + static let logoModeInstant = String(localized: "app_info.settings.danger.logo_mode.instant", defaultValue: "instant", comment: "Logo panic mode: wipe immediately on triple-tap") + static let logoModeConfirm = String(localized: "app_info.settings.danger.logo_mode.confirm", defaultValue: "confirm first", comment: "Logo panic mode: ask before wiping on triple-tap") + static let logoModeOff = String(localized: "app_info.settings.danger.logo_mode.off", defaultValue: "off", comment: "Logo panic mode: disable the triple-tap gesture") + static let disableLogoConfirmTitle = String(localized: "app_info.settings.danger.logo_disable_confirm_title", defaultValue: "turn off logo wipe?", comment: "Title asking to confirm disabling the logo triple-tap panic gesture") + static let disableLogoConfirmAction = String(localized: "app_info.settings.danger.logo_disable_confirm_action", defaultValue: "turn off", comment: "Confirm button that disables the logo triple-tap panic gesture") 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") } @@ -573,23 +581,47 @@ struct AppInfoView: View { Button("common.cancel", role: .cancel) {} } - Text(confirmLogoPanic ? Strings.Settings.panicNoteConfirm : Strings.Settings.panicNote) + Text(logoShortcutPanicNote) .bitchatFont(size: 11) .foregroundColor(secondaryTextColor) .fixedSize(horizontal: false, vertical: true) settingsCard { - settingToggle( - title: Text(verbatim: Strings.Settings.confirmLogoTitle), - subtitle: Text(verbatim: Strings.Settings.confirmLogoSubtitle), - isOn: Binding( - get: { confirmLogoPanic }, - set: { newValue in - confirmLogoPanic = newValue - PanicWipeSettings.confirmLogoShortcut = newValue - } - ) - ) + VStack(alignment: .leading, spacing: 8) { + Text(verbatim: Strings.Settings.logoModeTitle) + .bitchatFont(size: 12) + .foregroundColor(palette.primary) + Text(verbatim: Strings.Settings.logoModeSubtitle) + .bitchatFont(size: 11) + .foregroundColor(secondaryTextColor) + .fixedSize(horizontal: false, vertical: true) + Picker( + selection: Binding( + get: { logoShortcutMode }, + set: { requestLogoShortcutMode($0) } + ) + ) { + Text(Strings.Settings.logoModeInstant).tag(PanicWipeSettings.LogoShortcutMode.instant) + Text(Strings.Settings.logoModeConfirm).tag(PanicWipeSettings.LogoShortcutMode.confirm) + Text(Strings.Settings.logoModeOff).tag(PanicWipeSettings.LogoShortcutMode.off) + } label: { + EmptyView() + } + .pickerStyle(.segmented) + .labelsHidden() + } + } + .confirmationDialog( + Strings.Settings.disableLogoConfirmTitle, + isPresented: $showDisableLogoShortcutConfirm, + titleVisibility: .visible + ) { + Button(Strings.Settings.disableLogoConfirmAction, role: .destructive) { + applyLogoShortcutMode(.off) + } + Button("common.cancel", role: .cancel) { + pendingLogoShortcutMode = nil + } } } } @@ -597,6 +629,29 @@ struct AppInfoView: View { .padding() } + private var logoShortcutPanicNote: String { + switch logoShortcutMode { + case .instant: return Strings.Settings.panicNoteInstant + case .confirm: return Strings.Settings.panicNoteConfirm + case .off: return Strings.Settings.panicNoteDisabled + } + } + + private func requestLogoShortcutMode(_ mode: PanicWipeSettings.LogoShortcutMode) { + if mode == .off, logoShortcutMode != .off { + pendingLogoShortcutMode = mode + showDisableLogoShortcutConfirm = true + return + } + applyLogoShortcutMode(mode) + } + + private func applyLogoShortcutMode(_ mode: PanicWipeSettings.LogoShortcutMode) { + logoShortcutMode = mode + PanicWipeSettings.logoShortcutMode = mode + pendingLogoShortcutMode = nil + } + private func selectLanguage(_ code: String?) { let previous = languageOverride AppLanguageSettings.setOverride(code) diff --git a/bitchat/Views/ContentHeaderView.swift b/bitchat/Views/ContentHeaderView.swift index d3936990..79080d49 100644 --- a/bitchat/Views/ContentHeaderView.swift +++ b/bitchat/Views/ContentHeaderView.swift @@ -59,7 +59,7 @@ struct ContentHeaderView: View { // 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 — and - // wipes instantly unless confirm-before-logo-wipe is on.) + // follows PanicWipeSettings: instant / confirm / off.) .accessibilityAddTraits(.isButton) .accessibilityHint( String(localized: "content.accessibility.app_info_hint", comment: "Accessibility hint on the bitchat/ logo explaining a tap opens app info") @@ -82,6 +82,24 @@ struct ContentHeaderView: View { appChromeModel.cancelPanicWipe() } } + .alert( + String( + localized: "app_info.settings.danger.logo_disabled_title", + defaultValue: "logo wipe is off", + comment: "Title of the alert when triple-tapping the logo while the gesture is disabled" + ), + isPresented: $appChromeModel.showPanicGestureDisabledAlert + ) { + Button("common.ok", role: .cancel) { + appChromeModel.acknowledgePanicGestureDisabled() + } + } message: { + Text(String( + localized: "app_info.settings.danger.logo_disabled_message", + defaultValue: "nothing was wiped. turn the logo gesture back on in settings, or use the panic wipe button there.", + comment: "Message explaining that the logo triple-tap did nothing because it is disabled" + )) + } HStack(spacing: 0) { Text(verbatim: "@") diff --git a/bitchatTests/Services/PanicWipeSettingsTests.swift b/bitchatTests/Services/PanicWipeSettingsTests.swift index 0269b0ce..c14f9495 100644 --- a/bitchatTests/Services/PanicWipeSettingsTests.swift +++ b/bitchatTests/Services/PanicWipeSettingsTests.swift @@ -3,25 +3,49 @@ import Foundation @testable import bitchat struct PanicWipeSettingsTests { - private func isolatedDefaults() -> UserDefaults { - UserDefaults(suiteName: "PanicWipeSettingsTests.\(UUID().uuidString)")! + private func isolatedDefaults() -> (suite: String, defaults: UserDefaults) { + let suite = "PanicWipeSettingsTests.\(UUID().uuidString)" + return (suite, UserDefaults(suiteName: suite)!) + } + + private func cleanup(_ suite: String) { + UserDefaults().removePersistentDomain(forName: suite) } @Test func defaultsToInstantWipe() { - let defaults = isolatedDefaults() - #expect(!PanicWipeSettings.confirmLogoShortcut(in: defaults)) + let (suite, defaults) = isolatedDefaults() + defer { cleanup(suite) } + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .instant) } - @Test func persistsOptInConfirmation() { - let defaults = isolatedDefaults() - PanicWipeSettings.setConfirmLogoShortcut(true, in: defaults) - #expect(PanicWipeSettings.confirmLogoShortcut(in: defaults)) + @Test func persistsConfirmAndOffModes() { + let (suite, defaults) = isolatedDefaults() + defer { cleanup(suite) } + PanicWipeSettings.setLogoShortcutMode(.confirm, in: defaults) + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .confirm) + PanicWipeSettings.setLogoShortcutMode(.off, in: defaults) + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .off) } @Test func resetRestoresInstantDefault() { - let defaults = isolatedDefaults() - PanicWipeSettings.setConfirmLogoShortcut(true, in: defaults) + let (suite, defaults) = isolatedDefaults() + defer { cleanup(suite) } + PanicWipeSettings.setLogoShortcutMode(.off, in: defaults) PanicWipeSettings.reset(in: defaults) - #expect(!PanicWipeSettings.confirmLogoShortcut(in: defaults)) + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .instant) + } + + @Test func migratesLegacyConfirmFlag() { + let (suite, defaults) = isolatedDefaults() + defer { cleanup(suite) } + defaults.set(true, forKey: "panic.confirmLogoShortcut") + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .confirm) + } + + @Test func migratesLegacyDisabledFlag() { + let (suite, defaults) = isolatedDefaults() + defer { cleanup(suite) } + defaults.set(false, forKey: "panic.logoShortcutEnabled") + #expect(PanicWipeSettings.logoShortcutMode(in: defaults) == .off) } }