Signal-iOS/Signal/src/ViewControllers/CameraFirstCaptureSendFlow.swift

202 lines
7.6 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
protocol CameraFirstCaptureDelegate: AnyObject {
func cameraFirstCaptureSendFlowDidComplete(_ cameraFirstCaptureSendFlow: CameraFirstCaptureSendFlow)
func cameraFirstCaptureSendFlowDidCancel(_ cameraFirstCaptureSendFlow: CameraFirstCaptureSendFlow)
}
class CameraFirstCaptureSendFlow: Dependencies {
private weak var delegate: CameraFirstCaptureDelegate?
private var approvedAttachments: [SignalAttachment]?
private var approvalMessageBody: MessageBody?
private var textAttachment: UnsentTextAttachment?
private var mentionCandidates: [SignalServiceAddress] = []
private let selection = ConversationPickerSelection()
private var selectedConversations: [ConversationItem] { selection.conversations }
private let storiesOnly: Bool
private var showsStoriesInPicker = true
init(storiesOnly: Bool, delegate: CameraFirstCaptureDelegate) {
self.storiesOnly = storiesOnly
self.delegate = delegate
}
private func updateMentionCandidates() {
AssertIsOnMainThread()
guard selectedConversations.count == 1,
case .group(let groupThreadId) = selectedConversations.first?.messageRecipient else {
mentionCandidates = []
return
}
let groupThread = databaseStorage.read { readTx in
TSGroupThread.anyFetchGroupThread(uniqueId: groupThreadId, transaction: readTx)
}
owsAssertDebug(groupThread != nil)
if let groupThread = groupThread, groupThread.allowsMentionSend {
mentionCandidates = groupThread.recipientAddressesWithSneakyTransaction
} else {
mentionCandidates = []
}
}
}
extension CameraFirstCaptureSendFlow: SendMediaNavDelegate {
func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController) {
// Restore status bar visibility (if current VC hides it) so that
// there's no visible UI updates in the presenter.
if sendMediaNavigationController.topViewController?.prefersStatusBarHidden ?? false {
sendMediaNavigationController.modalPresentationCapturesStatusBarAppearance = false
sendMediaNavigationController.setNeedsStatusBarAppearanceUpdate()
}
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
}
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], messageBody: MessageBody?) {
self.approvedAttachments = attachments
self.approvalMessageBody = messageBody
let pickerVC = ConversationPickerViewController(
selection: selection,
attachments: attachments
)
pickerVC.pickerDelegate = self
pickerVC.shouldBatchUpdateIdentityKeys = true
if storiesOnly {
pickerVC.isStorySectionExpanded = true
pickerVC.sectionOptions = .storiesOnly
} else if showsStoriesInPicker {
pickerVC.sectionOptions.insert(.stories)
}
sendMediaNavigationController.pushViewController(pickerVC, animated: true)
}
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didFinishWithTextAttachment textAttachment: UnsentTextAttachment) {
self.textAttachment = textAttachment
let pickerVC = ConversationPickerViewController(selection: selection, textAttacment: textAttachment)
pickerVC.pickerDelegate = self
pickerVC.shouldBatchUpdateIdentityKeys = true
if showsStoriesInPicker || storiesOnly {
pickerVC.isStorySectionExpanded = true
pickerVC.sectionOptions = .storiesOnly
} else {
owsFailDebug("Shouldn't ever have stories disabled with text attachments!")
}
sendMediaNavigationController.pushViewController(pickerVC, animated: true)
}
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeMessageBody newMessageBody: MessageBody?) {
self.approvalMessageBody = newMessageBody
}
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeViewOnceState isViewOnce: Bool) {
guard !self.storiesOnly else { return }
// Don't enable view once media to send to stories.
self.showsStoriesInPicker = !isViewOnce
}
}
extension CameraFirstCaptureSendFlow: SendMediaNavDataSource {
func sendMediaNavInitialMessageBody(_ sendMediaNavigationController: SendMediaNavigationController) -> MessageBody? {
return approvalMessageBody
}
var sendMediaNavTextInputContextIdentifier: String? {
nil
}
var sendMediaNavRecipientNames: [String] {
selectedConversations.map { $0.titleWithSneakyTransaction }
}
func sendMediaNavMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
return mentionCandidates
}
func sendMediaNavMentionCacheInvalidationKey() -> String {
return "\(mentionCandidates.hashValue)"
}
}
extension CameraFirstCaptureSendFlow: ConversationPickerDelegate {
public func conversationPickerSelectionDidChange(_ conversationPickerViewController: ConversationPickerViewController) {
updateMentionCandidates()
}
public func conversationPickerDidCompleteSelection(_ conversationPickerViewController: ConversationPickerViewController) {
if let textAttachment = textAttachment {
let selectedStoryItems = selectedConversations.filter { $0 is StoryConversationItem }
guard !selectedStoryItems.isEmpty else {
owsFailDebug("Selection was unexpectedly empty.")
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
return
}
firstly {
TSResourceMultisend.sendTextAttachment(
textAttachment,
to: selectedStoryItems
).enqueuedPromise
}.done { _ in
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
}.catch { error in
owsFailDebug("Error: \(error)")
}
return
}
guard let approvedAttachments = self.approvedAttachments else {
owsFailDebug("approvedAttachments was unexpectedly nil")
delegate?.cameraFirstCaptureSendFlowDidCancel(self)
return
}
let conversations = selectedConversations
firstly {
TSResourceMultisend.sendApprovedMedia(
conversations: conversations,
approvalMessageBody: self.approvalMessageBody,
approvedAttachments: approvedAttachments
).enqueuedPromise
}.done { _ in
self.delegate?.cameraFirstCaptureSendFlowDidComplete(self)
}.catch { error in
owsFailDebug("Error: \(error)")
}
}
public func conversationPickerCanCancel(_ conversationPickerViewController: ConversationPickerViewController) -> Bool {
return false
}
public func conversationPickerDidCancel(_ conversationPickerViewController: ConversationPickerViewController) {
owsFailDebug("Camera-first capture flow should never cancel conversation picker.")
}
public func approvalMode(_ conversationPickerViewController: ConversationPickerViewController) -> ApprovalMode {
return .send
}
public func conversationPickerDidBeginEditingText() {}
public func conversationPickerSearchBarActiveDidChange(_ conversationPickerViewController: ConversationPickerViewController) {}
}