247 lines
9.0 KiB
Swift
247 lines
9.0 KiB
Swift
//
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import AVFAudio
|
|
import SignalServiceKit
|
|
import SignalUI
|
|
|
|
extension ConversationViewController: MessageActionsDelegate {
|
|
|
|
func messageActionsEditItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
populateMessageEdit(itemViewModel)
|
|
}
|
|
|
|
func populateMessageEdit(_ itemViewModel: CVItemViewModelImpl) {
|
|
guard let message = itemViewModel.interaction as? TSOutgoingMessage else {
|
|
return owsFailDebug("Invalid interaction.")
|
|
}
|
|
|
|
var editValidationError: EditSendValidationError?
|
|
var quotedReplyModel: DraftQuotedReplyModel?
|
|
Self.databaseStorage.read { transaction in
|
|
|
|
// If edit send validation fails (timeframe expired,
|
|
// too many edits, etc), display a message here.
|
|
if let error = context.editManager.validateCanSendEdit(
|
|
targetMessageTimestamp: message.timestamp,
|
|
thread: self.thread,
|
|
tx: transaction.asV2Read
|
|
) {
|
|
editValidationError = error
|
|
return
|
|
}
|
|
|
|
if let quotedMessage = message.quotedMessage {
|
|
let originalMessage = (quotedMessage.timestampValue?.uint64Value).flatMap {
|
|
return InteractionFinder.findMessage(
|
|
withTimestamp: $0,
|
|
threadId: message.uniqueThreadId,
|
|
author: quotedMessage.authorAddress,
|
|
transaction: transaction
|
|
)
|
|
}
|
|
if
|
|
let originalMessage,
|
|
originalMessage is OWSPaymentMessage
|
|
{
|
|
quotedReplyModel = DraftQuotedReplyModel.forEditingOriginalPaymentMessage(
|
|
originalMessage: originalMessage,
|
|
replyMessage: message,
|
|
quotedReply: quotedMessage,
|
|
tx: transaction
|
|
)
|
|
} else {
|
|
quotedReplyModel = DependenciesBridge.shared.quotedReplyManager.buildDraftQuotedReplyForEditing(
|
|
quotedReplyMessage: message,
|
|
quotedReply: quotedMessage,
|
|
originalMessage: originalMessage,
|
|
tx: transaction.asV2Read
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let editValidationError {
|
|
OWSActionSheets.showActionSheet(message: editValidationError.localizedDescription)
|
|
} else {
|
|
inputToolbar?.quotedReplyDraft = quotedReplyModel
|
|
inputToolbar?.editTarget = message
|
|
|
|
inputToolbar?.editThumbnail = nil
|
|
if let imageStream = itemViewModel.bodyMediaAttachmentStreams.first(where: \.isValidImage) {
|
|
imageStream.thumbnailImageSmall { [weak inputToolbar = self.inputToolbar] image in
|
|
// If editing already ended, don't set it
|
|
guard let inputToolbar,
|
|
inputToolbar.shouldShowEditUI else { return }
|
|
inputToolbar.editThumbnail = image
|
|
} failure: {
|
|
owsFailDebug("Could not load thumnail.")
|
|
}
|
|
}
|
|
|
|
inputToolbar?.beginEditingMessage()
|
|
}
|
|
}
|
|
|
|
func messageActionsShowDetailsForItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
showDetailView(itemViewModel)
|
|
}
|
|
|
|
func prepareDetailViewForInteractivePresentation(_ itemViewModel: CVItemViewModelImpl) {
|
|
AssertIsOnMainThread()
|
|
|
|
guard let message = itemViewModel.interaction as? TSMessage else {
|
|
return owsFailDebug("Invalid interaction.")
|
|
}
|
|
|
|
guard let panHandler = panHandler else {
|
|
return owsFailDebug("Missing panHandler")
|
|
}
|
|
|
|
let detailVC = MessageDetailViewController(
|
|
message: message,
|
|
spoilerState: self.viewState.spoilerState,
|
|
editManager: self.context.editManager,
|
|
thread: thread
|
|
)
|
|
detailVC.detailDelegate = self
|
|
conversationSplitViewController?.navigationTransitionDelegate = detailVC
|
|
panHandler.messageDetailViewController = detailVC
|
|
}
|
|
|
|
func showDetailView(_ itemViewModel: CVItemViewModelImpl) {
|
|
AssertIsOnMainThread()
|
|
|
|
guard let message = itemViewModel.interaction as? TSMessage else {
|
|
owsFailDebug("Invalid interaction.")
|
|
return
|
|
}
|
|
|
|
let panHandler = viewState.panHandler
|
|
|
|
let detailVC: MessageDetailViewController
|
|
if let panHandler = panHandler,
|
|
let messageDetailViewController = panHandler.messageDetailViewController,
|
|
messageDetailViewController.message.uniqueId == message.uniqueId {
|
|
detailVC = messageDetailViewController
|
|
detailVC.pushPercentDrivenTransition = panHandler.percentDrivenTransition
|
|
} else {
|
|
detailVC = MessageDetailViewController(
|
|
message: message,
|
|
spoilerState: self.viewState.spoilerState,
|
|
editManager: self.context.editManager,
|
|
thread: thread
|
|
)
|
|
detailVC.detailDelegate = self
|
|
conversationSplitViewController?.navigationTransitionDelegate = detailVC
|
|
}
|
|
|
|
navigationController?.pushViewController(detailVC, animated: true)
|
|
}
|
|
|
|
func messageActionsReplyToItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
populateReplyForMessage(itemViewModel)
|
|
}
|
|
|
|
public func populateReplyForMessage(_ itemViewModel: CVItemViewModelImpl) {
|
|
AssertIsOnMainThread()
|
|
|
|
if DebugFlags.internalLogging {
|
|
Logger.info("")
|
|
}
|
|
guard let inputToolbar = inputToolbar else {
|
|
owsFailDebug("Missing inputToolbar.")
|
|
return
|
|
}
|
|
|
|
self.uiMode = .normal
|
|
|
|
let load: () -> DraftQuotedReplyModel? = {
|
|
guard let message = itemViewModel.interaction as? TSMessage else {
|
|
return nil
|
|
}
|
|
return Self.databaseStorage.read { transaction in
|
|
if message is OWSPaymentMessage {
|
|
return DraftQuotedReplyModel.fromOriginalPaymentMessage(message, tx: transaction)
|
|
}
|
|
return DependenciesBridge.shared.quotedReplyManager.buildDraftQuotedReply(
|
|
originalMessage: message,
|
|
tx: transaction.asV2Read
|
|
)
|
|
}
|
|
}
|
|
guard let quotedReply = load() else {
|
|
owsFailDebug("Could not build quoted reply.")
|
|
return
|
|
}
|
|
|
|
inputToolbar.editTarget = nil
|
|
inputToolbar.quotedReplyDraft = quotedReply
|
|
inputToolbar.beginEditingMessage()
|
|
}
|
|
|
|
func messageActionsForwardItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
AssertIsOnMainThread()
|
|
|
|
ForwardMessageViewController.present(forItemViewModels: [itemViewModel],
|
|
from: self,
|
|
delegate: self)
|
|
}
|
|
|
|
func messageActionsStartedSelect(initialItem itemViewModel: CVItemViewModelImpl) {
|
|
uiMode = .selection
|
|
|
|
selectionState.add(itemViewModel: itemViewModel, selectionType: .allContent)
|
|
}
|
|
|
|
func messageActionsDeleteItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
itemViewModel.interaction.presentDeletionActionSheet(from: self)
|
|
}
|
|
|
|
func messageActionsSpeakItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
guard let textValue = itemViewModel.displayableBodyText?.fullTextValue else {
|
|
return
|
|
}
|
|
|
|
let utterance: AVSpeechUtterance = {
|
|
switch textValue {
|
|
case .text(let text):
|
|
return AVSpeechUtterance(string: text)
|
|
case .attributedText(let attributedText):
|
|
return AVSpeechUtterance(attributedString: attributedText)
|
|
case .messageBody(let messageBody):
|
|
return messageBody.utterance
|
|
}
|
|
}()
|
|
|
|
self.speechManager.speak(utterance)
|
|
}
|
|
|
|
func messageActionsStopSpeakingItem(_ itemViewModel: CVItemViewModelImpl) {
|
|
self.speechManager.stop()
|
|
}
|
|
|
|
func messageActionsShowPaymentDetails(_ itemViewModel: CVItemViewModelImpl) {
|
|
guard let model = itemViewModel.paymentAttachment?.model else {
|
|
owsFailDebug("We should have a matching TSPaymentModel at this point")
|
|
return
|
|
}
|
|
|
|
guard let contactAddress = (thread as? TSContactThread)?.contactAddress else {
|
|
owsFailDebug("Should be contact thread")
|
|
return
|
|
}
|
|
let contactName = databaseStorage.read { tx in
|
|
return self.contactsManager.displayName(for: contactAddress, tx: tx).resolvedValue()
|
|
}
|
|
|
|
let paymentHistoryItem = PaymentsHistoryItem(paymentModel: model, displayName: contactName)
|
|
let paymentsDetailViewController = PaymentsDetailViewController(
|
|
paymentItem: paymentHistoryItem
|
|
)
|
|
navigationController?.pushViewController(paymentsDetailViewController, animated: true)
|
|
}
|
|
}
|