show verification code requested sheet in CVC

This commit is contained in:
kate-signal 2026-05-25 08:44:48 -04:00 committed by GitHub
parent e14f223e79
commit 7beb7330a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 116 additions and 61 deletions

View File

@ -68,6 +68,7 @@
044D77782E5E6D750048C21A /* PollVoteRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044D77772E5E6D700048C21A /* PollVoteRecord.swift */; };
044D84452E9FDDF00090BA64 /* BackupArchivePollTerminateChatUpdateArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044D84442E9FDDE30090BA64 /* BackupArchivePollTerminateChatUpdateArchiver.swift */; };
044D84472E9FEE010090BA64 /* BackupArchivePollArchiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044D84462E9FEDE20090BA64 /* BackupArchivePollArchiver.swift */; };
044EA2232FC0EFAE005B5A3E /* SafetyTipsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044EA2222FC0EFA6005B5A3E /* SafetyTipsSheet.swift */; };
045B40892EC67510002D3F9A /* PinnedMessageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045B40882EC6750A002D3F9A /* PinnedMessageManager.swift */; };
045B408C2EC67C03002D3F9A /* PinnedMessageRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045B408B2EC67BFF002D3F9A /* PinnedMessageRecord.swift */; };
045B408E2EC6897B002D3F9A /* PinnedMessageManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 045B408D2EC68974002D3F9A /* PinnedMessageManagerTest.swift */; };
@ -4219,6 +4220,7 @@
044D77772E5E6D700048C21A /* PollVoteRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteRecord.swift; sourceTree = "<group>"; };
044D84442E9FDDE30090BA64 /* BackupArchivePollTerminateChatUpdateArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupArchivePollTerminateChatUpdateArchiver.swift; sourceTree = "<group>"; };
044D84462E9FEDE20090BA64 /* BackupArchivePollArchiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupArchivePollArchiver.swift; sourceTree = "<group>"; };
044EA2222FC0EFA6005B5A3E /* SafetyTipsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafetyTipsSheet.swift; sourceTree = "<group>"; };
045B40882EC6750A002D3F9A /* PinnedMessageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessageManager.swift; sourceTree = "<group>"; };
045B408B2EC67BFF002D3F9A /* PinnedMessageRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessageRecord.swift; sourceTree = "<group>"; };
045B408D2EC68974002D3F9A /* PinnedMessageManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedMessageManagerTest.swift; sourceTree = "<group>"; };
@ -11324,6 +11326,7 @@
0550A5E12C4035170072CC02 /* CLVViewInfo.swift */,
34E95C1F269F4F4F004807EC /* CLVViewState.swift */,
4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */,
044EA2222FC0EFA6005B5A3E /* SafetyTipsSheet.swift */,
1477630A275E20D700D1067E /* ThreadContextualActionProvider.swift */,
);
path = "Chat List";
@ -18730,6 +18733,7 @@
348EE28F25B897BF00814FC2 /* ReusableMediaView.swift in Sources */,
66A22C0928A18D49007CD4F5 /* RingerSwitch.swift in Sources */,
D9E43C2C2CC194140001536E /* RTCIceServerFetcher.swift in Sources */,
044EA2232FC0EFAE005B5A3E /* SafetyTipsSheet.swift in Sources */,
C1D9B1552B7FA28200D94595 /* SafetyTipsViewController.swift in Sources */,
7677E41329F84C2100AC6A75 /* ScreenLockUI.swift in Sources */,
667EDE6428F8D6B7001FB487 /* SDAnimatedImage+Duration.swift in Sources */,

View File

