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

import SignalServiceKit
import SignalUI

class AdvancedPrivacySettingsViewController: OWSTableViewController2 {
    override func viewDidLoad() {
        super.viewDidLoad()

        title = OWSLocalizedString(
            "SETTINGS_PRIVACY_ADVANCED_TITLE",
            comment: "Title for the advanced privacy settings",
        )

        updateTableContents()

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateTableContents),
            name: OWSChatConnection.chatConnectionStateDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateTableContents),
            name: SSKReachability.owsReachabilityDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateTableContents),
            name: .syncManagerConfigurationSyncDidComplete,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(updateTableContents),
            name: .isSignalProxyReadyDidChange,
            object: nil,
        )
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        updateTableContents()
    }

    @objc
    private func updateTableContents() {
        let contents = OWSTableContents()

        let censorshipCircumventionSection = OWSTableSection()
        let isCensorshipCircumventionSwitchEnabled: Bool

        if SSKEnvironment.shared.signalServiceRef.hasCensoredPhoneNumber {
            isCensorshipCircumventionSwitchEnabled = true
            if SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionManuallyDisabled {
                censorshipCircumventionSection.footerTitle = OWSLocalizedString(
                    "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_MANUALLY_DISABLED",
                    comment: "Table footer for the 'censorship circumvention' section shown when censorship circumvention has been manually disabled.",
                )
            } else {
                censorshipCircumventionSection.footerTitle = OWSLocalizedString(
                    "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_AUTO_ENABLED",
                    comment: "Table footer for the 'censorship circumvention' section shown when censorship circumvention has been auto-enabled based on local phone number.",
                )
            }
        } else if
            !SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionActive,
            DependenciesBridge.shared.chatConnectionManager.unidentifiedConnectionState == .open
        {
            isCensorshipCircumventionSwitchEnabled = false
            censorshipCircumventionSection.footerTitle = OWSLocalizedString(
                "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_WEBSOCKET_CONNECTED",
                comment: "Table footer for the 'censorship circumvention' section shown when the app is connected to the Signal service.",
            )
        } else if !SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionActive, !SSKEnvironment.shared.reachabilityManagerRef.isReachable {
            isCensorshipCircumventionSwitchEnabled = false
            censorshipCircumventionSection.footerTitle = OWSLocalizedString(
                "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER_NO_CONNECTION",
                comment: "Table footer for the 'censorship circumvention' section shown when the app is not connected to the internet.",
            )
        } else {
            isCensorshipCircumventionSwitchEnabled = true
            censorshipCircumventionSection.footerTitle = OWSLocalizedString(
                "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_FOOTER",
                comment: "Table footer for the 'censorship circumvention' section when censorship circumvention can be manually enabled.",
            )
        }

        censorshipCircumventionSection.add(.switch(
            withText: OWSLocalizedString(
                "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION",
                comment: "Label for the 'manual censorship circumvention' switch.",
            ),
            isOn: { SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionActive },
            isEnabled: { isCensorshipCircumventionSwitchEnabled || DebugFlags.exposeCensorshipCircumvention },
            target: self,
            selector: #selector(didToggleEnableCensorshipCircumventionSwitch),
        ))

        if SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionManuallyActivated {
            censorshipCircumventionSection.add(.disclosureItem(
                withText: OWSLocalizedString(
                    "SETTINGS_ADVANCED_CENSORSHIP_CIRCUMVENTION_COUNTRY",
                    comment: "Label for the 'manual censorship circumvention' country.",
                ),
                accessoryText: ensureManualCensorshipCircumventionCountry().localizedCountryName,
                actionBlock: { [weak self] in
                    let vc = DomainFrontingCountryViewController()
                    self?.navigationController?.pushViewController(vc, animated: true)
                },
            ))
        }

        contents.add(censorshipCircumventionSection)

        let proxySection = OWSTableSection()
        proxySection.footerAttributedTitle = .composed(of: [
            OWSLocalizedString("USE_PROXY_EXPLANATION", comment: "Explanation of when you should use a signal proxy"),
            " ",
            CommonStrings.learnMore.styled(with: .link(URL.Support.proxies)),
        ])
        .styled(with: defaultFooterTextStyle)

        proxySection.add(.disclosureItem(
            withText: OWSLocalizedString(
                "PROXY_SETTINGS_TITLE",
                comment: "Title for the signal proxy settings",
            ),
            accessoryText: SignalProxy.isEnabled ? CommonStrings.switchOn : CommonStrings.switchOff,
            actionBlock: { [weak self] in
                let vc = ProxySettingsViewController()
                self?.navigationController?.pushViewController(vc, animated: true)
            },
        ))
        contents.add(proxySection)

        let relayCallsSection = OWSTableSection()
        relayCallsSection.footerTitle = OWSLocalizedString(
            "SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE_DETAIL",
            comment: "User settings section footer, a detailed explanation",
        )
        relayCallsSection.add(.switch(
            withText: OWSLocalizedString(
                "SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE",
                comment: "Table cell label",
            ),
            isOn: { SSKEnvironment.shared.preferencesRef.doCallsHideIPAddress },
            target: self,
            selector: #selector(didToggleCallsHideIPAddressSwitch),
        ))
        contents.add(relayCallsSection)

        if BuildFlags.KeyTransparency.enabled {
            let keyTransparencySection = OWSTableSection()

            keyTransparencySection.add(.switch(
                withText: OWSLocalizedString(
                    "SETTINGS_KEY_TRANSPARENCY_OPT_OUT_SWITCH_TITLE",
                    comment: "Title for for a settings option describing that Key Transparency is enabled.",
                ),
                isOn: {
                    let db = DependenciesBridge.shared.db
                    let keyTransparencyManager = DependenciesBridge.shared.keyTransparencyManager
                    return db.read { tx in
                        keyTransparencyManager.isEnabled(tx: tx)
                    }
                },
                actionBlock: { uiSwitch in
                    let db = DependenciesBridge.shared.db
                    let keyTransparencyManager = DependenciesBridge.shared.keyTransparencyManager
                    db.write { tx in
                        keyTransparencyManager.setIsEnabled(
                            uiSwitch.isOn,
                            updateStorageService: true,
                            tx: tx,
                        )
                    }
                },
            ))
            keyTransparencySection.footerAttributedTitle = NSAttributedString.composed(of: [
                OWSLocalizedString(
                    "SETTINGS_KEY_TRANSPARENCY_OPT_OUT_FOOTER",
                    comment: "Footer for settings section describing Key Transparency.",
                ),
                " ",
                CommonStrings.learnMore.styled(with: .link(URL.Support.keyTransparency)),
            ]).styled(with: defaultFooterTextStyle)

            contents.add(keyTransparencySection)
        }

        let sealedSenderSection = OWSTableSection()
        sealedSenderSection.headerTitle = OWSLocalizedString(
            "SETTINGS_UNIDENTIFIED_DELIVERY_SECTION_TITLE",
            comment: "table section label",
        )
        sealedSenderSection.add(.init(
            customCellBlock: { [weak self] in
                let cell = OWSTableItem.newCell()
                guard let self else { return cell }
                cell.selectionStyle = .none

                let stackView = UIStackView()
                stackView.axis = .horizontal
                stackView.spacing = 8
                cell.contentView.addSubview(stackView)
                stackView.autoPinEdgesToSuperviewMargins()

                let nameLabel = UILabel()
                nameLabel.text = OWSLocalizedString("SETTINGS_UNIDENTIFIED_DELIVERY_SHOW_INDICATORS", comment: "switch label")
                nameLabel.textColor = Theme.primaryTextColor
                nameLabel.font = OWSTableItem.primaryLabelFont
                nameLabel.adjustsFontForContentSizeCategory = true
                nameLabel.numberOfLines = 0
                nameLabel.lineBreakMode = .byWordWrapping
                nameLabel.setCompressionResistanceHorizontalHigh()
                nameLabel.setContentHuggingHorizontalHigh()
                stackView.addArrangedSubview(nameLabel)

                let imageView = UIImageView()
                imageView.contentMode = .center
                imageView.setTemplateImageName(Theme.iconName(.sealedSenderIndicator), tintColor: Theme.primaryTextColor)
                imageView.autoSetDimension(.width, toSize: 20)
                stackView.addArrangedSubview(imageView)

                stackView.addArrangedSubview(.hStretchingSpacer())

                // Leave space for the switch.
                stackView.addArrangedSubview(.spacer(withWidth: 60))

                let cellSwitch = UISwitch()
                cellSwitch.isOn = SSKEnvironment.shared.preferencesRef.shouldShowUnidentifiedDeliveryIndicators
                cellSwitch.addTarget(self, action: #selector(self.didToggleUDShowIndicatorsSwitch), for: .valueChanged)
                cell.accessoryView = cellSwitch

                return cell
            },
            actionBlock: {

            },
        ))

        if DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegisteredPrimaryDevice {
            sealedSenderSection.add(.switch(
                withText: OWSLocalizedString(
                    "SETTINGS_UNIDENTIFIED_DELIVERY_UNRESTRICTED_ACCESS",
                    comment: "switch label",
                ),
                isOn: { SSKEnvironment.shared.udManagerRef.shouldAllowUnrestrictedAccessLocal() },
                target: self,
                selector: #selector(didToggleUDUnrestrictedAccessSwitch),
            ))
            sealedSenderSection.footerAttributedTitle = NSAttributedString.composed(of: [
                OWSLocalizedString(
                    "SETTINGS_UNIDENTIFIED_DELIVERY_UNRESTRICTED_ACCESS_FOOTER",
                    comment: "table section footer",
                ),
                " ",
                CommonStrings.learnMore.styled(
                    with: .link(URL(string: "https://signal.org/blog/sealed-sender/")!),
                ),
            ])
            .styled(with: defaultFooterTextStyle)

        }

        contents.add(sealedSenderSection)

        self.contents = contents
    }

    override func themeDidChange() {
        super.themeDidChange()
        updateTableContents()
    }

    @objc
    private func didToggleEnableCensorshipCircumventionSwitch(_ sender: UISwitch) {
        SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionManuallyDisabled = !sender.isOn
        SSKEnvironment.shared.signalServiceRef.isCensorshipCircumventionManuallyActivated = sender.isOn
        updateTableContents()
    }

    private func ensureManualCensorshipCircumventionCountry() -> OWSCountryMetadata {
        let preferredCountryCode = SSKEnvironment.shared.signalServiceRef.manualCensorshipCircumventionCountryCode ?? Locale.current.regionCode

        if
            let preferredCountryCode,
            let countryMetadata = OWSCountryMetadata.countryMetadata(countryCode: preferredCountryCode)
        {
            SSKEnvironment.shared.signalServiceRef.manualCensorshipCircumventionCountryCode = preferredCountryCode
            return countryMetadata
        } else {
            let fallbackCountryCode = "US"
            SSKEnvironment.shared.signalServiceRef.manualCensorshipCircumventionCountryCode = fallbackCountryCode
            return OWSCountryMetadata.countryMetadata(countryCode: fallbackCountryCode)!
        }
    }

    @objc
    private func didToggleCallsHideIPAddressSwitch(_ sender: UISwitch) {
        SSKEnvironment.shared.preferencesRef.setDoCallsHideIPAddress(sender.isOn)
    }

    @objc
    private func didToggleUDShowIndicatorsSwitch(_ sender: UISwitch) {
        SSKEnvironment.shared.preferencesRef.setShouldShowUnidentifiedDeliveryIndicatorsAndSendSyncMessage(sender.isOn)
    }

    @objc
    private func didToggleUDUnrestrictedAccessSwitch(_ sender: UISwitch) {
        SSKEnvironment.shared.udManagerRef.setShouldAllowUnrestrictedAccessLocal(sender.isOn)
    }
}