diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 6b9345dd06..a031a214a0 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -5446,6 +5446,9 @@ /* Title for the 'safety number confirmation' view */ "SAFETY_NUMBER_CONFIRMATION_TITLE" = "Safety Number Changes"; +/* Text explaining that the given contact has had their safety number verified. */ +"SAFETY_NUMBER_CONFIRMATION_VERIFIED" = "Verified"; + /* View safety number action for the 'safety number confirmation' view */ "SAFETY_NUMBER_CONFIRMATION_VIEW_ACTION" = "View"; diff --git a/SignalUI/ViewControllers/SafetyNumberConfirmationSheet.swift b/SignalUI/ViewControllers/SafetyNumberConfirmationSheet.swift index be65664d81..c49f781a4a 100644 --- a/SignalUI/ViewControllers/SafetyNumberConfirmationSheet.swift +++ b/SignalUI/ViewControllers/SafetyNumberConfirmationSheet.swift @@ -17,10 +17,12 @@ public class SafetyNumberConfirmationSheet: UIViewController { struct Item { let address: SignalServiceAddress - let displayName: String? - let verificationState: OWSVerificationState? + let displayName: String + let verificationState: OWSVerificationState } - var items = [Item]() + + private var confirmationItems: [Item] + let confirmAction: ActionSheetAction let cancelAction: ActionSheetAction let completionHandler: (Bool) -> Void @@ -34,36 +36,82 @@ public class SafetyNumberConfirmationSheet: UIViewController { self.init(addressesToConfirm: addresses, confirmationText: confirmationText, completionHandler: completionHandler) } - public init(addressesToConfirm addresses: [SignalServiceAddress], - confirmationText: String, - cancelText: String = CommonStrings.cancelButton, - theme: Theme.ActionSheet = .translucentDark, - completionHandler: @escaping (Bool) -> Void) { + public init( + addressesToConfirm: [SignalServiceAddress], + confirmationText: String, + cancelText: String = CommonStrings.cancelButton, + theme: Theme.ActionSheet = .translucentDark, + completionHandler: @escaping (Bool) -> Void + ) { + assert(!addressesToConfirm.isEmpty) + + self.confirmationItems = Self.databaseStorage.read { transaction in + Self.buildConfirmationItems( + addressesToConfirm: addressesToConfirm, + transaction: transaction + ) + } - assert(!addresses.isEmpty) self.confirmAction = ActionSheetAction(title: confirmationText, style: .default) self.cancelAction = ActionSheetAction(title: cancelText, style: .cancel) self.completionHandler = completionHandler self.theme = theme + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .custom transitioningDelegate = self - SDSDatabaseStorage.shared.read { transaction in - self.items = addresses.map { - return Item( - address: $0, - displayName: Self.contactsManager.displayName(for: $0, transaction: transaction), - verificationState: Self.identityManager.verificationState(for: $0, transaction: transaction) - ) - } - } + observeIdentityChangeNotification() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + fileprivate static func buildConfirmationItems( + addressesToConfirm: [SignalServiceAddress], + transaction: SDSAnyReadTransaction + ) -> [Item] { + addressesToConfirm.map { address in + return Item( + address: address, + displayName: contactsManager.displayName(for: address, transaction: transaction), + verificationState: identityManager.verificationState(for: address, transaction: transaction) + ) + } + } + + // MARK: - Identity change notification + + private func observeIdentityChangeNotification() { + NotificationCenter.default.addObserver( + self, + selector: #selector(identityStateDidChange), + name: .identityStateDidChange, + object: nil + ) + } + + /// Rebuild our confirmation items and reload the table, to ensure we + /// reflect the latest identity state after the user may have verified + /// one of the addresses we presented. + @objc + private func identityStateDidChange() { + databaseStorage.read { transaction in + let addressesToConfirm = confirmationItems.map { $0.address } + + confirmationItems = Self.buildConfirmationItems( + addressesToConfirm: addressesToConfirm, + transaction: transaction + ) + } + + tableView.reloadData() + } + + // MARK: - Present if necessary + @objc public class func presentIfNecessary(address: SignalServiceAddress, confirmationText: String, completion: @escaping (Bool) -> Void) -> Bool { return presentIfNecessary(addresses: [address], confirmationText: confirmationText, completion: completion) @@ -186,7 +234,7 @@ public class SafetyNumberConfirmationSheet: UIViewController { confirmAction.button.releaseAction = { [weak self] in guard let self = self else { return } let identityManager = self.identityManager - let unconfirmedAddresses = self.items.map { $0.address } + let unconfirmedAddresses = self.confirmationItems.map { $0.address } self.databaseStorage.asyncWrite(block: { writeTx in for address in unconfirmedAddresses { @@ -259,14 +307,14 @@ public class SafetyNumberConfirmationSheet: UIViewController { var minimizedHeight: CGFloat { // We want to show, at most, 3.5 rows when minimized. When we have // less than 4 rows, we will match our size to the number of rows. - let compactTableViewHeight = min(CGFloat(items.count), 3.5) * cellHeight + let compactTableViewHeight = min(CGFloat(confirmationItems.count), 3.5) * cellHeight let preferredMinimizedHeight = baseStackViewHeight + compactTableViewHeight + view.safeAreaInsets.bottom return min(maximizedHeight, preferredMinimizedHeight) } var maximizedHeight: CGFloat { - let tableViewHeight = CGFloat(items.count) * cellHeight + let tableViewHeight = CGFloat(confirmationItems.count) * cellHeight let preferredMaximizedHeight = baseStackViewHeight + tableViewHeight + view.safeAreaInsets.bottom let maxPermittedHeight = CurrentAppContext().frame.height - view.safeAreaInsets.top - 16 @@ -479,7 +527,7 @@ extension SafetyNumberConfirmationSheet: UITableViewDelegate, UITableViewDataSou } public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return items.count + return confirmationItems.count } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -490,7 +538,7 @@ extension SafetyNumberConfirmationSheet: UITableViewDelegate, UITableViewDataSou return cell } - guard let item = items[safe: indexPath.row] else { + guard let item = confirmationItems[safe: indexPath.row] else { return cell } @@ -546,19 +594,22 @@ private class SafetyNumberCell: ContactTableViewCell { configuration.accessoryView = ContactCellAccessoryView(accessoryView: buttonWrapper, size: buttonSize) - if let verificationState = item.verificationState, verificationState == .noLongerVerified { - let previouslyVerified = NSMutableAttributedString() - previouslyVerified.appendTemplatedImage(named: "check-12", font: UIFont.ows_regularFont(withSize: 11)) - previouslyVerified.append(" ") - previouslyVerified.append( - OWSLocalizedString("SAFETY_NUMBER_CONFIRMATION_PREVIOUSLY_VERIFIED", - comment: "Text explaining that the given contact previously had their safety number verified.") - ) - configuration.attributedSubtitle = previouslyVerified - } else if let displayName = item.displayName { + switch item.verificationState { + case .noLongerVerified: + configuration.attributedSubtitle = .prefixedWithCheck(text: OWSLocalizedString( + "SAFETY_NUMBER_CONFIRMATION_PREVIOUSLY_VERIFIED", + comment: "Text explaining that the given contact previously had their safety number verified." + )) + case .verified: + configuration.attributedSubtitle = .prefixedWithCheck(text: OWSLocalizedString( + "SAFETY_NUMBER_CONFIRMATION_VERIFIED", + comment: "Text explaining that the given contact has had their safety number verified." + )) + case .`default`: if let phoneNumber = item.address.phoneNumber { let formattedPhoneNumber = PhoneNumber.bestEffortLocalizedPhoneNumber(withE164: phoneNumber) - if displayName != formattedPhoneNumber { + + if item.displayName != formattedPhoneNumber { configuration.attributedSubtitle = NSAttributedString(string: formattedPhoneNumber) } } @@ -578,6 +629,20 @@ private class SafetyNumberCell: ContactTableViewCell { } } +private extension NSAttributedString { + static func prefixedWithCheck( + text: String + ) -> NSAttributedString { + let string = NSMutableAttributedString() + + string.appendTemplatedImage(named: "check-12", font: UIFont.ows_regularFont(withSize: 11)) + string.append(" ") + string.append(text) + + return string + } +} + // MARK: - extension SafetyNumberConfirmationSheet: UIGestureRecognizerDelegate { public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {