From a978b4cc8b3190a5601ecc363c18a29ab5e876c8 Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Thu, 4 Jun 2026 11:29:34 -0700 Subject: [PATCH] Add warning sheet when copying Recovery Key --- .../BackupRecordKeyViewController.swift | 49 +++++++- .../error-triangle.imageset/Contents.json | 16 +++ .../error-triangle.pdf | 107 ++++++++++++++++++ .../translations/en.lproj/Localizable.strings | 9 ++ SignalUI/Utils/URL+Support.swift | 1 + 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Signal/Symbols.xcassets/error/error-triangle.imageset/Contents.json create mode 100644 Signal/Symbols.xcassets/error/error-triangle.imageset/error-triangle.pdf diff --git a/Signal/Backups/BackupRecordKeyViewController.swift b/Signal/Backups/BackupRecordKeyViewController.swift index e87b0676fa..d6d64392cd 100644 --- a/Signal/Backups/BackupRecordKeyViewController.swift +++ b/Signal/Backups/BackupRecordKeyViewController.swift @@ -115,7 +115,7 @@ class BackupRecordKeyViewController: OWSViewController, OWSNavigationChildContro comment: "Title for a button allowing users to copy their 'Recovery Key' to the clipboard.", )), primaryAction: UIAction { [weak self] _ in - self?.copyToClipboard() + self?.copyToClipboardWithConfirmation() }, ), ] @@ -175,6 +175,53 @@ class BackupRecordKeyViewController: OWSViewController, OWSNavigationChildContro stackView.setCustomSpacing(32, after: aepTextView) } + private func copyToClipboardWithConfirmation() { + let bodyText: NSAttributedString = OWSLocalizedString( + "BACKUP_RECORD_KEY_COPY_WARNING_SHEET_BODY", + comment: "Body for a warning sheet shown before copying the user's 'Recovery Key' to the clipboard, warning them not to share it with anyone.", + ).styled( + with: .font(.dynamicTypeSubheadline), + .xmlRules([.style("bold", StringStyle(.font(.dynamicTypeSubheadline.bold())))]), + ) + + let warningSheet = HeroSheetViewController( + hero: .circleIcon( + icon: .errorTriangle, + iconSize: 40, + tintColor: .Signal.red, + backgroundColor: UIColor(rgbHex: 0xF8E0D9), + ), + title: OWSLocalizedString( + "BACKUP_RECORD_KEY_COPY_WARNING_SHEET_TITLE", + comment: "Title for a warning sheet shown before copying the user's 'Recovery Key' to the clipboard.", + ), + body: HeroSheetViewController.Body( + textContent: .attributed(bodyText), + ), + primary: .button(HeroSheetViewController.Button( + title: OWSLocalizedString( + "BACKUP_RECORD_KEY_COPY_WARNING_SHEET_PRIMARY_BUTTON_TITLE", + comment: "Title for the primary button in a warning sheet shown before copying the user's 'Recovery Key' to the clipboard, which acknowledges the warning and proceeds with the copy.", + ), + action: { sheet in + sheet.dismiss(animated: true) { [weak self] in + guard let self else { return } + copyToClipboard() + } + }, + )), + secondary: .button(HeroSheetViewController.Button( + title: CommonStrings.learnMore, + style: .secondary, + action: .custom({ sheet in + UIApplication.shared.open(.Support.phishingPrevention) + }), + )), + ) + + present(warningSheet, animated: true) + } + private func copyToClipboard() { UIPasteboard.general.setItems( [[UIPasteboard.typeAutomatic: displayableAEP.displayString]], diff --git a/Signal/Symbols.xcassets/error/error-triangle.imageset/Contents.json b/Signal/Symbols.xcassets/error/error-triangle.imageset/Contents.json new file mode 100644 index 0000000000..7bb16d36ec --- /dev/null +++ b/Signal/Symbols.xcassets/error/error-triangle.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "error-triangle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Signal/Symbols.xcassets/error/error-triangle.imageset/error-triangle.pdf b/Signal/Symbols.xcassets/error/error-triangle.imageset/error-triangle.pdf new file mode 100644 index 0000000000..51dd723597 --- /dev/null +++ b/Signal/Symbols.xcassets/error/error-triangle.imageset/error-triangle.pdf @@ -0,0 +1,107 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.980103 15.426270 cm +0.000000 0.000000 0.000000 scn +11.019876 2.073738 m +10.324054 2.073738 9.775503 1.481371 9.828871 0.787598 c +10.252174 -4.715347 l +10.283031 -5.116498 10.617537 -5.426263 11.019876 -5.426263 c +11.422214 -5.426263 11.756720 -5.116498 11.787577 -4.715347 c +12.210880 0.787598 l +12.264248 1.481371 11.715697 2.073738 11.019876 2.073738 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.980103 20.426270 cm +0.000000 0.000000 0.000000 scn +9.769861 -12.926263 m +9.769861 -12.235908 10.329505 -11.676263 11.019861 -11.676263 c +11.710217 -11.676263 12.269861 -12.235908 12.269861 -12.926263 c +12.269861 -13.616619 11.710217 -14.176262 11.019861 -14.176262 c +10.329505 -14.176262 9.769861 -13.616619 9.769861 -12.926263 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 0.980103 2.568329 cm +0.000000 0.000000 0.000000 scn +8.107189 18.687922 m +9.410621 20.914618 12.629106 20.914612 13.932535 18.687920 c +21.572298 5.636655 l +22.889338 3.386715 21.266691 0.556679 18.659622 0.556679 c +3.380099 0.556679 l +0.773029 0.556679 -0.849612 3.386719 0.467425 5.636659 c +8.107189 18.687922 l +h +12.422260 17.803856 m +11.794682 18.875969 10.245042 18.875969 9.617465 17.803858 c +1.977700 4.752595 l +1.343571 3.669292 2.124843 2.306679 3.380099 2.306679 c +18.659622 2.306679 l +19.914881 2.306679 20.696152 3.669289 20.062023 4.752591 c +12.422260 17.803856 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1436 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001526 00000 n +0000001549 00000 n +0000001722 00000 n +0000001796 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1855 +%%EOF \ No newline at end of file diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0ef75cfa90..56b29ed784 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -652,6 +652,15 @@ /* Title for a button allowing users to copy their 'Recovery Key' to the clipboard. */ "BACKUP_RECORD_KEY_COPY_TO_CLIPBOARD_BUTTON_TITLE" = "Copy to Clipboard"; +/* Body for a warning sheet shown before copying the user's 'Recovery Key' to the clipboard, warning them not to share it with anyone. */ +"BACKUP_RECORD_KEY_COPY_WARNING_SHEET_BODY" = "Signal will never message you for your recovery key. Do not respond to a chat pretending to be Signal. Store your recovery key somewhere safe and do not share it with anyone."; + +/* Title for the primary button in a warning sheet shown before copying the user's 'Recovery Key' to the clipboard, which acknowledges the warning and proceeds with the copy. */ +"BACKUP_RECORD_KEY_COPY_WARNING_SHEET_PRIMARY_BUTTON_TITLE" = "Got It"; + +/* Title for a warning sheet shown before copying the user's 'Recovery Key' to the clipboard. */ +"BACKUP_RECORD_KEY_COPY_WARNING_SHEET_TITLE" = "Do Not Share Your Recovery Key"; + /* Title for a button allowing users to create a new 'Recovery Key'. */ "BACKUP_RECORD_KEY_CREATE_NEW_KEY_BUTTON_TITLE" = "Create New Key"; diff --git a/SignalUI/Utils/URL+Support.swift b/SignalUI/Utils/URL+Support.swift index 9f589557c6..fbe850209e 100644 --- a/SignalUI/Utils/URL+Support.swift +++ b/SignalUI/Utils/URL+Support.swift @@ -13,6 +13,7 @@ extension URL { public static let groups: URL = .supportArticle("360007319331") public static let inactivePrimaryDevice: URL = .supportArticle("9021007554074") public static let linkedDevices: URL = .supportArticle("360007320551") + public static let phishingPrevention: URL = .supportArticle("9932566320410") public static let pin: URL = .supportArticle("360007059792") public static let profilesAndMessageRequests: URL = .supportArticle("360007459591") public static let proxies: URL = .supportArticle("360056052052")