// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalUI import SignalCoreKit import SignalServiceKit protocol NewCallViewControllerDelegate: AnyObject { func goToChat(for thread: TSThread) } class NewCallViewController: RecipientPickerContainerViewController { weak var delegate: (any NewCallViewControllerDelegate)? override func viewDidLoad() { super.viewDidLoad() title = OWSLocalizedString( "NEW_CALL_TITLE", comment: "Title for the contact picker that allows new calls to be started" ) recipientPicker.allowsAddByAddress = true recipientPicker.shouldShowInvites = true recipientPicker.delegate = self addChild(recipientPicker) view.addSubview(recipientPicker.view) recipientPicker.view.autoPin(toTopLayoutGuideOf: self, withInset: 0) recipientPicker.view.autoPinEdge(toSuperviewEdge: .leading) recipientPicker.view.autoPinEdge(toSuperviewEdge: .trailing) recipientPicker.view.autoPinEdge(toSuperviewEdge: .bottom) navigationItem.leftBarButtonItem = .cancelButton(dismissingFrom: self) } private var callStarterContext: CallStarter.Context { .init( blockingManager: blockingManager, databaseStorage: databaseStorage, callService: AppEnvironment.shared.callService ) } private func startIndividualCall(thread: TSContactThread, withVideo: Bool) { self.startCall(callStarter: CallStarter( contactThread: thread, withVideo: withVideo, context: self.callStarterContext )) } private func startGroupCall(thread: TSGroupThread) { self.startCall(callStarter: CallStarter( groupThread: thread, context: self.callStarterContext )) } private func startCall(callStarter: CallStarter) { let startCallResult = callStarter.startCall(from: self) if startCallResult.callDidStartOrResume { self.dismiss(animated: false) } } } // MARK: - RecipientContextMenuHelperDelegate extension NewCallViewController: RecipientContextMenuHelperDelegate { private func goToChatAction(thread: TSThread) -> UIAction { UIAction( title: OWSLocalizedString( "NEW_CALL_MESSAGE_ACTION_TITLE", comment: "Title for a long-press context menu action to message a given recipient or group, triggered from a recipient in the New Call contact picker" ), image: Theme.iconImage(.contextMenuMessage) ) { [weak self] _ in self?.delegate?.goToChat(for: thread) } } private func startVoiceCallAction(thread: TSContactThread) -> UIAction { UIAction( title: OWSLocalizedString( "NEW_CALL_VOICE_CALL_ACTION_TITLE", comment: "Title for a long-press context menu action to start a voice call, triggered from a recipient in the New Call contact picker" ), image: Theme.iconImage(.contextMenuVoiceCall) ) { [weak self] _ in self?.startIndividualCall(thread: thread, withVideo: false) } } private func startVideoCallAction(handler: @escaping UIActionHandler) -> UIAction { UIAction( title: OWSLocalizedString( "NEW_CALL_VIDEO_CALL_ACTION_TITLE", comment: "Title for a long-press context menu action to start a video call, triggered from a recipient in the New Call contact picker" ), image: Theme.iconImage(.contextMenuVideoCall), handler: handler ) } func additionalActions(for address: SignalServiceAddress) -> [UIAction] { let thread = TSContactThread.getOrCreateThread(contactAddress: address) return [ goToChatAction(thread: thread), startVoiceCallAction(thread: thread), startVideoCallAction { [weak self] _ in self?.startIndividualCall(thread: thread, withVideo: true) }, ] } func additionalActions(for groupThread: TSGroupThread) -> [UIAction] { [ goToChatAction(thread: groupThread), startVideoCallAction { [weak self] _ in self?.startGroupCall(thread: groupThread) } ] } } // MARK: - RecipientPickerDelegate extension NewCallViewController: RecipientPickerDelegate, UsernameLinkScanDelegate { func recipientPicker(_ recipientPickerViewController: SignalUI.RecipientPickerViewController, getRecipientState recipient: SignalUI.PickedRecipient) -> SignalUI.RecipientPickerRecipientState { .canBeSelected } func recipientPicker(_ recipientPickerViewController: SignalUI.RecipientPickerViewController, didSelectRecipient recipient: SignalUI.PickedRecipient) { switch recipient.identifier { case let .address(address): let thread = TSContactThread.getOrCreateThread(contactAddress: address) startIndividualCall(thread: thread, withVideo: false) case let .group(groupThread): startGroupCall(thread: groupThread) } } func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, accessoryViewForRecipient recipient: PickedRecipient, transaction: SDSAnyReadTransaction) -> ContactCellAccessoryView? { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 20 stackView.tintColor = Theme.primaryTextColor switch recipient.identifier { case .address(let address): // This doesn't actually need to be hooked up to any action // since tapping the row already starts a voice call. let voiceCallImageView = UIImageView(image: Theme.iconImage(.buttonVoiceCall)) let videoCallButton = OWSButton( imageName: Theme.iconName(.buttonVideoCall), tintColor: nil ) { [weak self] in let thread = TSContactThread.getOrCreateThread(contactAddress: address) self?.startIndividualCall(thread: thread, withVideo: true) } stackView.addArrangedSubviews([ voiceCallImageView, videoCallButton, ]) case .group(_): stackView.addArrangedSubview(UIImageView(image: Theme.iconImage(.buttonVideoCall))) } return .init(accessoryView: stackView, size: .init(width: 24 * 2 + 20, height: 24)) } func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController, shouldAllowUserInteractionForRecipient recipient: PickedRecipient, transaction: SDSAnyReadTransaction) -> Bool { true } }