Fix forwarding of messages with mentions
This commit is contained in:
parent
d5e0ae1716
commit
964d2fdffa
@ -2,6 +2,8 @@
|
||||
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import SignalServiceKit
|
||||
|
||||
public protocol ForwardMessageDelegate: AnyObject {
|
||||
func forwardMessageFlowDidComplete(items: [ForwardMessageItem],
|
||||
recipientThreads: [TSThread])
|
||||
@ -24,7 +26,6 @@ class ForwardMessageViewController: InteractiveSheetViewController {
|
||||
|
||||
public typealias Item = ForwardMessageItem
|
||||
fileprivate typealias Content = ForwardMessageContent
|
||||
fileprivate typealias RecipientThread = ForwardMessageRecipientThread
|
||||
|
||||
fileprivate var content: Content
|
||||
|
||||
@ -33,8 +34,6 @@ class ForwardMessageViewController: InteractiveSheetViewController {
|
||||
private let selection = ConversationPickerSelection()
|
||||
var selectedConversations: [ConversationItem] { selection.conversations }
|
||||
|
||||
fileprivate var currentMentionableAddresses: [SignalServiceAddress] = []
|
||||
|
||||
private init(content: Content) {
|
||||
self.content = content
|
||||
self.pickerVC = ForwardPickerViewController(selection: selection)
|
||||
@ -177,25 +176,6 @@ class ForwardMessageViewController: InteractiveSheetViewController {
|
||||
|
||||
override var renderExternalHandle: Bool { false }
|
||||
override var minHeight: CGFloat { 576 }
|
||||
|
||||
fileprivate func updateCurrentMentionableAddresses() {
|
||||
guard selectedConversations.count == 1,
|
||||
let conversationItem = selectedConversations.first else {
|
||||
self.currentMentionableAddresses = []
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try databaseStorage.write { transaction in
|
||||
let recipientThread = try RecipientThread.build(conversationItem: conversationItem,
|
||||
transaction: transaction)
|
||||
self.currentMentionableAddresses = recipientThread.mentionCandidates
|
||||
}
|
||||
} catch {
|
||||
owsFailDebug("Error: \(error)")
|
||||
self.currentMentionableAddresses = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sending
|
||||
@ -269,11 +249,11 @@ extension ForwardMessageViewController {
|
||||
let recipientConversations = self.selectedConversations
|
||||
firstly(on: .global()) {
|
||||
self.recipientThreads(for: recipientConversations)
|
||||
}.then(on: .main) { (recipientThreads: [RecipientThread]) -> Promise<Void> in
|
||||
}.then(on: .main) { (recipientThreads: [TSThread]) -> Promise<Void> in
|
||||
try Self.databaseStorage.write { transaction in
|
||||
for recipientThread in recipientThreads {
|
||||
// We're sending a message to this thread, approve any pending message request
|
||||
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: recipientThread.thread,
|
||||
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: recipientThread,
|
||||
transaction: transaction)
|
||||
}
|
||||
|
||||
@ -314,16 +294,15 @@ extension ForwardMessageViewController {
|
||||
return self.send(toRecipientThreads: recipientThreads) { recipientThread in
|
||||
self.send(body: messageBody,
|
||||
linkPreviewDraft: nil,
|
||||
thread: recipientThread.thread)
|
||||
recipientThread: recipientThread)
|
||||
}
|
||||
} else {
|
||||
return Promise.value(())
|
||||
}
|
||||
}
|
||||
}.map(on: .main) {
|
||||
let threads = recipientThreads.map { $0.thread }
|
||||
self.forwardMessageDelegate?.forwardMessageFlowDidComplete(items: content.allItems,
|
||||
recipientThreads: threads)
|
||||
recipientThreads: recipientThreads)
|
||||
}
|
||||
}.catch(on: .main) { error in
|
||||
owsFailDebug("Error: \(error)")
|
||||
@ -332,7 +311,7 @@ extension ForwardMessageViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func send(item: Item, toRecipientThreads recipientThreads: [RecipientThread]) -> Promise<Void> {
|
||||
private func send(item: Item, toRecipientThreads recipientThreads: [TSThread]) -> Promise<Void> {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
let componentState = item.componentState
|
||||
@ -341,7 +320,7 @@ extension ForwardMessageViewController {
|
||||
let stickerInfo = stickerMetadata.stickerInfo
|
||||
if StickerManager.isStickerInstalled(stickerInfo: stickerInfo) {
|
||||
return send(toRecipientThreads: recipientThreads) { recipientThread in
|
||||
self.send(installedSticker: stickerInfo, thread: recipientThread.thread)
|
||||
self.send(installedSticker: stickerInfo, thread: recipientThread)
|
||||
}
|
||||
} else {
|
||||
guard let stickerAttachment = componentState.stickerAttachment else {
|
||||
@ -352,7 +331,7 @@ extension ForwardMessageViewController {
|
||||
return send(toRecipientThreads: recipientThreads) { recipientThread in
|
||||
self.send(uninstalledSticker: stickerMetadata,
|
||||
stickerData: stickerData,
|
||||
thread: recipientThread.thread)
|
||||
thread: recipientThread)
|
||||
}
|
||||
} catch {
|
||||
return Promise(error: error)
|
||||
@ -365,7 +344,7 @@ extension ForwardMessageViewController {
|
||||
contactShare.dbRecord.saveAvatarImage(avatarImage, transaction: transaction)
|
||||
}
|
||||
}
|
||||
return self.send(contactShare: contactShare, thread: recipientThread.thread)
|
||||
return self.send(contactShare: contactShare, thread: recipientThread)
|
||||
}
|
||||
} else if let attachments = item.attachments,
|
||||
!attachments.isEmpty {
|
||||
@ -379,20 +358,22 @@ extension ForwardMessageViewController {
|
||||
return send(toRecipientThreads: recipientThreads) { recipientThread in
|
||||
self.send(body: messageBody,
|
||||
linkPreviewDraft: linkPreviewDraft,
|
||||
thread: recipientThread.thread)
|
||||
recipientThread: recipientThread)
|
||||
}
|
||||
} else {
|
||||
return Promise(error: ForwardError.invalidInteraction)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func send(body: MessageBody, linkPreviewDraft: OWSLinkPreviewDraft?, thread: TSThread) -> Promise<Void> {
|
||||
fileprivate func send(body: MessageBody, linkPreviewDraft: OWSLinkPreviewDraft?, recipientThread: TSThread) -> Promise<Void> {
|
||||
databaseStorage.read { transaction in
|
||||
ThreadUtil.enqueueMessage(body: body,
|
||||
thread: thread,
|
||||
quotedReplyModel: nil,
|
||||
linkPreviewDraft: linkPreviewDraft,
|
||||
transaction: transaction)
|
||||
ThreadUtil.enqueueMessage(
|
||||
body: body.forNewContext(recipientThread, transaction: transaction.unwrapGrdbRead),
|
||||
thread: recipientThread,
|
||||
quotedReplyModel: nil,
|
||||
linkPreviewDraft: linkPreviewDraft,
|
||||
transaction: transaction
|
||||
)
|
||||
}
|
||||
return Promise.value(())
|
||||
}
|
||||
@ -424,14 +405,14 @@ extension ForwardMessageViewController {
|
||||
return Promise.value(())
|
||||
}
|
||||
|
||||
fileprivate func send(toRecipientThreads recipientThreads: [RecipientThread],
|
||||
enqueueBlock: @escaping (RecipientThread) -> Promise<Void>) -> Promise<Void> {
|
||||
fileprivate func send(toRecipientThreads recipientThreads: [TSThread],
|
||||
enqueueBlock: @escaping (TSThread) -> Promise<Void>) -> Promise<Void> {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
return Promise.when(fulfilled: recipientThreads.map { thread in enqueueBlock(thread) }).asVoid()
|
||||
}
|
||||
|
||||
fileprivate func recipientThreads(for conversationItems: [ConversationItem]) -> Promise<[RecipientThread]> {
|
||||
fileprivate func recipientThreads(for conversationItems: [ConversationItem]) -> Promise<[TSThread]> {
|
||||
firstly(on: .global()) {
|
||||
guard conversationItems.count > 0 else {
|
||||
throw OWSAssertionError("No recipients.")
|
||||
@ -439,7 +420,10 @@ extension ForwardMessageViewController {
|
||||
|
||||
return try self.databaseStorage.write { transaction in
|
||||
try conversationItems.map {
|
||||
try RecipientThread.build(conversationItem: $0, transaction: transaction)
|
||||
guard let thread = $0.getOrCreateThread(transaction: transaction) else {
|
||||
throw ForwardError.missingThread
|
||||
}
|
||||
return thread
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -450,7 +434,6 @@ extension ForwardMessageViewController {
|
||||
|
||||
extension ForwardMessageViewController: ConversationPickerDelegate {
|
||||
func conversationPickerSelectionDidChange(_ conversationPickerViewController: ConversationPickerViewController) {
|
||||
updateCurrentMentionableAddresses()
|
||||
ensureBottomFooterVisibility()
|
||||
}
|
||||
|
||||
@ -814,37 +797,6 @@ private enum ForwardMessageContent {
|
||||
|
||||
// MARK: -
|
||||
|
||||
private struct ForwardMessageRecipientThread {
|
||||
let thread: TSThread
|
||||
let mentionCandidates: [SignalServiceAddress]
|
||||
|
||||
static func build(conversationItem: ConversationItem,
|
||||
transaction: SDSAnyWriteTransaction) throws -> ForwardMessageRecipientThread {
|
||||
|
||||
guard let thread = conversationItem.getOrCreateThread(transaction: transaction) else {
|
||||
owsFailDebug("Missing thread for conversation")
|
||||
throw ForwardError.missingThread
|
||||
}
|
||||
|
||||
let mentionCandidates = self.mentionCandidates(conversationItem: conversationItem,
|
||||
thread: thread,
|
||||
transaction: transaction)
|
||||
return ForwardMessageRecipientThread(thread: thread, mentionCandidates: mentionCandidates)
|
||||
}
|
||||
|
||||
private static func mentionCandidates(conversationItem: ConversationItem,
|
||||
thread: TSThread,
|
||||
transaction: SDSAnyReadTransaction) -> [SignalServiceAddress] {
|
||||
guard let groupThread = thread as? TSGroupThread,
|
||||
Mention.threadAllowsMentionSend(groupThread) else {
|
||||
return []
|
||||
}
|
||||
return groupThread.recipientAddresses
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private class ForwardNavigationViewController: OWSNavigationController {
|
||||
weak var forwardMessageViewController: ForwardMessageViewController?
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -45,6 +45,48 @@ public class MessageBody: NSObject, NSCopying, NSSecureCoding {
|
||||
public func plaintextBody(transaction: GRDBReadTransaction) -> String {
|
||||
return ranges.plaintextBody(text: text, transaction: transaction)
|
||||
}
|
||||
|
||||
public func forNewContext(
|
||||
_ context: TSThread,
|
||||
transaction: GRDBReadTransaction
|
||||
) -> MessageBody {
|
||||
guard hasRanges else { return self }
|
||||
guard let groupThread = context as? TSGroupThread, groupThread.isGroupV2Thread else {
|
||||
return MessageBody(text: plaintextBody(transaction: transaction), ranges: .empty)
|
||||
}
|
||||
|
||||
let mutableText = NSMutableString(string: text)
|
||||
var mentions = [NSRange: UUID]()
|
||||
var rangeOffset = 0
|
||||
|
||||
func shouldPreserveMention(_ address: SignalServiceAddress) -> Bool {
|
||||
return groupThread.recipientAddresses.contains(address)
|
||||
}
|
||||
|
||||
for (range, uuid) in ranges.orderedMentions {
|
||||
guard range.location >= 0 && range.location + range.length <= (text as NSString).length else {
|
||||
owsFailDebug("Ignoring invalid range in body ranges \(range)")
|
||||
continue
|
||||
}
|
||||
|
||||
let mentionAddress = SignalServiceAddress(uuid: uuid)
|
||||
let offsetRange = NSRange(location: range.location + rangeOffset, length: range.length)
|
||||
|
||||
if shouldPreserveMention(mentionAddress) {
|
||||
mentions[offsetRange] = uuid
|
||||
} else {
|
||||
let displayName = Self.contactsManager.displayName(
|
||||
for: mentionAddress,
|
||||
transaction: transaction.asAnyRead
|
||||
)
|
||||
let mentionPlaintext = MessageBodyRanges.mentionPrefix + displayName
|
||||
mutableText.replaceCharacters(in: offsetRange, with: mentionPlaintext)
|
||||
rangeOffset += (mentionPlaintext as NSString).length - range.length
|
||||
}
|
||||
}
|
||||
|
||||
return MessageBody(text: String(mutableText), ranges: MessageBodyRanges(mentions: mentions))
|
||||
}
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
|
||||
@ -43,7 +43,9 @@ public class AttachmentMultisend: Dependencies {
|
||||
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread,
|
||||
transaction: transaction)
|
||||
|
||||
let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody,
|
||||
let messageBodyForContext = approvalMessageBody?.forNewContext(thread, transaction: transaction.unwrapGrdbRead)
|
||||
|
||||
let message = try! ThreadUtil.createUnsentMessage(body: messageBodyForContext,
|
||||
mediaAttachments: attachments,
|
||||
thread: thread,
|
||||
quotedReplyModel: nil,
|
||||
@ -117,7 +119,9 @@ public class AttachmentMultisend: Dependencies {
|
||||
ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread,
|
||||
transaction: transaction)
|
||||
|
||||
let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody,
|
||||
let messageBodyForContext = approvalMessageBody?.forNewContext(thread, transaction: transaction.unwrapGrdbRead)
|
||||
|
||||
let message = try! ThreadUtil.createUnsentMessage(body: messageBodyForContext,
|
||||
mediaAttachments: attachments,
|
||||
thread: thread,
|
||||
quotedReplyModel: nil,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user