Asyncify AttachmentMultisend

This commit is contained in:
Max Radermacher 2025-12-04 21:27:40 -06:00 committed by GitHub
parent 2fc65f3f01
commit 215659ae55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 184 additions and 288 deletions

View File

@ -147,46 +147,42 @@ extension CameraFirstCaptureSendFlow: ConversationPickerDelegate {
}
public func conversationPickerDidCompleteSelection(_ conversationPickerViewController: ConversationPickerViewController) {
if let textAttachment = textAttachment {
if let textAttachment {
let selectedStoryItems = selectedConversations.filter { $0 is StoryConversationItem }
guard !selectedStoryItems.isEmpty else {
owsFailDebug("Selection was unexpectedly empty.")
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
return
}
firstly {
AttachmentMultisend.sendTextAttachment(
textAttachment,
to: selectedStoryItems
).enqueuedPromise
}.done { _ in
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
}.catch { error in
owsFailDebug("Error: \(error)")
Task { @MainActor in
do {
_ = try await AttachmentMultisend.enqueueTextAttachment(textAttachment, to: selectedStoryItems)
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
} catch {
owsFailDebug("\(error)")
}
}
return
}
guard let approvedAttachments = self.approvedAttachments else {
owsFailDebug("approvedAttachments was unexpectedly nil")
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
if let approvedAttachments {
let approvedMessageBody = self.approvedMessageBody
let selectedConversations = self.selectedConversations
Task { @MainActor in
do {
_ = try await AttachmentMultisend.enqueueApprovedMedia(
conversations: selectedConversations,
approvedMessageBody: approvedMessageBody,
approvedAttachments: approvedAttachments
)
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
} catch {
owsFailDebug("\(error)")
}
}
return
}
let conversations = selectedConversations
firstly {
AttachmentMultisend.sendApprovedMedia(
conversations: conversations,
approvedMessageBody: self.approvedMessageBody,
approvedAttachments: approvedAttachments
).enqueuedPromise
}.done { _ in
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
}.catch { error in
owsFailDebug("Error: \(error)")
}
owsFailDebug("completed without anything to send")
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
}
public func conversationPickerCanCancel(_ conversationPickerViewController: ConversationPickerViewController) -> Bool {

View File

@ -342,28 +342,26 @@ extension ForwardMessageViewController {
} else if !item.attachments.isEmpty {
// TODO: What about link previews in this case?
let conversations = selectedConversations
_ = try await AttachmentMultisend.sendApprovedMedia(
_ = try await AttachmentMultisend.enqueueApprovedMedia(
conversations: conversations,
approvedMessageBody: item.messageBody,
approvedAttachments: ApprovedAttachments(nonViewOnceAttachments: item.attachments),
).enqueuedPromise.awaitable()
)
} else if let textAttachment = item.textAttachment {
// TODO: we want to reuse the uploaded link preview image attachment instead of re-uploading
// if the original was sent recently (if not the image could be stale)
_ = try await AttachmentMultisend.sendTextAttachment(
_ = try await AttachmentMultisend.enqueueTextAttachment(
textAttachment.asUnsentAttachment(), to: selectedConversations
).enqueuedPromise.awaitable()
)
} else if let messageBody = item.messageBody {
let linkPreviewDraft = item.linkPreviewDraft
await enqueueMessageViaThreadUtil(toRecipientThreads: outgoingMessageRecipientThreads) { recipientThread in
self.send(body: messageBody, linkPreviewDraft: linkPreviewDraft, recipientThread: recipientThread)
}
// Send the text message to any selected story recipients
// as a text story with default styling.
let storyConversations = selectedConversations.filter { $0.outgoingMessageType == .storyMessage }
let storySendResult = StorySharing.sendTextStory(with: messageBody, linkPreviewDraft: linkPreviewDraft, to: storyConversations)
_ = try await storySendResult?.enqueuedPromise.awaitable()
// Send the text message to any selected story recipients as a text story
// with default styling.
_ = try await StorySharing.enqueueTextStory(with: messageBody, linkPreviewDraft: linkPreviewDraft, to: selectedConversations)
} else {
throw ForwardError.invalidInteraction
}

View File

@ -59,7 +59,7 @@ public class HydratedMessageBody: Equatable, Hashable {
mentionHydrator: MentionHydrator,
isRTL: Bool = CurrentAppContext().isRTL
) {
guard messageBody.text.isEmpty.negated else {
if messageBody.text.isEmpty {
self.hydratedText = ""
self.unhydratedMentions = []
self.mentionAttributes = []

View File

@ -193,28 +193,28 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
selectedConversations: selectedConversations,
approvedSend: approvedSend,
) {
case .success:
case nil:
self.dismissSendProgressSheet {}
self.shareViewDelegate?.shareViewWasCompleted()
case .failure(let error):
self.dismissSendProgressSheet { self.showSendFailure(error: error) }
case .some(let failure):
self.dismissSendProgressSheet { self.showSendFailure(failure) }
}
}
}
private struct SendError: Error {
private struct SendFailure {
let outgoingMessages: [PreparedOutgoingMessage]
let error: Error
}
private nonisolated func tryToSend(
private func tryToSend(
selectedConversations: [ConversationItem],
approvedSend: ApprovedSend,
) async -> Result<Void, SendError> {
) async -> SendFailure? {
switch approvedSend {
case .text(let messageBody, let linkPreview):
guard !messageBody.text.isEmpty else {
return .failure(.init(outgoingMessages: [], error: OWSAssertionError("Missing body.")))
return SendFailure(outgoingMessages: [], error: OWSAssertionError("Missing body."))
}
let linkPreviewDataSource: LinkPreviewDataSource?
@ -238,13 +238,13 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
)
return try unpreparedMessage.prepare(tx: tx)
},
storySendBlock: { storyConversations in
enqueueStory: { conversations in
// Send the text message to any selected story recipients
// as a text story with default styling.
StorySharing.sendTextStory(
try await StorySharing.enqueueTextStory(
with: messageBody,
linkPreviewDraft: linkPreview,
to: storyConversations
to: conversations
)
}
)
@ -254,7 +254,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
let contactShareManager = DependenciesBridge.shared.contactShareManager
contactShareForSending = try await contactShareManager.validateAndPrepare(draft: contactShare)
} catch {
return .failure(.init(outgoingMessages: [], error: error))
return SendFailure(outgoingMessages: [], error: error)
}
return await self.sendToOutgoingMessageThreads(
selectedConversations: selectedConversations,
@ -277,32 +277,35 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
return try unpreparedMessage.prepare(tx: tx)
},
// We don't send contact shares to stories
storySendBlock: nil
enqueueStory: { _ in [] },
)
case .other(let attachments, let messageBody):
// This method will also add threads to the profile whitelist.
let sendResult = AttachmentMultisend.sendApprovedMedia(
conversations: selectedConversations,
approvedMessageBody: messageBody,
approvedAttachments: attachments
)
let preparedMessages: [PreparedOutgoingMessage]
let enqueueResults: [AttachmentMultisend.EnqueueResult]
do {
preparedMessages = try await sendResult.preparedPromise.awaitable()
} catch let error {
return .failure(.init(outgoingMessages: [], error: error))
}
await MainActor.run {
self.presentOrUpdateSendProgressSheet(outgoingMessages: preparedMessages)
enqueueResults = try await AttachmentMultisend.enqueueApprovedMedia(
conversations: selectedConversations,
approvedMessageBody: messageBody,
approvedAttachments: attachments,
)
} catch {
return SendFailure(outgoingMessages: [], error: error)
}
self.presentOrUpdateSendProgressSheet(outgoingMessages: enqueueResults.map(\.preparedMessage))
do {
_ = try await sendResult.sentPromise.awaitable()
} catch let error {
return .failure(.init(outgoingMessages: preparedMessages, error: error))
try await withThrowingTaskGroup { taskGroup in
for sendPromise in enqueueResults.map(\.sendPromise) {
taskGroup.addTask { try await sendPromise.awaitable() }
}
try await taskGroup.waitForAll()
}
} catch {
return SendFailure(outgoingMessages: enqueueResults.map(\.preparedMessage), error: error)
}
return .success(())
return nil
}
}
@ -339,26 +342,25 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
}
}
private nonisolated func sendToOutgoingMessageThreads(
private func sendToOutgoingMessageThreads(
selectedConversations: [ConversationItem],
messageBody: MessageBody?,
messageBlock: @escaping (AttachmentMultisend.Destination, DBWriteTransaction) throws -> PreparedOutgoingMessage,
storySendBlock: (([ConversationItem]) -> AttachmentMultisend.Result?)?
) async -> Result<Void, SendError> {
messageBlock: (AttachmentMultisend.Destination, DBWriteTransaction) throws -> PreparedOutgoingMessage,
enqueueStory: (_ conversations: [ConversationItem]) async throws -> [AttachmentMultisend.EnqueueResult],
) async -> SendFailure? {
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
let conversations = selectedConversations.filter { $0.outgoingMessageType == .message }
let preparedNonStoryMessages: [PreparedOutgoingMessage]
let nonStorySendPromises: [Promise<Void>]
do {
let destinations = try await AttachmentMultisend.prepareForSending(
messageBody,
to: conversations,
db: SSKEnvironment.shared.databaseStorageRef,
attachmentValidator: DependenciesBridge.shared.attachmentContentValidator
let destinations = try await AttachmentMultisend.prepareDestinations(
forSendingMessageBody: messageBody,
toConversations: conversations,
)
(preparedNonStoryMessages, nonStorySendPromises) = try await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
(preparedNonStoryMessages, nonStorySendPromises) = try await databaseStorage.awaitableWrite { tx in
let preparedMessages = try destinations.map { destination in
return try messageBlock(destination, tx)
}
@ -380,41 +382,32 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
}
return (preparedMessages, sendPromises)
}
} catch let error {
return .failure(.init(outgoingMessages: [], error: error))
} catch {
return SendFailure(outgoingMessages: [], error: error)
}
let storyConversations = selectedConversations.filter { $0.outgoingMessageType == .storyMessage }
let storySendResult = storySendBlock?(storyConversations)
let preparedStoryMessages: [PreparedOutgoingMessage]
let enqueueStoryResults: [AttachmentMultisend.EnqueueResult]
do {
preparedStoryMessages = try await storySendResult?.preparedPromise.awaitable() ?? []
enqueueStoryResults = try await enqueueStory(selectedConversations)
} catch let error {
return .failure(.init(outgoingMessages: [], error: error))
return SendFailure(outgoingMessages: [], error: error)
}
let preparedMessages = preparedNonStoryMessages + preparedStoryMessages
await MainActor.run {
self.presentOrUpdateSendProgressSheet(outgoingMessages: preparedMessages)
}
let preparedMessages = preparedNonStoryMessages + enqueueStoryResults.map(\.preparedMessage)
self.presentOrUpdateSendProgressSheet(outgoingMessages: preparedMessages)
do {
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
nonStorySendPromises.forEach { promise in
taskGroup.addTask(operation: {
try await promise.awaitable()
})
try await withThrowingTaskGroup { taskGroup in
for sendPromise in nonStorySendPromises + enqueueStoryResults.map(\.sendPromise) {
taskGroup.addTask { try await sendPromise.awaitable() }
}
taskGroup.addTask(operation: {
try await _ = storySendResult?.sentPromise.awaitable()
})
try await taskGroup.waitForAll()
}
return .success(())
} catch let error {
return .failure(.init(outgoingMessages: preparedMessages, error: error))
} catch {
return SendFailure(outgoingMessages: preparedMessages, error: error)
}
return nil
}
private nonisolated func threads(for conversationItems: [ConversationItem], tx: DBWriteTransaction) -> [TSThread] {
@ -427,10 +420,10 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
}
}
private func showSendFailure(error: SendError) {
private func showSendFailure(_ failure: SendFailure) {
AssertIsOnMainThread()
owsFailDebug("Error: \(error.error)")
Logger.warn("\(failure.error)")
let cancelAction = ActionSheetAction(
title: CommonStrings.cancelButton,
@ -438,7 +431,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
) { [weak self] _ in
guard let self = self else { return }
SSKEnvironment.shared.databaseStorageRef.write { transaction in
for message in error.outgoingMessages {
for message in failure.outgoingMessages {
// If we sent the message to anyone, mark it as failed
message.updateWithAllSendingRecipientsMarkedAsFailed(tx: transaction)
}
@ -448,7 +441,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
let failureTitle = OWSLocalizedString("SHARE_EXTENSION_SENDING_FAILURE_TITLE", comment: "Alert title")
if let untrustedIdentityError = error as? UntrustedIdentityError {
if let untrustedIdentityError = failure.error as? UntrustedIdentityError {
let untrustedServiceId = untrustedIdentityError.serviceId
let failureFormat = OWSLocalizedString(
"SHARE_EXTENSION_FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_FORMAT",
@ -501,7 +494,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
}
// Resend
self.resendMessages(error.outgoingMessages)
self.resendMessages(failure.outgoingMessages)
}
actionSheet.addAction(confirmAction)
@ -511,7 +504,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
actionSheet.addAction(cancelAction)
let retryAction = ActionSheetAction(title: CommonStrings.retryButton, style: .default) { [weak self] _ in
self?.resendMessages(error.outgoingMessages)
self?.resendMessages(failure.outgoingMessages)
}
actionSheet.addAction(retryAction)
@ -521,16 +514,15 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
func resendMessages(_ outgoingMessages: [PreparedOutgoingMessage]) {
AssertIsOnMainThread()
owsAssertDebug(outgoingMessages.count > 0)
owsAssertDebug(!outgoingMessages.isEmpty)
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueueRef
var promises = [Promise<Void>]()
SSKEnvironment.shared.databaseStorageRef.write { transaction in
databaseStorage.write { tx in
for message in outgoingMessages {
promises.append(SSKEnvironment.shared.messageSenderJobQueueRef.add(
.promise,
message: message,
transaction: transaction
))
promises.append(messageSenderJobQueue.add(.promise, message: message, transaction: tx))
}
}
@ -540,7 +532,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
self.shareViewDelegate?.shareViewWasCompleted()
}.catch { error in
self.dismissSendProgressSheet {
self.showSendFailure(error: .init(outgoingMessages: outgoingMessages, error: error))
self.showSendFailure(SendFailure(outgoingMessages: outgoingMessages, error: error))
}
}
}

View File

@ -16,13 +16,10 @@ extension AttachmentMultisend {
public let messageBody: ValidatedMessageBody?
}
public static func prepareForSending(
_ messageBody: MessageBody?,
to conversations: [ConversationItem],
db: SDSDatabaseStorage,
attachmentValidator: AttachmentContentValidator
public static func prepareDestinations(
forSendingMessageBody messageBody: MessageBody?,
toConversations conversations: [ConversationItem],
) async throws -> [Destination] {
// If the message body has no mentions, we can "hydrate" once across all threads
// and share it. We only need to re-generate per-thread if there are mentions.
let canShareMessageBody = !(messageBody?.ranges.hasMentions ?? false)
@ -33,7 +30,7 @@ extension AttachmentMultisend {
let messageBody: HydratedMessageBody?
}
let preDestinations: [PreDestination] = try await db.awaitableWrite { tx in
let preDestinations: [PreDestination] = try await deps.databaseStorage.awaitableWrite { tx in
return try conversations.map { conversation in
guard let thread = conversation.getOrCreateThread(transaction: tx) else {
throw OWSAssertionError("Missing thread for conversation")
@ -57,7 +54,7 @@ extension AttachmentMultisend {
// We only prepare the single shared body.
let validatedMessageBody: ValidatedMessageBody?
if let messageBody {
validatedMessageBody = try await attachmentValidator.prepareOversizeTextIfNeeded(
validatedMessageBody = try await deps.attachmentValidator.prepareOversizeTextIfNeeded(
messageBody
)
} else {
@ -83,7 +80,7 @@ extension AttachmentMultisend {
))
continue
}
let validatedMessageBody = try await attachmentValidator.prepareOversizeTextIfNeeded(
let validatedMessageBody = try await deps.attachmentValidator.prepareOversizeTextIfNeeded(
hydratedMessageBody.asMessageBodyForForwarding()
)
destinations.append(.init(

View File

@ -9,166 +9,88 @@ import LibSignalClient
public class AttachmentMultisend {
public struct Result {
/// Resolved when the messages are prepared but before uploading/sending.
public let preparedPromise: Promise<[PreparedOutgoingMessage]>
/// Resolved when sending is durably enqueued but before uploading/sending.
public let enqueuedPromise: Promise<[TSThread]>
/// Resolved when the message is sent.
public let sentPromise: Promise<[TSThread]>
public struct EnqueueResult {
public let preparedMessage: PreparedOutgoingMessage
public let sendPromise: Promise<Void>
}
private init() {}
// MARK: - API
public class func sendApprovedMedia(
public class func enqueueApprovedMedia(
conversations: [ConversationItem],
approvedMessageBody: MessageBody?,
approvedAttachments: ApprovedAttachments,
) -> AttachmentMultisend.Result {
let (preparedPromise, preparedFuture) = Promise<[PreparedOutgoingMessage]>.pending()
let (enqueuedPromise, enqueuedFuture) = Promise<[TSThread]>.pending()
) async throws -> [EnqueueResult] {
let destinations = try await prepareDestinations(
forSendingMessageBody: approvedMessageBody,
toConversations: conversations,
)
let sentPromise = Promise<[TSThread]>.wrapAsync {
let threads: [TSThread]
let preparedMessages: [PreparedOutgoingMessage]
let sendPromises: [Promise<Void>]
do {
let destinations = try await Self.prepareForSending(
approvedMessageBody,
to: conversations,
db: deps.databaseStorage,
attachmentValidator: deps.attachmentValidator
)
var hasNonStoryDestination = false
var hasStoryDestination = false
destinations.forEach { destination in
switch destination.conversationItem.outgoingMessageType {
case .message:
hasNonStoryDestination = true
case .storyMessage:
hasStoryDestination = true
}
}
let segmentedAttachments = try await segmentAttachmentsIfNecessary(
for: conversations,
approvedAttachments: approvedAttachments,
hasNonStoryDestination: hasNonStoryDestination,
hasStoryDestination: hasStoryDestination
)
(threads, preparedMessages, sendPromises) = try await deps.databaseStorage.awaitableWrite { tx in
let threads: [TSThread]
let preparedMessages: [PreparedOutgoingMessage]
(threads, preparedMessages) = try prepareForSending(
destinations: destinations,
// Stories get an untruncated message body
messageBodyForStories: approvedMessageBody,
approvedAttachments: segmentedAttachments,
isViewOnce: approvedAttachments.isViewOnce,
tx: tx
)
let sendPromises: [Promise<Void>] = preparedMessages.map {
deps.messageSenderJobQueue.add(
.promise,
message: $0,
transaction: tx
)
}
return (threads, preparedMessages, sendPromises)
}
} catch let error {
preparedFuture.reject(error)
enqueuedFuture.reject(error)
throw error
var hasNonStoryDestination = false
var hasStoryDestination = false
destinations.forEach { destination in
switch destination.conversationItem.outgoingMessageType {
case .message:
hasNonStoryDestination = true
case .storyMessage:
hasStoryDestination = true
}
preparedFuture.resolve(preparedMessages)
enqueuedFuture.resolve(threads)
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
sendPromises.forEach { promise in
taskGroup.addTask {
try await promise.awaitable()
}
}
try await taskGroup.waitForAll()
}
return threads
}
return .init(
preparedPromise: preparedPromise,
enqueuedPromise: enqueuedPromise,
sentPromise: sentPromise
let segmentedAttachments = try await segmentAttachmentsIfNecessary(
for: conversations,
approvedAttachments: approvedAttachments,
hasNonStoryDestination: hasNonStoryDestination,
hasStoryDestination: hasStoryDestination,
)
return try await deps.databaseStorage.awaitableWrite { tx in
let preparedMessages = try prepareMessages(
// Stories get an untruncated message body
forSendingMessageBodyForStories: approvedMessageBody,
approvedAttachments: segmentedAttachments,
isViewOnce: approvedAttachments.isViewOnce,
toDestinations: destinations,
tx: tx,
)
return preparedMessages.map {
let sendPromise = deps.messageSenderJobQueue.add(.promise, message: $0, transaction: tx)
return EnqueueResult(preparedMessage: $0, sendPromise: sendPromise)
}
}
}
public class func sendTextAttachment(
public class func enqueueTextAttachment(
_ textAttachment: UnsentTextAttachment,
to conversations: [ConversationItem]
) -> AttachmentMultisend.Result {
let (preparedPromise, preparedFuture) = Promise<[PreparedOutgoingMessage]>.pending()
let (enqueuedPromise, enqueuedFuture) = Promise<[TSThread]>.pending()
let sentPromise = Promise<[TSThread]>.wrapAsync {
// Prepare the text attachment
let textAttachment = try await textAttachment.validateAndPrepareForSending()
let threads: [TSThread]
let preparedMessages: [PreparedOutgoingMessage]
let sendPromises: [Promise<Void>]
do {
(threads, preparedMessages, sendPromises) = try await deps.databaseStorage.awaitableWrite { tx in
let threads: [TSThread]
let preparedMessages: [PreparedOutgoingMessage]
(threads, preparedMessages) = try prepareForSending(
conversations: conversations,
textAttachment,
tx: tx
)
let sendPromises: [Promise<Void>] = preparedMessages.map {
deps.messageSenderJobQueue.add(
.promise,
message: $0,
transaction: tx
)
}
return (threads, preparedMessages, sendPromises)
}
} catch let error {
preparedFuture.reject(error)
enqueuedFuture.reject(error)
throw error
}
preparedFuture.resolve(preparedMessages)
enqueuedFuture.resolve(threads)
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
sendPromises.forEach { promise in
taskGroup.addTask {
try await promise.awaitable()
}
}
try await taskGroup.waitForAll()
}
return threads
) async throws -> [EnqueueResult] {
if conversations.isEmpty {
return []
}
return .init(
preparedPromise: preparedPromise,
enqueuedPromise: enqueuedPromise,
sentPromise: sentPromise
)
// Prepare the text attachment
let textAttachment = try await textAttachment.validateAndPrepareForSending()
return try await deps.databaseStorage.awaitableWrite { tx in
let preparedMessages = try prepareMessages(
forSendingTextAttachment: textAttachment,
toConversations: conversations,
tx: tx,
)
return preparedMessages.map {
let sendPromise = deps.messageSenderJobQueue.add(.promise, message: $0, transaction: tx)
return EnqueueResult(preparedMessage: $0, sendPromise: sendPromise)
}
}
}
// MARK: - Dependencies
private struct Dependencies {
struct Dependencies {
let attachmentManager: AttachmentManager
let attachmentValidator: AttachmentContentValidator
let contactsMentionHydrator: ContactsMentionHydrator.Type
@ -178,7 +100,7 @@ public class AttachmentMultisend {
let tsAccountManager: TSAccountManager
}
private static var deps = Dependencies(
static let deps = Dependencies(
attachmentManager: DependenciesBridge.shared.attachmentManager,
attachmentValidator: DependenciesBridge.shared.attachmentContentValidator,
contactsMentionHydrator: ContactsMentionHydrator.self,
@ -300,13 +222,13 @@ public class AttachmentMultisend {
// MARK: - Preparing messages
private class func prepareForSending(
destinations: [Destination],
messageBodyForStories: MessageBody?,
private class func prepareMessages(
forSendingMessageBodyForStories messageBodyForStories: MessageBody?,
approvedAttachments: [SegmentAttachmentResult],
isViewOnce: Bool,
tx: DBWriteTransaction
) throws -> ([TSThread], [PreparedOutgoingMessage]) {
toDestinations destinations: [Destination],
tx: DBWriteTransaction,
) throws -> [PreparedOutgoingMessage] {
let segmentedAttachments = approvedAttachments.reduce([], { arr, segmented in
return arr + segmented.segmentedOrOriginal.map { ($0, segmented.renderingFlag == .shouldLoop) }
})
@ -366,17 +288,14 @@ public class AttachmentMultisend {
builders: storyMessageBuilders,
tx: tx
)
let preparedMessages = nonStoryMessages + groupStoryMessages + privateStoryMessages
let allThreads = nonStoryThreads.map(\.thread) + groupStoryThreads + privateStoryThreads
return (allThreads, preparedMessages)
return nonStoryMessages + groupStoryMessages + privateStoryMessages
}
private class func prepareForSending(
conversations: [ConversationItem],
_ textAttachment: UnsentTextAttachment.ForSending,
tx: DBWriteTransaction
) throws -> ([TSThread], [PreparedOutgoingMessage]) {
var allStoryThreads = [TSThread]()
private class func prepareMessages(
forSendingTextAttachment textAttachment: UnsentTextAttachment.ForSending,
toConversations conversations: [ConversationItem],
tx: DBWriteTransaction,
) throws -> [PreparedOutgoingMessage] {
var privateStoryThreads = [TSPrivateStoryThread]()
var groupStoryThreads = [TSGroupThread]()
for conversation in conversations {
@ -393,7 +312,6 @@ public class AttachmentMultisend {
case .storyMessage:
throw OWSAssertionError("Invalid story message target!")
}
allStoryThreads.append(thread)
}
let storyMessageBuilder = try storyMessageBuilder(
@ -413,8 +331,7 @@ public class AttachmentMultisend {
builders: [storyMessageBuilder],
tx: tx
)
let preparedMessages = groupStoryMessages + privateStoryMessages
return (allStoryThreads, preparedMessages)
return groupStoryMessages + privateStoryMessages
}
// MARK: Preparing Non-Story Messages

View File

@ -6,19 +6,15 @@
public import SignalServiceKit
public enum StorySharing {
public static func sendTextStory(
public static func enqueueTextStory(
with messageBody: MessageBody,
linkPreviewDraft: OWSLinkPreviewDraft?,
to conversations: [ConversationItem]
) -> AttachmentMultisend.Result? {
to conversations: [ConversationItem],
) async throws -> [AttachmentMultisend.EnqueueResult] {
let storyConversations = conversations.filter { $0.outgoingMessageType == .storyMessage }
owsAssertDebug(conversations.count == storyConversations.count)
guard !storyConversations.isEmpty else { return nil }
return AttachmentMultisend.sendTextAttachment(
return try await AttachmentMultisend.enqueueTextAttachment(
buildTextAttachment(with: messageBody, linkPreviewDraft: linkPreviewDraft),
to: storyConversations
to: storyConversations,
)
}