Consolidate code to decline message requests
This commit is contained in:
parent
d4fa2a3ec6
commit
d474e2a505
@ -847,6 +847,7 @@
|
||||
50D5E2432980B53000899660 /* LinkValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D5E2422980B53000899660 /* LinkValidatorTest.swift */; };
|
||||
50D6A93F2AA9167400B7F093 /* UniqueObjectRecipientMerger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D6A93E2AA9167400B7F093 /* UniqueObjectRecipientMerger.swift */; };
|
||||
50D6BDEF2ED6724600CC012E /* DeviceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D6BDEE2ED6724600CC012E /* DeviceType.swift */; };
|
||||
50D839512F916A3700EE009A /* MessageRequestDecliner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D839502F916A3700EE009A /* MessageRequestDecliner.swift */; };
|
||||
50D8796A2A16D2C20031345D /* MessageLoaderBatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */; };
|
||||
50D9CD8D2C52D78000273D6C /* StoryRecipientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */; };
|
||||
50DCCBFA2F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */; };
|
||||
@ -5121,6 +5122,7 @@
|
||||
50D5E2422980B53000899660 /* LinkValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkValidatorTest.swift; sourceTree = "<group>"; };
|
||||
50D6A93E2AA9167400B7F093 /* UniqueObjectRecipientMerger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniqueObjectRecipientMerger.swift; sourceTree = "<group>"; };
|
||||
50D6BDEE2ED6724600CC012E /* DeviceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceType.swift; sourceTree = "<group>"; };
|
||||
50D839502F916A3700EE009A /* MessageRequestDecliner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestDecliner.swift; sourceTree = "<group>"; };
|
||||
50D879692A16D2C20031345D /* MessageLoaderBatchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLoaderBatchTest.swift; sourceTree = "<group>"; };
|
||||
50D9CD8C2C52D78000273D6C /* StoryRecipientManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryRecipientManager.swift; sourceTree = "<group>"; };
|
||||
50DCCBF92F1817280024D124 /* DisappearingMessagesConfigurationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisappearingMessagesConfigurationMessage.swift; sourceTree = "<group>"; };
|
||||
@ -8885,6 +8887,7 @@
|
||||
34A95517271B510400B05242 /* LinkPreviewGroupLink.swift */,
|
||||
346EAA13250199A300E8AB6F /* MemberRequestView.swift */,
|
||||
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
|
||||
50D839502F916A3700EE009A /* MessageRequestDecliner.swift */,
|
||||
88D1D40122EBB5A100F472C5 /* MessageRequestView.swift */,
|
||||
50BD86AE2A3CFF89005B6AC1 /* ResendMessagePromptBuilder.swift */,
|
||||
88C980D327F3AD2C009750C0 /* TSInteraction+DeleteActionSheet.swift */,
|
||||
@ -18574,6 +18577,7 @@
|
||||
66CDB7522AF9D117009A36EC /* MessageFetchBGRefreshTask.swift in Sources */,
|
||||
34DE9C02256575300080E4AF /* MessageLoader.swift in Sources */,
|
||||
881218F0238CA51600E6F271 /* MessageReactionPicker.swift in Sources */,
|
||||
50D839512F916A3700EE009A /* MessageRequestDecliner.swift in Sources */,
|
||||
88D1D40222EBB5A100F472C5 /* MessageRequestView.swift in Sources */,
|
||||
34EB0E722629DC2B00B62DC3 /* MessageSelectionView.swift in Sources */,
|
||||
34EB0CEB26289D8800B62DC3 /* MessageTimerView.swift in Sources */,
|
||||
|
||||
@ -129,87 +129,20 @@ extension ConversationViewController: MessageRequestDelegate {
|
||||
}
|
||||
|
||||
private extension ConversationViewController {
|
||||
func blockThread() {
|
||||
// Leave the group while blocking the thread.
|
||||
SSKEnvironment.shared.databaseStorageRef.write { transaction in
|
||||
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(
|
||||
thread,
|
||||
blockMode: .local,
|
||||
shouldLeaveIfGroup: true,
|
||||
transaction: transaction,
|
||||
)
|
||||
SSKEnvironment.shared.syncManagerRef.sendMessageRequestResponseSyncMessage(
|
||||
thread: thread,
|
||||
responseType: .block,
|
||||
transaction: transaction,
|
||||
)
|
||||
}
|
||||
NotificationCenter.default.post(name: ChatListViewController.clearSearch, object: nil)
|
||||
}
|
||||
|
||||
func blockThreadAndDelete() {
|
||||
// Do not leave the group while blocking the thread; we'll
|
||||
// that below so that we can surface an error to the user
|
||||
// if leaving the group fails.
|
||||
SSKEnvironment.shared.databaseStorageRef.write { transaction in
|
||||
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(
|
||||
thread,
|
||||
blockMode: .local,
|
||||
shouldLeaveIfGroup: false,
|
||||
transaction: transaction,
|
||||
)
|
||||
}
|
||||
leaveAndSoftDeleteThread(messageRequestResponseType: .blockAndDelete)
|
||||
}
|
||||
|
||||
func blockThreadAndReportSpam(in thread: TSThread) {
|
||||
let spamReport = SSKEnvironment.shared.databaseStorageRef.write { tx in
|
||||
return ReportSpamUIUtils.blockAndBuildSpamReport(in: thread, tx: tx)
|
||||
}
|
||||
Task {
|
||||
try? await spamReport?.submit(using: SSKEnvironment.shared.networkManagerRef)
|
||||
}
|
||||
|
||||
presentToastCVC(ReportSpamUIUtils.successfulReportText(didBlock: true))
|
||||
NotificationCenter.default.post(name: ChatListViewController.clearSearch, object: nil)
|
||||
}
|
||||
|
||||
func leaveAndSoftDeleteThread(
|
||||
messageRequestResponseType: OutgoingMessageRequestResponseSyncMessage.ResponseType,
|
||||
) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
|
||||
let syncManager = SSKEnvironment.shared.syncManagerRef
|
||||
|
||||
databaseStorage.write { tx in
|
||||
syncManager.sendMessageRequestResponseSyncMessage(
|
||||
thread: self.thread,
|
||||
responseType: messageRequestResponseType,
|
||||
transaction: tx,
|
||||
)
|
||||
}
|
||||
|
||||
let completion = {
|
||||
databaseStorage.write { transaction in
|
||||
DependenciesBridge.shared.threadSoftDeleteManager.softDelete(
|
||||
threads: [self.thread],
|
||||
// We're already sending a sync message about this above!
|
||||
sendDeleteForMeSyncMessage: false,
|
||||
tx: transaction,
|
||||
)
|
||||
}
|
||||
func declineMessageRequest(responseType: OutgoingMessageRequestResponseSyncMessage.ResponseType) {
|
||||
MessageRequestDecliner.declineMessageRequest(
|
||||
inThread: self.thread,
|
||||
responseType: responseType,
|
||||
)
|
||||
if responseType.shouldDeleteThread {
|
||||
self.conversationSplitViewController?.closeSelectedConversation(animated: true)
|
||||
NotificationCenter.default.post(name: ChatListViewController.clearSearch, object: nil)
|
||||
}
|
||||
|
||||
guard let groupThread = thread as? TSGroupThread, groupThread.groupModel.groupMembership.isLocalUserFullOrInvitedMember else {
|
||||
// If we don't need to leave the group, finish up immediately.
|
||||
return completion()
|
||||
if responseType.shouldReportSpam {
|
||||
self.presentToastCVC(
|
||||
ReportSpamUIUtils.successfulReportText(didBlock: responseType.shouldBlockThread),
|
||||
)
|
||||
}
|
||||
|
||||
// Leave the group if we're a member.
|
||||
GroupManager.leaveGroupOrDeclineInviteAsyncWithUI(groupThread: groupThread, fromViewController: self, success: completion)
|
||||
NotificationCenter.default.post(name: ChatListViewController.clearSearch, object: nil)
|
||||
}
|
||||
|
||||
/// Accept a message request, or unblock chat.
|
||||
@ -380,19 +313,18 @@ extension ConversationViewController {
|
||||
)
|
||||
|
||||
actionSheet.addAction(ActionSheetAction(title: blockActionTitle) { [weak self] _ in
|
||||
self?.blockThread()
|
||||
self?.declineMessageRequest(responseType: .block)
|
||||
sheetCompletion?(true)
|
||||
})
|
||||
|
||||
if !hasReportedSpam {
|
||||
actionSheet.addAction(ActionSheetAction(title: blockAndReportSpamActionTitle) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
self.blockThreadAndReportSpam(in: self.thread)
|
||||
self?.declineMessageRequest(responseType: .blockAndSpam)
|
||||
sheetCompletion?(true)
|
||||
})
|
||||
} else {
|
||||
actionSheet.addAction(ActionSheetAction(title: blockAndDeleteActionTitle) { [weak self] _ in
|
||||
self?.blockThreadAndDelete()
|
||||
self?.declineMessageRequest(responseType: .blockAndDelete)
|
||||
sheetCompletion?(true)
|
||||
})
|
||||
}
|
||||
@ -442,8 +374,8 @@ extension ConversationViewController {
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(title: actionSheetTitle, message: actionSheetMessage)
|
||||
actionSheet.addAction(ActionSheetAction(title: confirmationText, handler: { _ in
|
||||
self.leaveAndSoftDeleteThread(messageRequestResponseType: .delete)
|
||||
actionSheet.addAction(ActionSheetAction(title: confirmationText, handler: { [weak self] _ in
|
||||
self?.declineMessageRequest(responseType: .delete)
|
||||
}))
|
||||
actionSheet.addAction(ActionSheetAction(title: CommonStrings.cancelButton, style: .cancel))
|
||||
return actionSheet
|
||||
@ -452,8 +384,9 @@ extension ConversationViewController {
|
||||
// TODO[SPAM]: For groups, fetch the inviter to add to the message
|
||||
func createReportThreadActionSheet() -> ActionSheetController {
|
||||
return ReportSpamUIUtils.createReportSpamActionSheet(
|
||||
for: thread,
|
||||
forThread: thread,
|
||||
isBlocked: threadViewModel.isBlocked,
|
||||
declineMessageRequest: self.declineMessageRequest(responseType:),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
65
Signal/ConversationView/MessageRequestDecliner.swift
Normal file
65
Signal/ConversationView/MessageRequestDecliner.swift
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
import SignalUI
|
||||
|
||||
enum MessageRequestDecliner {
|
||||
@MainActor
|
||||
static func declineMessageRequest(
|
||||
inThread thread: TSThread,
|
||||
responseType: OutgoingMessageRequestResponseSyncMessage.ResponseType,
|
||||
) {
|
||||
let blockingManager = SSKEnvironment.shared.blockingManagerRef
|
||||
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
|
||||
let deleteManager = DependenciesBridge.shared.threadSoftDeleteManager
|
||||
let syncManager = SSKEnvironment.shared.syncManagerRef
|
||||
|
||||
// Leave the group if we're going to block it or delete it. (If we're only
|
||||
// reporting spam without blocking it, we remain a member of the group.)
|
||||
let shouldLeaveGroup = responseType.shouldBlockThread || responseType.shouldDeleteThread
|
||||
|
||||
databaseStorage.write { tx in
|
||||
syncManager.sendMessageRequestResponseSyncMessage(
|
||||
thread: thread,
|
||||
responseType: responseType,
|
||||
transaction: tx,
|
||||
)
|
||||
if responseType.shouldBlockThread {
|
||||
blockingManager.addBlockedThread(
|
||||
thread,
|
||||
blockMode: .local,
|
||||
shouldLeaveIfGroup: false,
|
||||
transaction: tx,
|
||||
)
|
||||
}
|
||||
if responseType.shouldReportSpam {
|
||||
let spamReport = ReportSpamUIUtils.insertSpamReportMessage(in: thread, tx: tx)
|
||||
// We don't wait for this because it's best effort.
|
||||
Task {
|
||||
_ = try? await spamReport?.submit(using: SSKEnvironment.shared.networkManagerRef)
|
||||
}
|
||||
}
|
||||
if shouldLeaveGroup, let thread = thread as? TSGroupThread, thread.groupModel.groupMembership.isLocalUserFullOrInvitedMember {
|
||||
// We don't wait for this because it's durably enqeueued and may take up to
|
||||
// 24 hours to complete.
|
||||
_ = GroupManager.localLeaveGroupOrDeclineInvite(
|
||||
groupThread: thread,
|
||||
waitForMessageProcessing: true,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
if responseType.shouldDeleteThread {
|
||||
deleteManager.softDelete(
|
||||
threads: [thread],
|
||||
// We're already sending a sync message about this above!
|
||||
sendDeleteForMeSyncMessage: false,
|
||||
tx: tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,21 +8,28 @@ import SignalServiceKit
|
||||
import SignalUI
|
||||
|
||||
enum ReportSpamUIUtils {
|
||||
/// Called only if the user reports spam.
|
||||
/// The `Bool` parameter represents if the thread was also blocked.
|
||||
typealias Completion = (Bool) -> Void
|
||||
|
||||
static func showReportSpamActionSheet(
|
||||
_ thread: TSThread,
|
||||
isBlocked: Bool,
|
||||
from viewController: UIViewController,
|
||||
completion: Completion?,
|
||||
forThread thread: TSThread,
|
||||
isBlocked: Bool,
|
||||
onSuccess: @escaping @MainActor (OutgoingMessageRequestResponseSyncMessage.ResponseType) -> Void,
|
||||
) {
|
||||
let actionSheet = createReportSpamActionSheet(for: thread, isBlocked: isBlocked, completion: completion)
|
||||
let actionSheet = createReportSpamActionSheet(
|
||||
forThread: thread,
|
||||
isBlocked: isBlocked,
|
||||
declineMessageRequest: { responseType in
|
||||
MessageRequestDecliner.declineMessageRequest(inThread: thread, responseType: responseType)
|
||||
onSuccess(responseType)
|
||||
},
|
||||
)
|
||||
viewController.presentActionSheet(actionSheet)
|
||||
}
|
||||
|
||||
static func createReportSpamActionSheet(for thread: TSThread, isBlocked: Bool, completion: Completion? = nil) -> ActionSheetController {
|
||||
static func createReportSpamActionSheet(
|
||||
forThread thread: TSThread,
|
||||
isBlocked: Bool,
|
||||
declineMessageRequest: @escaping @MainActor (OutgoingMessageRequestResponseSyncMessage.ResponseType) -> Void,
|
||||
) -> ActionSheetController {
|
||||
let actionSheetTitle = OWSLocalizedString(
|
||||
"MESSAGE_REQUEST_REPORT_CONVERSATION_TITLE",
|
||||
comment: "Action sheet title to confirm reporting a conversation as spam via a message request.",
|
||||
@ -40,13 +47,7 @@ enum ReportSpamUIUtils {
|
||||
comment: "Action sheet action to confirm reporting a conversation as spam via a message request.",
|
||||
),
|
||||
handler: { _ in
|
||||
let spamReport = SSKEnvironment.shared.databaseStorageRef.write { tx in
|
||||
return Self.buildSpamReport(in: thread, tx: tx)
|
||||
}
|
||||
Task {
|
||||
try? await spamReport?.submit(using: SSKEnvironment.shared.networkManagerRef)
|
||||
}
|
||||
completion?(false)
|
||||
declineMessageRequest(.spam)
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -58,13 +59,7 @@ enum ReportSpamUIUtils {
|
||||
comment: "Action sheet action to confirm blocking and reporting spam for a thread via a message request.",
|
||||
),
|
||||
handler: { _ in
|
||||
let spamReport = SSKEnvironment.shared.databaseStorageRef.write { tx in
|
||||
return Self.blockAndBuildSpamReport(in: thread, tx: tx)
|
||||
}
|
||||
Task {
|
||||
try? await spamReport?.submit(using: SSKEnvironment.shared.networkManagerRef)
|
||||
}
|
||||
completion?(true)
|
||||
declineMessageRequest(.blockAndSpam)
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -87,38 +82,7 @@ enum ReportSpamUIUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static func blockAndBuildSpamReport(in thread: TSThread, tx: DBWriteTransaction) -> SpamReport? {
|
||||
SSKEnvironment.shared.blockingManagerRef.addBlockedThread(
|
||||
thread,
|
||||
blockMode: .local,
|
||||
shouldLeaveIfGroup: false,
|
||||
transaction: tx,
|
||||
)
|
||||
|
||||
let result = Self._buildSpamReport(in: thread, tx: tx)
|
||||
|
||||
SSKEnvironment.shared.syncManagerRef.sendMessageRequestResponseSyncMessage(
|
||||
thread: thread,
|
||||
responseType: .blockAndSpam,
|
||||
transaction: tx,
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
static func buildSpamReport(in thread: TSThread, tx: DBWriteTransaction) -> SpamReport? {
|
||||
let result = Self._buildSpamReport(in: thread, tx: tx)
|
||||
|
||||
SSKEnvironment.shared.syncManagerRef.sendMessageRequestResponseSyncMessage(
|
||||
thread: thread,
|
||||
responseType: .spam,
|
||||
transaction: tx,
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private static func _buildSpamReport(in thread: TSThread, tx: DBWriteTransaction) -> SpamReport? {
|
||||
static func insertSpamReportMessage(in thread: TSThread, tx: DBWriteTransaction) -> SpamReport? {
|
||||
var aci: Aci?
|
||||
var isGroup = false
|
||||
if let contactThread = thread as? TSContactThread {
|
||||
|
||||
@ -694,13 +694,14 @@ class ConversationSettingsViewController: OWSTableViewController2, BadgeCollecti
|
||||
|
||||
func didTapReportSpam() {
|
||||
ReportSpamUIUtils.showReportSpamActionSheet(
|
||||
thread,
|
||||
isBlocked: threadViewModel.isBlocked,
|
||||
from: self,
|
||||
) { [weak self] didBlock in
|
||||
self?.reloadThreadAndUpdateContent()
|
||||
self?.presentToast(text: ReportSpamUIUtils.successfulReportText(didBlock: didBlock))
|
||||
}
|
||||
forThread: thread,
|
||||
isBlocked: threadViewModel.isBlocked,
|
||||
onSuccess: { [weak self] responseType in
|
||||
self?.reloadThreadAndUpdateContent()
|
||||
self?.presentToast(text: ReportSpamUIUtils.successfulReportText(didBlock: responseType.shouldBlockThread))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func didTapInternalSettings() {
|
||||
|
||||
@ -27,6 +27,33 @@ public final class OutgoingMessageRequestResponseSyncMessage: OutgoingSyncMessag
|
||||
case .blockAndSpam: .blockAndSpam
|
||||
}
|
||||
}
|
||||
|
||||
public var shouldBlockThread: Bool {
|
||||
switch self {
|
||||
case .block, .blockAndDelete, .blockAndSpam:
|
||||
return true
|
||||
case .accept, .delete, .spam:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var shouldDeleteThread: Bool {
|
||||
switch self {
|
||||
case .delete, .blockAndDelete:
|
||||
return true
|
||||
case .accept, .block, .spam, .blockAndSpam:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var shouldReportSpam: Bool {
|
||||
switch self {
|
||||
case .spam, .blockAndSpam:
|
||||
return true
|
||||
case .accept, .block, .delete, .blockAndDelete:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// v0: The sending thread is also the acted-upon thread.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user