@ -78,6 +78,14 @@ extension ConversationViewController {
object: AVAudioSession.sharedInstance(),
)
NotificationCenter.default.addObserver(
self,
selector: #selector(smsVerificationCodeRequested),
name: .smsVerificationCodeRequested,
object: nil,
)
SafetyTipsManager.startObservingDarwinNotifications()
AppEnvironment.shared.callService.callServiceState.addObserver(self, syncStateImmediately: false)
}
@ -203,6 +211,28 @@ extension ConversationViewController {
AssertIsOnMainThread()
ensureBottomViewType()
}
@objc
private func smsVerificationCodeRequested(_ notification: NSNotification) {
AssertIsOnMainThread()
let db = DependenciesBridge.shared.db
let safetyTipsManager = SafetyTipsManager()
let timestamp: UInt64? = db.read { tx in
safetyTipsManager.lastVerificationCodeTimestampMsWithinExpiryTime(transaction: tx)
}
guard let timestamp else { return }
let actionSheetController = SafetyTipsSheet.makeSmsCodeRequestedSheet(
timestampMs: timestamp,
fromViewController: self,
)
present(actionSheetController, animated: true, completion: {
db.write { tx in
safetyTipsManager.removeVerificationCodeRequestedTimestampMs(transaction: tx)
}
})
}
}
// MARK: -

View File

