TM-SGNL-iOS/Signal/Provisioning/UserInterface/ProvisioningSetDeviceNameViewController.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

175 lines
6.7 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
class ProvisioningSetDeviceNameViewController: ProvisioningBaseViewController {
private let provisionMessage: ProvisionMessage
init(
provisionMessage: ProvisionMessage,
provisioningController: ProvisioningController
) {
self.provisionMessage = provisionMessage
super.init(provisioningController: provisioningController)
}
// MARK: UIViewController overrides
override public func loadView() {
view = UIView()
view.addSubview(primaryView)
primaryView.autoPinEdgesToSuperviewEdges()
view.backgroundColor = Theme.backgroundColor
let titleLabel = self.createTitleLabel(text: OWSLocalizedString("SECONDARY_ONBOARDING_CHOOSE_DEVICE_NAME", comment: "header text when this device is being added as a secondary"))
primaryView.addSubview(titleLabel)
titleLabel.accessibilityIdentifier = "linking.deviceName.titleLabel"
titleLabel.setContentHuggingHigh()
let explanationLabel = self.createExplanationLabel(explanationText: OWSLocalizedString("SECONDARY_ONBOARDING_CHOOSE_DEVICE_NAME_EXPLANATION",
comment: "label text"))
explanationLabel.accessibilityIdentifier = "linking.deviceName.explanationLabel"
textField.autoSetDimension(.width, toSize: 200)
textField.placeholder = OWSLocalizedString("SECONDARY_ONBOARDING_CHOOSE_DEVICE_NAME_PLACEHOLDER", comment: "text field placeholder")
textField.textColor = Theme.primaryTextColor
textField.font = UIFont.dynamicTypeBodyClamped
textField.accessibilityIdentifier = "linking.deviceName.textField"
textField.text = UIDevice.current.name
textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
textField.setCompressionResistanceHigh()
textFieldStrokeNormal = textField.addBottomStroke()
textFieldStrokeError = textField.addBottomStroke(color: .ows_accentRed, strokeWidth: 2)
textFieldStrokeError.isHidden = true
validationErrorLabel = UILabel()
validationErrorLabel.textColor = .ows_accentRed
validationErrorLabel.font = UIFont.dynamicTypeSubheadlineClamped
validationErrorLabel.autoSetDimension(.height, toSize: validationErrorLabel.font.lineHeight)
validationErrorLabel.accessibilityIdentifier = "linking.deviceName.validationErrorLabel"
validationErrorLabel.setCompressionResistanceHigh()
let textFieldStack = UIStackView(arrangedSubviews: [textField, validationErrorLabel])
textFieldStack.axis = .vertical
textFieldStack.alignment = .center
textFieldStack.setCompressionResistanceHigh()
let primaryButton = self.primaryButton(title: OWSLocalizedString("SECONDARY_ONBOARDING_COMPLETE_LINKING_PROCESS", comment: "body text while displaying a QR code which, when scanned, will link this device."),
selector: #selector(didTapFinalizeLinking))
primaryButton.accessibilityIdentifier = "onboarding.confirmLink.confirmButton"
let primaryButtonView = ProvisioningBaseViewController.horizontallyWrap(primaryButton: primaryButton)
let aboveTextFieldSpacer = UIView.vStretchingSpacer(maxHeight: 60)
let belowTextFieldSpacer = UIView.vStretchingSpacer()
let compressableBottomMargin = UIView.vStretchingSpacer(minHeight: 16, maxHeight: primaryLayoutMargins.bottom)
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
explanationLabel,
aboveTextFieldSpacer,
textFieldStack,
belowTextFieldSpacer,
primaryButtonView,
compressableBottomMargin
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.spacing = 12
primaryView.addSubview(stackView)
// Because of the keyboard, vertical spacing can get pretty cramped,
// so we have custom spacer logic.
stackView.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom)
stackView.autoPinEdge(.bottom, to: .bottom, of: keyboardLayoutGuideViewSafeArea)
}
// MARK: -
let textField = UITextField()
var validationErrorLabel: UILabel!
var textFieldStrokeError: UIView!
var textFieldStrokeNormal: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
textField.becomeFirstResponder()
}
var textFieldValidationError: ValidationError? {
didSet {
guard textFieldValidationError != oldValue else { return }
updateValidationUI()
}
}
@objc
func textFieldDidChange(_ textField: UITextField) {
// We want to remove the validation error as soon as possible
// but we don't want to risk adding a validation error prematurely.
guard textFieldValidationError != nil else { return }
_ = validateDeviceName()
}
func updateValidationUI() {
guard let textFieldValidationError = textFieldValidationError else {
validationErrorLabel.isHidden = true
textFieldStrokeError.isHidden = true
textFieldStrokeNormal.isHidden = false
return
}
validationErrorLabel.isHidden = false
textFieldStrokeError.isHidden = false
textFieldStrokeNormal.isHidden = true
switch textFieldValidationError {
case .empty:
validationErrorLabel.text = OWSLocalizedString("VALIDATION_ERROR_CANNOT_BE_BLANK", comment: "error label near a field")
case .tooLong:
validationErrorLabel.text = OWSLocalizedString("VALIDATION_ERROR_TOO_LONG", comment: "error label near a field")
}
}
enum ValidationError {
case empty, tooLong
}
func validateDeviceName() -> String? {
guard let deviceName = textField.text?.filterStringForDisplay(), !deviceName.isEmpty else {
textFieldValidationError = .empty
return nil
}
let characterLimit = 50
guard deviceName.count <= characterLimit else {
textFieldValidationError = .tooLong
return nil
}
textFieldValidationError = nil
return deviceName
}
@objc
func didTapFinalizeLinking() {
guard let deviceName = validateDeviceName() else {
return
}
provisioningController.didSetDeviceName(
String(deviceName),
provisionMessage: provisionMessage,
from: self,
willLinkAndSync: false
)
}
}