287 lines
11 KiB
Swift
287 lines
11 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import SafariServices
|
|
import SignalServiceKit
|
|
public import SignalUI
|
|
|
|
// MARK: - RegistrationSplashPresenter
|
|
|
|
public protocol RegistrationSplashPresenter: AnyObject {
|
|
func continueFromSplash()
|
|
func setHasOldDevice(_ hasOldDevice: Bool)
|
|
|
|
func switchToDeviceLinkingMode()
|
|
func transferDevice()
|
|
}
|
|
|
|
// MARK: - RegistrationSplashViewController
|
|
|
|
public class RegistrationSplashViewController: OWSViewController {
|
|
|
|
private weak var presenter: RegistrationSplashPresenter?
|
|
|
|
public init(presenter: RegistrationSplashPresenter) {
|
|
self.presenter = presenter
|
|
super.init()
|
|
navigationItem.hidesBackButton = true
|
|
}
|
|
|
|
public override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
view.backgroundColor = .Signal.background
|
|
|
|
// Buttons in the top right corner.
|
|
let canSwitchModes = UIDevice.current.isIPad || FeatureFlags.linkedPhones
|
|
var transferButtonTrailingAnchor: NSLayoutAnchor<NSLayoutXAxisAnchor> = view.layoutMarginsGuide.trailingAnchor
|
|
if canSwitchModes {
|
|
let modeSwitchButton = UIButton(
|
|
configuration: .plain(),
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.didTapModeSwitch()
|
|
}
|
|
)
|
|
modeSwitchButton.configuration?.image = .init(named: UIDevice.current.isIPad ? "link" : "link-slash")
|
|
modeSwitchButton.tintColor = .ows_gray25
|
|
modeSwitchButton.accessibilityIdentifier = "onboarding.splash.modeSwitch"
|
|
|
|
view.addSubview(modeSwitchButton)
|
|
modeSwitchButton.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
modeSwitchButton.widthAnchor.constraint(equalToConstant: 40),
|
|
modeSwitchButton.heightAnchor.constraint(equalToConstant: 40),
|
|
modeSwitchButton.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
|
|
modeSwitchButton.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
|
])
|
|
|
|
transferButtonTrailingAnchor = modeSwitchButton.leadingAnchor
|
|
}
|
|
|
|
if FeatureFlags.preRegDeviceTransfer {
|
|
let transferButton = UIButton(
|
|
configuration: .plain(),
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.didTapTransfer()
|
|
}
|
|
)
|
|
transferButton.configuration?.image = Theme.iconImage(.transfer).resizedImage(to: .square(24))
|
|
transferButton.accessibilityIdentifier = "onboarding.splash.transfer"
|
|
|
|
view.addSubview(transferButton)
|
|
transferButton.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
transferButton.widthAnchor.constraint(equalToConstant: 40),
|
|
transferButton.heightAnchor.constraint(equalToConstant: 40),
|
|
transferButton.trailingAnchor.constraint(equalTo: transferButtonTrailingAnchor),
|
|
transferButton.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
|
|
])
|
|
}
|
|
|
|
// Main content view.
|
|
let stackView = UIStackView()
|
|
stackView.axis = .vertical
|
|
stackView.alignment = .fill
|
|
stackView.preservesSuperviewLayoutMargins = true
|
|
stackView.isLayoutMarginsRelativeArrangement = true
|
|
view.addSubview(stackView)
|
|
view.sendSubviewToBack(stackView) // don't obscure buttons in the corner
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
stackView.bottomAnchor.constraint(
|
|
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
|
|
constant: UIDevice.current.hasIPhoneXNotch ? 0 : -32
|
|
),
|
|
])
|
|
|
|
// Image at the top.
|
|
let heroImage = UIImage(named: "onboarding_splash_hero")
|
|
let heroImageView = UIImageView(image: heroImage)
|
|
heroImageView.contentMode = .scaleAspectFit
|
|
heroImageView.layer.minificationFilter = .trilinear
|
|
heroImageView.layer.magnificationFilter = .trilinear
|
|
heroImageView.setCompressionResistanceLow()
|
|
heroImageView.setContentHuggingVerticalLow()
|
|
heroImageView.accessibilityIdentifier = "registration.splash.heroImageView"
|
|
stackView.addArrangedSubview(heroImageView)
|
|
stackView.setCustomSpacing(22, after: heroImageView)
|
|
|
|
// Welcome text.
|
|
let titleText = {
|
|
if TSConstants.isUsingProductionService {
|
|
return OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_TITLE",
|
|
comment: "Title of the 'onboarding splash' view."
|
|
)
|
|
} else {
|
|
return "Internal Staging Build\n\(AppVersionImpl.shared.currentAppVersion)"
|
|
}
|
|
}()
|
|
let titleLabel = UILabel.titleLabelForRegistration(text: titleText)
|
|
titleLabel.accessibilityIdentifier = "registration.splash.titleLabel"
|
|
stackView.addArrangedSubview(titleLabel)
|
|
stackView.setCustomSpacing(12, after: titleLabel)
|
|
|
|
// Terms of service and privacy policy.
|
|
let tosPPButton = UIButton(
|
|
configuration: .smallBorderless(title: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_TERM_AND_PRIVACY_POLICY",
|
|
comment: "Link to the 'terms and privacy policy' in the 'onboarding splash' view."
|
|
)),
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.showTOSPP()
|
|
}
|
|
)
|
|
tosPPButton.configuration?.baseForegroundColor = .Signal.secondaryLabel
|
|
tosPPButton.enableMultilineLabel()
|
|
tosPPButton.accessibilityIdentifier = "registration.splash.explanationLabel"
|
|
stackView.addArrangedSubview(tosPPButton)
|
|
stackView.setCustomSpacing(57, after: tosPPButton)
|
|
|
|
// Large buttons enclosed in a container with some extra horizontal padding.
|
|
let continueButton = UIButton(
|
|
configuration: .largePrimary(title: CommonStrings.continueButton),
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.continuePressed()
|
|
}
|
|
)
|
|
continueButton.accessibilityIdentifier = "registration.splash.continueButton"
|
|
|
|
let largeButtonsContainer = UIStackView(arrangedSubviews: [continueButton])
|
|
largeButtonsContainer.isLayoutMarginsRelativeArrangement = true
|
|
largeButtonsContainer.directionalLayoutMargins = .layoutMarginsForLargeRegistrationButtons()
|
|
largeButtonsContainer.spacing = 16
|
|
largeButtonsContainer.axis = .vertical
|
|
largeButtonsContainer.alignment = .fill
|
|
stackView.addArrangedSubview(largeButtonsContainer)
|
|
|
|
if FeatureFlags.Backups.supported {
|
|
let restoreOrTransferButton = UIButton(
|
|
configuration: .largeSecondary(title: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_RESTORE_OR_TRANSFER_BUTTON_TITLE",
|
|
comment: "Button for restoring or transferring account in the 'onboarding splash' view."
|
|
)),
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.didTapRestoreOrTransfer()
|
|
}
|
|
)
|
|
restoreOrTransferButton.enableMultilineLabel()
|
|
restoreOrTransferButton.accessibilityIdentifier = "registration.splash.continueButton"
|
|
largeButtonsContainer.addArrangedSubview(restoreOrTransferButton)
|
|
}
|
|
}
|
|
|
|
// MARK: - Events
|
|
|
|
private func didTapModeSwitch() {
|
|
Logger.info("")
|
|
presenter?.switchToDeviceLinkingMode()
|
|
}
|
|
|
|
private func didTapTransfer() {
|
|
Logger.info("")
|
|
presenter?.transferDevice()
|
|
}
|
|
|
|
private func showTOSPP() {
|
|
let safariVC = SFSafariViewController(url: TSConstants.legalTermsUrl)
|
|
present(safariVC, animated: true)
|
|
}
|
|
|
|
private func continuePressed() {
|
|
Logger.info("")
|
|
presenter?.continueFromSplash()
|
|
}
|
|
|
|
private func didTapRestoreOrTransfer() {
|
|
Logger.info("")
|
|
let sheet = RestoreOrTransferPickerController(
|
|
setHasOldDeviceBlock: { [weak self] hasOldDevice in
|
|
self?.dismiss(animated: true) {
|
|
self?.presenter?.setHasOldDevice(hasOldDevice)
|
|
}
|
|
}
|
|
)
|
|
self.present(sheet, animated: true)
|
|
}
|
|
}
|
|
|
|
private class RestoreOrTransferPickerController: StackSheetViewController {
|
|
|
|
private let setHasOldDeviceBlock: ((Bool) -> Void)
|
|
init(setHasOldDeviceBlock: @escaping (Bool) -> Void) {
|
|
self.setHasOldDeviceBlock = setHasOldDeviceBlock
|
|
super.init()
|
|
}
|
|
|
|
open override var sheetBackgroundColor: UIColor { .Signal.secondaryBackground }
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
stackView.spacing = 16
|
|
|
|
let hasDeviceButton = UIButton.registrationChoiceButton(
|
|
title: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_HAVE_OLD_DEVICE_TITLE",
|
|
comment: "Title for the 'have my old device' choice of the 'Restore or Transfer' prompt"
|
|
),
|
|
subtitle: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_HAVE_OLD_DEVICE_BODY",
|
|
comment: "Explanation of 'have old device' flow for the 'Restore or Transfer' prompt"
|
|
),
|
|
iconName: "qr-code-48",
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.setHasOldDeviceBlock(true)
|
|
}
|
|
)
|
|
stackView.addArrangedSubview(hasDeviceButton)
|
|
|
|
let noDeviceButton = UIButton.registrationChoiceButton(
|
|
title: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_DO_NOT_HAVE_OLD_DEVICE_TITLE",
|
|
comment: "Title for the 'do not have my old device' choice of the 'Restore or Transfer' prompt"
|
|
),
|
|
subtitle: OWSLocalizedString(
|
|
"ONBOARDING_SPLASH_DO_NOT_HAVE_OLD_DEVICE_BODY",
|
|
comment: "Explanation of 'do not have old device' flow for the 'Restore or Transfer' prompt"
|
|
),
|
|
iconName: "no-phone-48",
|
|
primaryAction: UIAction { [weak self] _ in
|
|
self?.setHasOldDeviceBlock(false)
|
|
}
|
|
)
|
|
stackView.addArrangedSubview(noDeviceButton)
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
private class PreviewRegistrationSplashPresenter: RegistrationSplashPresenter {
|
|
func continueFromSplash() {
|
|
print("continueFromSplash")
|
|
}
|
|
|
|
func setHasOldDevice(_ hasOldDevice: Bool) {
|
|
print("setHasOldDevice: \(hasOldDevice)")
|
|
}
|
|
|
|
func switchToDeviceLinkingMode() {
|
|
print("switchToDeviceLinkingMode")
|
|
}
|
|
|
|
func transferDevice() {
|
|
print("transferDevice")
|
|
}
|
|
}
|
|
|
|
@available(iOS 17, *)
|
|
#Preview {
|
|
let presenter = PreviewRegistrationSplashPresenter()
|
|
return RegistrationSplashViewController(presenter: presenter)
|
|
}
|
|
#endif
|