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

import UniformTypeIdentifiers

import SignalServiceKit
import SignalUI

class NotificationSettingsSoundViewController: OWSTableViewController2 {
    private let thread: TSThread?
    private let completion: (() -> Void)?

    private let originalNotificationSound: Sound
    private lazy var notificationSound: Sound = originalNotificationSound

    init(thread: TSThread? = nil, completion: (() -> Void)? = nil) {
        self.thread = thread
        self.completion = completion

        if let thread {
            self.originalNotificationSound = Sounds.notificationSoundWithSneakyTransaction(forThreadUniqueId: thread.uniqueId)
        } else {
            self.originalNotificationSound = Sounds.globalNotificationSound
        }

        super.init()
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = OWSLocalizedString(
            "SETTINGS_ITEM_NOTIFICATION_SOUND",
            comment: "Label for settings view that allows user to change the notification sound.",
        )

        navigationItem.leftBarButtonItem = .cancelButton { [weak self] in
            self?.didTapCancel()
        }

        navigationItem.rightBarButtonItem = .setButton { [weak self] in
            self?.didTapDone()
        }

        updateTableContents()
        updateNavigation()
    }

    private var hasUnsavedChanges: Bool {
        notificationSound != originalNotificationSound
    }

    // Don't allow interactive dismiss when there are unsaved changes.
    override var isModalInPresentation: Bool {
        get { hasUnsavedChanges }
        set {}
    }

    private func updateNavigation() {
        navigationItem.rightBarButtonItem?.isEnabled = hasUnsavedChanges
    }

    func updateTableContents() {
        let contents = OWSTableContents()

        let section = OWSTableSection()

        for sound in Sounds.allNotificationSounds {
            let soundName: String
            if sound == .standard(.note) {
                soundName = String.nonPluralLocalizedStringWithFormat(
                    OWSLocalizedString(
                        "SETTINGS_AUDIO_DEFAULT_TONE_LABEL_FORMAT",
                        comment: "Format string for the default 'Note' sound. Embeds the system {{sound name}}.",
                    ),
                    sound.displayName,
                )
            } else {
                soundName = sound.displayName
            }

            section.add(.init(
                text: soundName,
                actionBlock: { [weak self] in
                    self?.soundWasSelected(sound)
                },
                accessoryType: sound == notificationSound ? .checkmark : .none,
            ))
        }

        section.add(.disclosureItem(
            withText: OWSLocalizedString(
                "NOTIFICATIONS_SECTION_SOUNDS_ADD_CUSTOM_SOUND",
                comment: "Label for settings UI that allows user to add a new notification sound.",
            ),
            actionBlock: { [weak self] in
                guard let self else { return }
                var contentTypes: [UTType] = [.wav, .aiff, .mp3]
                for additionalUti in [
                    "public.aifc-audio",
                    "com.apple.coreaudio-format",
                    "com.apple.mpeg-4-ringtone",
                ] {
                    if let element = UTType(additionalUti) {
                        contentTypes.append(element)
                    }
                }
                let picker = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes, asCopy: true)
                picker.delegate = self
                self.present(picker, animated: true)
            },
        ))

        contents.add(section)

        self.contents = contents
    }

    private var player: AudioPlayer?
    private func soundWasSelected(_ sound: Sound) {
        player?.stop()
        player = Sounds.audioPlayer(forSound: sound, audioBehavior: .playback)
        player?.isLooping = false
        player?.play()

        guard notificationSound != sound else { return }

        notificationSound = sound
        updateTableContents()
        updateNavigation()
    }

    private func stopPlayingAndDismiss() {
        player?.stop()
        dismiss(animated: true)
        completion?()
    }

    private func didTapCancel() {
        guard hasUnsavedChanges else {
            stopPlayingAndDismiss()
            return
        }

        OWSActionSheets.showPendingChangesActionSheet(discardAction: { [weak self] in
            self?.stopPlayingAndDismiss()
        })
    }

    private func didTapDone() {
        if let thread {
            Sounds.setNotificationSound(notificationSound, forThread: thread)
        } else {
            Sounds.setGlobalNotificationSound(notificationSound)
        }

        stopPlayingAndDismiss()
    }
}

extension NotificationSettingsSoundViewController: UIDocumentPickerDelegate {
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        Sounds.importSoundsAtUrls(urls)
        updateTableContents()
    }
}