Path: blob/main/Signal/Registration/UserInterface/RegistrationPhoneNumberDiscoverabilityViewController.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
protocol RegistrationPhoneNumberDiscoverabilityPresenter: AnyObject {
func setPhoneNumberDiscoverability(_ phoneNumberDiscoverability: PhoneNumberDiscoverability)
var presentedAsModal: Bool { get }
}
public struct RegistrationPhoneNumberDiscoverabilityState: Equatable {
let e164: E164
let phoneNumberDiscoverability: PhoneNumberDiscoverability
}
class RegistrationPhoneNumberDiscoverabilityViewController: OWSViewController {
private let state: RegistrationPhoneNumberDiscoverabilityState
private weak var presenter: RegistrationPhoneNumberDiscoverabilityPresenter?
init(
state: RegistrationPhoneNumberDiscoverabilityState,
presenter: RegistrationPhoneNumberDiscoverabilityPresenter,
) {
self.state = state
self.presenter = presenter
self.phoneNumberDiscoverability = state.phoneNumberDiscoverability
super.init()
navigationItem.hidesBackButton = true
}
@available(*, unavailable)
override init() {
owsFail("This should not be called")
}
// MARK: State
private var phoneNumberDiscoverability: PhoneNumberDiscoverability {
didSet { update() }
}
// MARK: UI
private lazy var everybodyButton: UIButton = createButtonForDiscoverability(.everybody)
private lazy var nobodyButton: UIButton = createButtonForDiscoverability(.nobody)
private func createButtonForDiscoverability(_ phoneNumberDiscoverability: PhoneNumberDiscoverability) -> UIButton {
let button = PrivacySettingButton(phoneNumberDiscoverability: phoneNumberDiscoverability)
button.addAction(
UIAction { [weak self] _ in
self?.phoneNumberDiscoverability = phoneNumberDiscoverability
},
for: .primaryActionTriggered,
)
return button
}
private lazy var selectionDescriptionLabel: UILabel = {
let label = UILabel.explanationLabelForRegistration(text: "")
label.font = .dynamicTypeFootnoteClamped
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .Signal.background
navigationItem.setHidesBackButton(true, animated: false)
if !(presenter?.presentedAsModal ?? true) {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: CommonStrings.nextButton,
style: .done,
target: self,
action: #selector(didTapSave),
accessibilityIdentifier: "registration.phoneNumberDiscoverability.nextButton",
)
}
let titleLabel = UILabel.titleLabelForRegistration(text: OWSLocalizedString(
"ONBOARDING_PHONE_NUMBER_DISCOVERABILITY_TITLE",
comment: "Title of the 'onboarding phone number discoverability' view.",
))
titleLabel.accessibilityIdentifier = "registration.phoneNumberDiscoverability.titleLabel"
let formattedPhoneNumber = state.e164.stringValue
let explanationTextFormat = OWSLocalizedString(
"ONBOARDING_PHONE_NUMBER_DISCOVERABILITY_EXPLANATION_FORMAT",
comment: "Explanation of the 'onboarding phone number discoverability' view. Embeds {user phone number}",
)
let subtitleLabel = UILabel.explanationLabelForRegistration(
text: String.nonPluralLocalizedStringWithFormat(explanationTextFormat, formattedPhoneNumber),
)
subtitleLabel.accessibilityIdentifier = "registration.phoneNumberDiscoverability.explanationLabel"
let stackView = addStaticContentStackView(arrangedSubviews: [
titleLabel,
subtitleLabel,
everybodyButton,
nobodyButton,
selectionDescriptionLabel,
.vStretchingSpacer(),
])
if presenter?.presentedAsModal ?? false {
let continueButton = UIButton(
configuration: .largePrimary(title: CommonStrings.continueButton),
primaryAction: UIAction { [weak self] _ in
self?.didTapSave()
},
)
continueButton.accessibilityIdentifier = "registration.phoneNumberDiscoverability.saveButton"
stackView.addArrangedSubview(continueButton.enclosedInVerticalStackView(isFullWidthButton: true))
}
stackView.spacing = 16
stackView.setCustomSpacing(24, after: subtitleLabel)
update()
}
private func update() {
everybodyButton.isSelected = phoneNumberDiscoverability == .everybody
nobodyButton.isSelected = phoneNumberDiscoverability == .nobody
selectionDescriptionLabel.text = phoneNumberDiscoverability.descriptionForDiscoverability
}
// MARK: Events
@objc
private func didTapSave() {
presenter?.setPhoneNumberDiscoverability(phoneNumberDiscoverability)
}
}
// MARK: - Privacy setting buttons
private extension RegistrationPhoneNumberDiscoverabilityViewController {
private class PrivacySettingButton: UIButton {
private lazy var contentView = PrivacySettingButtonContentView(
configuration: .init(phoneNumberDiscoverability: phoneNumberDiscoverability),
)
var phoneNumberDiscoverability: PhoneNumberDiscoverability {
didSet {
contentView.configuration = PrivacySettingButtonContentConfiguration(
phoneNumberDiscoverability: phoneNumberDiscoverability,
)
}
}
override var isSelected: Bool {
didSet {
contentView.configuration = PrivacySettingButtonContentConfiguration(
phoneNumberDiscoverability: phoneNumberDiscoverability,
isSelected: isSelected,
)
}
}
init(phoneNumberDiscoverability: PhoneNumberDiscoverability) {
self.phoneNumberDiscoverability = phoneNumberDiscoverability
super.init(frame: .zero)
configuration = .filled()
configuration?.baseBackgroundColor = .Signal.background
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Accessibility
override var accessibilityLabel: String? {
get { phoneNumberDiscoverability.nameForDiscoverability }
set { super.accessibilityLabel = newValue }
}
override var accessibilityHint: String? {
get { phoneNumberDiscoverability.descriptionForDiscoverability }
set { super.accessibilityHint = newValue }
}
}
private struct PrivacySettingButtonContentConfiguration: UIContentConfiguration {
var phoneNumberDiscoverability: PhoneNumberDiscoverability
var isSelected = false
func makeContentView() -> UIView & UIContentView {
PrivacySettingButtonContentView(configuration: self)
}
func updated(for state: UIConfigurationState) -> PrivacySettingButtonContentConfiguration {
// Looks the same.
self
}
}
private class PrivacySettingButtonContentView: UIView, UIContentView {
private var _configuration: PrivacySettingButtonContentConfiguration!
var configuration: UIContentConfiguration {
get { _configuration }
set {
guard let configuration = newValue as? PrivacySettingButtonContentConfiguration else { return }
_configuration = configuration
apply(configuration)
}
}
init(configuration: PrivacySettingButtonContentConfiguration) {
super.init(frame: .zero)
isUserInteractionEnabled = false
layoutMargins = .init(hMargin: 8, vMargin: 8)
let hStack = UIStackView(arrangedSubviews: [titleLabel, checkmark])
hStack.axis = .horizontal
hStack.alignment = .center
hStack.spacing = 12
addSubview(hStack)
hStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hStack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
hStack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
hStack.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
hStack.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
titleLabel.heightAnchor.constraint(greaterThanOrEqualTo: checkmark.heightAnchor),
heightAnchor.constraint(greaterThanOrEqualToConstant: 48),
])
addBottomStroke(color: .Signal.opaqueSeparator, strokeWidth: .hairlineWidth)
apply(configuration)
}
@available(*, unavailable, message: "Use other constructor")
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var checkmark: UIView = {
let iconView = UIImageView()
iconView.contentMode = .scaleAspectFit
iconView.translatesAutoresizingMaskIntoConstraints = false
iconView.widthAnchor.constraint(equalToConstant: 24).isActive = true
iconView.setTemplateImage(Theme.iconImage(.checkmark), tintColor: .Signal.label)
return iconView
}()
private lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.font = .dynamicTypeBodyClamped
titleLabel.textColor = .Signal.label
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
titleLabel.text = OWSLocalizedString(
"REGISTRATION_PROFILE_SETUP_FIND_MY_NUMBER_TITLE",
comment: "During registration, users can choose who can see their phone number.",
)
return titleLabel
}()
private func apply(_ configuration: PrivacySettingButtonContentConfiguration) {
titleLabel.text = configuration.phoneNumberDiscoverability.nameForDiscoverability
checkmark.isHidden = !configuration.isSelected
}
}
}