From 3f85bb454f27865eef16f8211d904f6dcabfb789 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 11:37:13 +0300 Subject: [PATCH] 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)) + } +}