Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/ConversationView/ConversationViewController+VoiceMessage.swift
1 views
//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
import SignalServiceKit
import SignalUI

extension ConversationViewController {
    func checkPermissionsAndStartRecordingVoiceMessage() {
        AssertIsOnMainThread()

        // Cancel any ongoing audio playback.
        AppEnvironment.shared.cvAudioPlayerRef.stopAll()

        let inProgressVoiceMessage = VoiceMessageInProgressDraft(
            thread: thread,
            audioSession: SUIEnvironment.shared.audioSessionRef,
            sleepManager: DependenciesBridge.shared.deviceSleepManager!,
        )
        viewState.inProgressVoiceMessage = inProgressVoiceMessage

        // Delay showing the voice memo UI for N ms to avoid a jarring transition
        // when you just tap and don't hold.
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
            guard let self else { return }
            guard self.viewState.inProgressVoiceMessage === inProgressVoiceMessage else { return }
            self.configureScrollDownButtons()
            self.inputToolbar?.showVoiceMemoUI()
        }

        ows_askForMicrophonePermissions { [weak self] granted in
            guard let self else { return }
            guard self.viewState.inProgressVoiceMessage === inProgressVoiceMessage else { return }

            guard granted else {
                self.cancelRecordingVoiceMessage()
                self.ows_showNoMicrophonePermissionActionSheet()
                return
            }

            self.startRecordingVoiceMessage(inProgressVoiceMessage)
        }
    }

    private func startRecordingVoiceMessage(_ inProgressVoiceMessage: VoiceMessageInProgressDraft) {
        AssertIsOnMainThread()

        ImpactHapticFeedback.impactOccurred(style: .light)

        do {
            try inProgressVoiceMessage.startRecording()
        } catch {
            owsFailDebug("Failed to start recording voice message \(error)")
            cancelRecordingVoiceMessage()
        }
    }

    func cancelRecordingVoiceMessage() {
        AssertIsOnMainThread()

        viewState.inProgressVoiceMessage?.stopRecordingAsync()
        viewState.inProgressVoiceMessage = nil

        NotificationHapticFeedback().notificationOccurred(.warning)

        clearVoiceMessageDraft()
        inputToolbar?.hideVoiceMemoUI(animated: true)
        configureScrollDownButtons()
    }

    private static let minimumVoiceMessageDuration: TimeInterval = 1

    func finishRecordingVoiceMessage(sendImmediately: Bool = false) {
        AssertIsOnMainThread()

        guard let inProgressVoiceMessage = viewState.inProgressVoiceMessage else { return }
        viewState.inProgressVoiceMessage = nil

        inProgressVoiceMessage.stopRecording()

        guard let duration = inProgressVoiceMessage.duration, duration >= Self.minimumVoiceMessageDuration else {
            inputToolbar?.showVoiceMemoTooltip()
            cancelRecordingVoiceMessage()
            return
        }

        ImpactHapticFeedback.impactOccurred(style: .medium)

        configureScrollDownButtons()

        if sendImmediately {
            sendVoiceMessageDraft(inProgressVoiceMessage)
        } else {
            SSKEnvironment.shared.databaseStorageRef.asyncWrite {
                let interruptedDraft = inProgressVoiceMessage.convertToDraft(transaction: $0)
                DispatchQueue.main.async {
                    self.inputToolbar?.showVoiceMemoDraft(interruptedDraft)
                }
            }
        }
    }

    func sendVoiceMessageDraft(_ voiceMemoDraft: VoiceMessageSendableDraft) {
        inputToolbar?.hideVoiceMemoUI(animated: true)

        let attachmentLimits = OutgoingAttachmentLimits.currentLimits()

        do {
            let attachment = try voiceMemoDraft.prepareAttachment(attachmentLimits: attachmentLimits)
            Task { @MainActor in
                await self.sendAttachments(
                    ApprovedAttachments(nonViewOnceAttachments: [attachment], imageQuality: .standard),
                    messageBody: nil,
                    from: self,
                    attachmentLimits: attachmentLimits,
                )
                clearVoiceMessageDraft()
            }
        } catch {
            self.showErrorAlert(attachmentError: error as? SignalAttachmentError)
        }
    }

    func clearVoiceMessageDraft() {
        SSKEnvironment.shared.databaseStorageRef.asyncWrite { [thread] in
            VoiceMessageInterruptedDraftStore.clearDraft(for: thread, transaction: $0)
        }
    }
}