@ -65,6 +65,7 @@ class ChatListFYISheetCoordinator {
private let keyTransparencyStore: KeyTransparencyStore
private let networkManager: NetworkManager
private let profileManager: ProfileManager
private let safetyTipsManager: SafetyTipsManager
init(
backupArchiveErrorStore: BackupArchiveErrorStore,
@ -86,6 +87,7 @@ class ChatListFYISheetCoordinator {
self.keyTransparencyStore = keyTransparencyStore
self.networkManager = networkManager
self.profileManager = profileManager
self.safetyTipsManager = SafetyTipsManager()
}
func presentIfNecessary(
@ -150,8 +152,7 @@ class ChatListFYISheetCoordinator {
tx: DBReadTransaction,
) -> FYISheet? {
let safetyTipsKVStore = SafetyTipsManager()
guard let timestamp = safetyTipsKVStore.lastVerificationCodeTimestampMsWithinExpiryTime(transaction: tx) else {
guard let timestamp = safetyTipsManager.lastVerificationCodeTimestampMsWithinExpiryTime(transaction: tx) else {
return nil
}
@ -517,67 +518,13 @@ class ChatListFYISheetCoordinator {
smsVerificationCodeSent: FYISheet.SMSVerificationCodeSent,
from chatListViewController: ChatListViewController,
) async {
let timestampString = DateUtil.formatMessageTimestampForCVC(
smsVerificationCodeSent.timestampMs,
shouldUseLongFormat: true,
let actionSheetController = SafetyTipsSheet.makeSmsCodeRequestedSheet(
timestampMs: smsVerificationCodeSent.timestampMs,
fromViewController: chatListViewController,
)
let bodyPartOne = OWSLocalizedString(
"VERIFICATION_CODE_REQUESTED_HERO_BODY_FIRST",
comment: "First part of body for a hero sheet informing the user a verification code was requested. {{ Embeds time the code was requested }}",
)
let bodyPartTwo = OWSLocalizedString(
"VERIFICATION_CODE_REQUESTED_HERO_BODY_SECOND",
comment: "Second part of body for a hero sheet informing the user a verification code was requested.",
)
let body: NSAttributedString = .composed(of: [
bodyPartOne.styled(
with: .font(.dynamicTypeHeadline),
.color(UIColor.Signal.label),
.paragraphSpacingAfter(4.0),
),
"\n",
timestampString.styled(
with: .font(.dynamicTypeBody),
.color(UIColor.Signal.label),
),
"\n",
bodyPartTwo.styled(
with: .font(.dynamicTypeBody),
.color(UIColor.Signal.label),
.paragraphSpacingBefore(12.0),
),
])
let actionSheet = ActionSheetController(
message: body,
image: UIImage(resource: .verificationcodeAlert96),
)
actionSheet.addAction(ActionSheetAction(
title: OWSLocalizedString(
"SAFETY_TIPS_BUTTON_ACTION_TITLE",
comment: "Title for Safety Tips button in thread details.",
),
handler: { [weak chatListViewController] _ in
guard let chatListViewController else { return }
let safetyTipsVC = SafetyTipsViewController(
primaryButton: SafetyTipsViewController.Button(
title: OWSLocalizedString(
"SETTINGS_ACCOUNT_BUTTON",
comment: "Label for button in Safety Tips to go to 'account' page in settings.",
),
action: { [weak chatListViewController] in
chatListViewController?.showAppSettings(mode: .accountSettings)
},
),
)
chatListViewController.present(safetyTipsVC, animated: true)
},
))
actionSheet.addAction(.ok)
chatListViewController.present(actionSheet, animated: true, completion: { [self] in
let kvStore = SafetyTipsManager()
chatListViewController.present(actionSheetController, animated: true, completion: { [self] in
db.write { tx in
kvStore.removeVerificationCodeRequestedTimestampMs(transaction: tx)
safetyTipsManager.removeVerificationCodeRequestedTimestampMs(transaction: tx)
}
})
}

View File

@ -0,0 +1,69 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
enum SafetyTipsSheet {
static func makeSmsCodeRequestedSheet(timestampMs: UInt64, fromViewController: UIViewController) -> ActionSheetController {
let timestampString = DateUtil.formatMessageTimestampForCVC(
timestampMs,
shouldUseLongFormat: true,
)
let bodyPartOne = OWSLocalizedString(
"VERIFICATION_CODE_REQUESTED_HERO_BODY_FIRST",
comment: "First part of body for a hero sheet informing the user a verification code was requested. {{ Embeds time the code was requested }}",
)
let bodyPartTwo = OWSLocalizedString(
"VERIFICATION_CODE_REQUESTED_HERO_BODY_SECOND",
comment: "Second part of body for a hero sheet informing the user a verification code was requested.",
)
let body: NSAttributedString = .composed(of: [
bodyPartOne.styled(
with: .font(.dynamicTypeHeadline),
.color(UIColor.Signal.label),
.paragraphSpacingAfter(4.0),
),
"\n",
timestampString.styled(
with: .font(.dynamicTypeBody),
.color(UIColor.Signal.label),
),
"\n",
bodyPartTwo.styled(
with: .font(.dynamicTypeBody),
.color(UIColor.Signal.label),
.paragraphSpacingBefore(12.0),
),
])
let actionSheet = ActionSheetController(
message: body,
image: UIImage(resource: .verificationcodeAlert96),
)
actionSheet.addAction(ActionSheetAction(
title: OWSLocalizedString(
"SAFETY_TIPS_BUTTON_ACTION_TITLE",
comment: "Title for Safety Tips button in thread details.",
),
handler: { [weak fromViewController] _ in
let safetyTipsVC = SafetyTipsViewController(
primaryButton: SafetyTipsViewController.Button(
title: OWSLocalizedString(
"SETTINGS_ACCOUNT_BUTTON",
comment: "Label for button in Safety Tips to go to 'account' page in settings.",
),
action: {
SignalApp.shared.showAppSettings(mode: .accountSettings)
},
),
)
fromViewController?.present(safetyTipsVC, animated: true)
},
))
actionSheet.addAction(.ok)
return actionSheet
}
}

View File

@ -146,6 +146,11 @@ class NotificationService: UNNotificationServiceExtension {
if let timestamp = verificationCodeRequestTimestampMs(userInfo: request.content.userInfo) {
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { transaction in
guard DependenciesBridge.shared.tsAccountManager.registrationState(tx: transaction).isPrimaryDevice == true else {
Logger.info("Received verification code push on non-primary device; ignoring.")
return
}
let kvStore = SafetyTipsManager()
kvStore.setLastVerificationCodeRequestedTimestampMs(value: timestamp, transaction: transaction)
}