Remove deprecated device transfer bits

This commit is contained in:
Pete Walters
2025-12-02 08:24:01 -06:00
committed by GitHub
parent e5d8d751a2
commit d6ac807f06
11 changed files with 24 additions and 783 deletions

View File

@@ -1007,7 +1007,6 @@
667664382A44B6C200716B84 /* OWSFingerprintBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667664372A44B6C200716B84 /* OWSFingerprintBuilder.swift */; };
6676A40A2BD1EB3D006B9CC9 /* OwnedAttachmentPointerProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6676A4092BD1EB3D006B9CC9 /* OwnedAttachmentPointerProto.swift */; };
6676A40C2BD1F5F9006B9CC9 /* QuotedAttachmentInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6676A40B2BD1F5F9006B9CC9 /* QuotedAttachmentInfo.swift */; };
66783C2B29CA4F6C00FC4E4E /* RegistrationTransferQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66783C2A29CA4F6C00FC4E4E /* RegistrationTransferQRCodeViewController.swift */; };
667AF9DA2B48A3F3008AEE5D /* TSInfoMessage+GroupUpdates+PersistableGroupUpdateItemUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667AF9D92B48A3F3008AEE5D /* TSInfoMessage+GroupUpdates+PersistableGroupUpdateItemUpdater.swift */; };
667AF9DE2B4C5824008AEE5D /* PersistableGroupUpdateItem+CVComponentSystemMessageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667AF9DD2B4C5824008AEE5D /* PersistableGroupUpdateItem+CVComponentSystemMessageAction.swift */; };
667AF9E02B4C6377008AEE5D /* TSInfoMessage+LegacyPersistablegroupUpdateItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 667AF9DF2B4C6377008AEE5D /* TSInfoMessage+LegacyPersistablegroupUpdateItemTest.swift */; };
@@ -3215,7 +3214,6 @@
F94262A0289B1B5600460798 /* SMKSecretSessionCipherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9426238289B1B5500460798 /* SMKSecretSessionCipherTest.swift */; };
F94262A1289B1B5600460798 /* SignalServiceAddressTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9426239289B1B5500460798 /* SignalServiceAddressTest.swift */; };
F94262A2289B1B5600460798 /* SSKBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F942623A289B1B5500460798 /* SSKBaseTest.swift */; };
F9426D18299FEC8900357352 /* RegistrationTransferChoiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9426D17299FEC8900357352 /* RegistrationTransferChoiceViewController.swift */; };
F9427EAB297F1E88008EF0AC /* SpamReportingToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9427EAA297F1E88008EF0AC /* SpamReportingToken.swift */; };
F9427EAE297F1EE3008EF0AC /* SpamReportingTokenTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9427EAD297F1EE3008EF0AC /* SpamReportingTokenTest.swift */; };
F9427EB0297F24AB008EF0AC /* SpamReportingTokenRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9427EAF297F24AB008EF0AC /* SpamReportingTokenRecord.swift */; };
@@ -4993,7 +4991,6 @@
667664372A44B6C200716B84 /* OWSFingerprintBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSFingerprintBuilder.swift; sourceTree = "<group>"; };
6676A4092BD1EB3D006B9CC9 /* OwnedAttachmentPointerProto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnedAttachmentPointerProto.swift; sourceTree = "<group>"; };
6676A40B2BD1F5F9006B9CC9 /* QuotedAttachmentInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedAttachmentInfo.swift; sourceTree = "<group>"; };
66783C2A29CA4F6C00FC4E4E /* RegistrationTransferQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTransferQRCodeViewController.swift; sourceTree = "<group>"; };
667AF9D92B48A3F3008AEE5D /* TSInfoMessage+GroupUpdates+PersistableGroupUpdateItemUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+GroupUpdates+PersistableGroupUpdateItemUpdater.swift"; sourceTree = "<group>"; };
667AF9DD2B4C5824008AEE5D /* PersistableGroupUpdateItem+CVComponentSystemMessageAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableGroupUpdateItem+CVComponentSystemMessageAction.swift"; sourceTree = "<group>"; };
667AF9DF2B4C6377008AEE5D /* TSInfoMessage+LegacyPersistablegroupUpdateItemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSInfoMessage+LegacyPersistablegroupUpdateItemTest.swift"; sourceTree = "<group>"; };
@@ -7234,7 +7231,6 @@
F9426238289B1B5500460798 /* SMKSecretSessionCipherTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SMKSecretSessionCipherTest.swift; sourceTree = "<group>"; };
F9426239289B1B5500460798 /* SignalServiceAddressTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalServiceAddressTest.swift; sourceTree = "<group>"; };
F942623A289B1B5500460798 /* SSKBaseTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SSKBaseTest.swift; path = SignalServiceKit/tests/SSKBaseTest.swift; sourceTree = SOURCE_ROOT; };
F9426D17299FEC8900357352 /* RegistrationTransferChoiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationTransferChoiceViewController.swift; sourceTree = "<group>"; };
F9427EAA297F1E88008EF0AC /* SpamReportingToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpamReportingToken.swift; sourceTree = "<group>"; };
F9427EAD297F1EE3008EF0AC /* SpamReportingTokenTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpamReportingTokenTest.swift; sourceTree = "<group>"; };
F9427EAF297F24AB008EF0AC /* SpamReportingTokenRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpamReportingTokenRecord.swift; sourceTree = "<group>"; };
@@ -10085,9 +10081,7 @@
F9440E7129C0FA490016FE95 /* RegistrationReglockTimeoutViewController.swift */,
C14366252DA55CF8008DE746 /* RegistrationRestoreFromBackupConfirmationViewController.swift */,
F933FC3E2992E77300D78DB0 /* RegistrationSplashViewController.swift */,
F9426D17299FEC8900357352 /* RegistrationTransferChoiceViewController.swift */,
66C3887D29CA537400E6DC00 /* RegistrationTransferProgressViewController.swift */,
66783C2A29CA4F6C00FC4E4E /* RegistrationTransferQRCodeViewController.swift */,
C12DB5E62DD39051005039FE /* RegistrationTransferStatusState.swift */,
C147C1712D9C58D60026952D /* RegistrationTransferStatusViewController.swift */,
C18DB09E2DA6B9D40084CFD6 /* RegistrationUIStyles.swift */,
@@ -17485,9 +17479,7 @@
C14366262DA55CF8008DE746 /* RegistrationRestoreFromBackupConfirmationViewController.swift in Sources */,
F933FC3F2992E77300D78DB0 /* RegistrationSplashViewController.swift in Sources */,
6600F39029918CBF00B1EDB7 /* RegistrationStep.swift in Sources */,
F9426D18299FEC8900357352 /* RegistrationTransferChoiceViewController.swift in Sources */,
66C3887E29CA537400E6DC00 /* RegistrationTransferProgressViewController.swift in Sources */,
66783C2B29CA4F6C00FC4E4E /* RegistrationTransferQRCodeViewController.swift in Sources */,
C12DB5E72DD39051005039FE /* RegistrationTransferStatusState.swift in Sources */,
C147C1722D9C58D60026952D /* RegistrationTransferStatusViewController.swift in Sources */,
C18DB09F2DA6B9D40084CFD6 /* RegistrationUIStyles.swift in Sources */,

View File

@@ -112,11 +112,6 @@ public protocol RegistrationCoordinator {
/// If not allowed, an error step may be returned.
func skipAndCreateNewPINCode() -> Guarantee<RegistrationStep>
/// When registering, the server will inform us (via an error code) if device transfer is
/// possible from an existing device. In this case, the user must either transfer or explicitly
/// decline transferring; this method informs the flow of the latter choice.
func skipDeviceTransfer() -> Guarantee<RegistrationStep>
/// Set the target restore method to be used in the next step to restore the system.
func updateRestoreMethod(method: RegistrationRestoreMethod) -> Guarantee<RegistrationStep>

View File

@@ -513,16 +513,6 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
return Guarantee.wrapAsync { await self.nextStep() }
}
public func skipDeviceTransfer() -> Guarantee<RegistrationStep> {
Logger.info("")
db.write { tx in
updatePersistedState(tx) {
$0.hasDeclinedTransfer = true
}
}
return Guarantee.wrapAsync { await self.nextStep() }
}
public func skipRestoreFromBackup() -> Guarantee<RegistrationStep> {
Logger.info("")
inMemoryState.hasSkippedRestoreFromMessageBackup = true
@@ -1890,11 +1880,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
}
if inMemoryState.needsToAskForDeviceTransfer && persistedState.restoreMethod == nil {
if BuildFlags.Backups.registrationFlow {
return .chooseRestoreMethod(.unspecified)
} else if !persistedState.hasDeclinedTransfer {
return .transferSelection
}
return .chooseRestoreMethod(.unspecified)
} else if
persistedState.restoreMethod?.isBackup == true,
inMemoryState.accountEntropyPool == nil
@@ -2440,11 +2426,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
}
if inMemoryState.needsToAskForDeviceTransfer && !persistedState.hasDeclinedTransfer {
if BuildFlags.Backups.registrationFlow {
return .chooseRestoreMethod(.unspecified)
} else {
return .transferSelection
}
return .chooseRestoreMethod(.unspecified)
}
if session.verified {
@@ -2761,11 +2743,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
return await nextStep()
case .deviceTransferPossible:
inMemoryState.needsToAskForDeviceTransfer = true
if BuildFlags.Backups.registrationFlow {
return .chooseRestoreMethod(.unspecified)
} else {
return .transferSelection
}
return .chooseRestoreMethod(.unspecified)
case .networkError:
if retriesLeft > 0 {
return await self.makeRegisterOrChangeNumberRequestFromSession(
@@ -4968,8 +4946,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
switch mode {
case .registering:
return
BuildFlags.Backups.registrationFlow
&& inMemoryState.accountEntropyPool != nil
inMemoryState.accountEntropyPool != nil
&& inMemoryState.hasBackedUpToSVR
&& inMemoryState.backupRestoreState == .none
&& !inMemoryState.hasSkippedRestoreFromMessageBackup

View File

@@ -31,12 +31,6 @@ public enum RegistrationStep: Equatable {
/// If registering via session, the step to enter the verification code.
case verificationCodeEntry(RegistrationVerificationState)
/// When registering, the server can inform the client that a device-to-device
/// transfer is possible. If so, the user must either do the transfer, or explicitly
/// elect not to. This step presents those options to the user.
/// Only happens if registering on a new device without local data.
case transferSelection
/// For the first time we enter the pin. This can be
/// for first account setup, creating a pin, or if
/// re-registering and needing to confirm the pin.
@@ -157,7 +151,6 @@ public enum RegistrationStep: Equatable {
case .scanQuickRegistrationQrCode: return "scanQuickRegistrationQrCode"
case .phoneNumberEntry: return "phoneNumberEntry"
case .verificationCodeEntry: return "verificationCodeEntry"
case .transferSelection: return "transferSelection"
case .deviceTransfer: return "deviceTransfer"
case .pinEntry: return "pinEntry"
case .pinAttemptsExhaustedWithoutReglock: return "pinAttemptsExhaustedWithoutReglock"

View File

@@ -284,15 +284,6 @@ public class RegistrationNavigationController: OWSNavigationController {
return nil
}
)
case .transferSelection:
return Controller(
type: RegistrationTransferChoiceViewController.self,
make: { presenter in
return RegistrationTransferChoiceViewController(presenter: presenter)
},
// No state to update.
update: nil
)
case .pinEntry(let state):
return Controller(
type: RegistrationPinViewController.self,
@@ -635,35 +626,6 @@ extension RegistrationNavigationController: RegistrationPinAttemptsExhaustedAndM
}
}
extension RegistrationNavigationController: RegistrationTransferChoicePresenter {
public func transferDevice() {
Logger.info("Pushing device transfer")
do {
// TODO: [Backups] - Don't reach into app environment, but this should be removed
// once Backups launches
let url = try AppEnvironment.shared.deviceTransferServiceRef.startAcceptingTransfersFromOldDevices(
mode: .primary
)
// We push these controllers right onto the same navigation stack, even though they
// are not coordinator "steps". They have their own internal logic to proceed and go
// back (direct calls to push and pop) and, when they complete, they will have _totally_
// overwritten our local database, thus wiping any in progress reg coordinator state
// and putting us into the chat list.
pushViewController(RegistrationTransferQRCodeViewController(url: url), animated: true)
} catch {
// TODO: [Backups] - update this error handling
Logger.error("Error transferring")
}
}
func continueRegistration() {
pushNextController(coordinator.skipDeviceTransfer())
}
}
extension RegistrationNavigationController: RegistrationProfilePresenter {
func goToNextStep(
givenName: OWSUserProfile.NameComponent,

View File

@@ -14,7 +14,6 @@ public protocol RegistrationSplashPresenter: AnyObject {
func setHasOldDevice(_ hasOldDevice: Bool)
func switchToDeviceLinkingMode()
func transferDevice()
}
// MARK: - RegistrationSplashViewController
@@ -39,7 +38,6 @@ public class RegistrationSplashViewController: OWSViewController, OWSNavigationC
// Buttons in the top right corner.
let canSwitchModes = UIDevice.current.isIPad || BuildFlags.linkedPhones
var transferButtonTrailingAnchor: NSLayoutAnchor<NSLayoutXAxisAnchor> = contentLayoutGuide.trailingAnchor
if canSwitchModes {
let modeSwitchButton = UIButton(
configuration: .plain(),
@@ -59,28 +57,6 @@ public class RegistrationSplashViewController: OWSViewController, OWSNavigationC
modeSwitchButton.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor),
modeSwitchButton.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
])
transferButtonTrailingAnchor = modeSwitchButton.leadingAnchor
}
if BuildFlags.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 = "registration.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: contentLayoutGuide.topAnchor),
])
}
// Image at the top.
@@ -139,24 +115,19 @@ public class RegistrationSplashViewController: OWSViewController, OWSNavigationC
)
continueButton.accessibilityIdentifier = "registration.splash.continueButton"
let largeButtonsContainer: UIView
if BuildFlags.Backups.registrationFlow {
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"
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 = UIStackView.verticalButtonStack(buttons: [ continueButton, restoreOrTransferButton ])
} else {
largeButtonsContainer = UIStackView.verticalButtonStack(buttons: [ continueButton ])
}
let largeButtonsContainer = UIStackView.verticalButtonStack(buttons: [ continueButton, restoreOrTransferButton ])
// Main content view.
let stackView = addStaticContentStackView(arrangedSubviews: [
@@ -178,11 +149,6 @@ public class RegistrationSplashViewController: OWSViewController, OWSNavigationC
presenter?.switchToDeviceLinkingMode()
}
private func didTapTransfer() {
Logger.info("")
presenter?.transferDevice()
}
private func showTOSPP() {
let safariVC = SFSafariViewController(url: TSConstants.legalTermsUrl)
present(safariVC, animated: true)

View File

@@ -1,131 +0,0 @@
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
// MARK: - RegistrationTransferChoicePresenter
protocol RegistrationTransferChoicePresenter: AnyObject {
func transferDevice()
func continueRegistration()
}
// MARK: - RegistrationTransferChoiceViewController
class RegistrationTransferChoiceViewController: OWSViewController {
private weak var presenter: RegistrationTransferChoicePresenter?
public init(presenter: RegistrationTransferChoicePresenter) {
self.presenter = presenter
super.init()
navigationItem.hidesBackButton = true
}
@available(*, unavailable)
public override init() {
owsFail("This should not be called")
}
public override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .Signal.background
// Create UI elements.
let titleLabel = UILabel.titleLabelForRegistration(text: OWSLocalizedString(
"REGISTRATION_DEVICE_TRANSFER_CHOICE_TITLE",
comment: "If a user is installing Signal on a new phone, they may be asked whether they want to transfer their account from their old device. This is the title on the screen that asks them this question."
))
titleLabel.accessibilityIdentifier = "registration.transferChoice.titleLabel"
let explanationLabel = UILabel.explanationLabelForRegistration(text: OWSLocalizedString(
"REGISTRATION_DEVICE_TRANSFER_CHOICE_EXPLANATION",
comment: "If a user is installing Signal on a new phone, they may be asked whether they want to transfer their account from their old device. This is a description on the screen that asks them this question."
))
explanationLabel.accessibilityIdentifier = "registration.transferChoice.explanationLabel"
let transferButton = UIButton.registrationChoiceButton(
title: OWSLocalizedString(
"DEVICE_TRANSFER_CHOICE_TRANSFER_TITLE",
comment: "The title for the device transfer 'choice' view 'transfer' option"
),
subtitle: OWSLocalizedString(
"DEVICE_TRANSFER_CHOICE_TRANSFER_BODY",
comment: "The body for the device transfer 'choice' view 'transfer' option"
),
iconName: Theme.iconName(.transfer),
primaryAction: UIAction { [weak self] _ in
self?.didSelectTransfer()
}
)
transferButton.accessibilityIdentifier = "registration.transferChoice.transferButton"
let registerButton = UIButton.registrationChoiceButton(
title: OWSLocalizedString(
"DEVICE_TRANSFER_CHOICE_REGISTER_TITLE",
comment: "The title for the device transfer 'choice' view 'register' option"
),
subtitle: OWSLocalizedString(
"DEVICE_TRANSFER_CHOICE_REGISTER_BODY",
comment: "The body for the device transfer 'choice' view 'register' option"
),
iconName: Theme.iconName(.register),
primaryAction: UIAction { [weak self] _ in
self?.didSelectRegister()
}
)
registerButton.accessibilityIdentifier = "registration.transferChoice.registerButton"
let stackView = addStaticContentStackView(arrangedSubviews: [
titleLabel,
explanationLabel,
registerButton,
transferButton,
.vStretchingSpacer(),
])
stackView.setCustomSpacing(24, after: explanationLabel)
}
// MARK: Events
private func didSelectTransfer() {
Logger.info("")
presenter?.transferDevice()
}
private func didSelectRegister() {
Logger.info("")
presenter?.continueRegistration()
}
}
// MARK: -
#if DEBUG
private class PreviewRegistrationTransferChoicePresenter: RegistrationTransferChoicePresenter {
func transferDevice() {
print("transferDevice")
}
func continueRegistration() {
print("continueRegistration")
}
}
@available(iOS 17, *)
#Preview {
let presenter = PreviewRegistrationTransferChoicePresenter()
return UINavigationController(
rootViewController: RegistrationTransferChoiceViewController(
presenter: presenter
)
)
}
#endif

View File

@@ -1,435 +0,0 @@
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import MultipeerConnectivity
import SignalServiceKit
public import SignalUI
public class RegistrationTransferQRCodeViewController: OWSViewController, OWSNavigationChildController {
public var prefersNavigationBarHidden: Bool { true }
private lazy var qrCodeView = QRCodeView(contentInset: 8)
private lazy var expansionButton: UIButton = {
let button = UIButton(configuration: .filled(), primaryAction: UIAction { [weak self] _ in
self?.toggleQRCodeExpansion()
})
button.configuration?.cornerStyle = .capsule
button.configuration?.imagePadding = 4
button.configuration?.titleTextAttributesTransformer = .defaultFont(.dynamicTypeHeadlineClamped)
button.configuration?.contentInsets = .init(hMargin: 16, vMargin: 8)
return button
}()
private lazy var compactQRCodeContainer: UIView = {
let view = UIView()
view.layer.cornerRadius = 12
view.directionalLayoutMargins = .init(margin: 16)
view.backgroundColor = .Signal.secondaryBackground
view.addSubview(qrCodeView)
qrCodeView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(expansionButton)
expansionButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
qrCodeView.widthAnchor.constraint(equalTo: qrCodeView.heightAnchor),
qrCodeView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
qrCodeView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
qrCodeView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
expansionButton.topAnchor.constraint(equalTo: qrCodeView.bottomAnchor, constant: 12),
expansionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
expansionButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
])
return view
}()
private lazy var closeButton: UIButton = {
let closeButton = OWSButton()
closeButton.setImage(
Theme.iconImage(.buttonX).withTintColor(.ows_white, renderingMode: .alwaysOriginal),
for: .normal
)
closeButton.contentMode = .center
closeButton.addTarget(self, action: #selector(compactQRCode), for: .touchUpInside)
return closeButton
}()
private let titleLabel: UILabel = {
let label = UILabel.titleLabelForRegistration(
text: OWSLocalizedString(
"DEVICE_TRANSFER_QRCODE_TITLE",
comment: "The title for the device transfer qr code view"
)
)
label.accessibilityIdentifier = "onboarding.transferQRCode.titleLabel"
return label
}()
private let explanationLabel: UILabel = {
let label = UILabel.explanationLabelForRegistration(
text: OWSLocalizedString(
"DEVICE_TRANSFER_QRCODE_EXPLANATION",
comment: "The explanation for the device transfer qr code view"
)
)
label.accessibilityIdentifier = "onboarding.transferQRCode.bodyLabel"
return label
}()
private lazy var explanationLabel2: UILabel = {
let explanationLabel2 = UILabel.explanationLabelForRegistration(
text: OWSLocalizedString(
"DEVICE_TRANSFER_QRCODE_EXPLANATION2",
comment: "The second explanation for the device transfer qr code view"
)
)
return explanationLabel2
}()
private lazy var bottomButtonsContainer: UIView = {
let helpButton = UIButton(
configuration: .mediumBorderless(title: OWSLocalizedString(
"DEVICE_TRANSFER_QRCODE_NOT_SEEING",
comment: "A prompt to provide further explanation if the user is not seeing the transfer on both devices."
)),
primaryAction: UIAction { [weak self] _ in
self?.didTapHelp()
}
)
helpButton.enableMultilineLabel()
var cancelButton = UIButton(
configuration: .mediumSecondary(title: CommonStrings.cancelButton),
primaryAction: UIAction { [weak self] _ in
self?.didTapCancel()
}
)
return UIStackView.verticalButtonStack(buttons: [ helpButton, cancelButton ], isFullWidthButtons: false)
}()
private let url: URL
public init(url: URL) {
self.url = url
super.init()
navigationItem.hidesBackButton = true
}
override public func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .Signal.background
// Put QR code view into full-width container to allow centering of the rounded edges view.
let qrCodeContainerView = UIView.container()
qrCodeContainerView.addSubview(compactQRCodeContainer)
compactQRCodeContainer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
compactQRCodeContainer.topAnchor.constraint(equalTo: qrCodeContainerView.topAnchor),
compactQRCodeContainer.bottomAnchor.constraint(equalTo: qrCodeContainerView.bottomAnchor),
compactQRCodeContainer.leadingAnchor.constraint(greaterThanOrEqualTo: qrCodeContainerView.leadingAnchor),
compactQRCodeContainer.centerXAnchor.constraint(equalTo: qrCodeContainerView.centerXAnchor),
])
// Content view.
let stackView = addStaticContentStackView(
arrangedSubviews: [
.spacer(withHeight: 16),
titleLabel,
explanationLabel,
qrCodeContainerView,
explanationLabel2,
.vStretchingSpacer(),
bottomButtonsContainer,
],
isScrollable: true
)
stackView.setCustomSpacing(24, after: explanationLabel)
stackView.setCustomSpacing(24, after: compactQRCodeContainer)
// QR code view.
qrCodeView.translatesAutoresizingMaskIntoConstraints = false
qrCodeView.setContentHuggingVerticalLow()
NSLayoutConstraint.activate([
{
let constraint = qrCodeView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: Constants.compactQRCodeWidthMultiple)
constraint.priority = .defaultHigh
return constraint
}(),
qrCodeView.widthAnchor.constraint(greaterThanOrEqualToConstant: Constants.compactQRCodeMinSize),
])
view.addSubview(closeButton)
closeButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
closeButton.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor),
closeButton.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor, constant: 8),
])
updateExpansionState(animated: false)
view.addGestureRecognizer(UITapGestureRecognizer(
target: self,
action: #selector(compactQRCode)
))
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
AppEnvironment.shared.deviceTransferServiceRef.addObserver(self)
Task { @MainActor in
qrCodeView.setQRCode(url: url, stylingMode: .brandedWithoutLogo)
}
}
public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
AppEnvironment.shared.deviceTransferServiceRef.removeObserver(self)
AppEnvironment.shared.deviceTransferServiceRef.stopAcceptingTransfersFromOldDevices()
}
public override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard isQRCodeExpanded else { return }
coordinator.animate { context in
self.isQRCodeExpanded = false
self.updateExpansionState(animated: false)
}
}
// MARK: - QR Code expansion
private var isQRCodeExpanded = false
private func updateExpansionState(animated: Bool) {
view.isUserInteractionEnabled = false
let otherViews = [
titleLabel,
explanationLabel,
bottomButtonsContainer,
]
let animations: () -> Void = {
if self.isQRCodeExpanded {
let darkTraitCollection = UITraitCollection(userInterfaceStyle: .dark)
let desiredSize = min(self.contentLayoutGuide.layoutFrame.width, 0.7 * self.contentLayoutGuide.layoutFrame.height) - (Constants.expandedQRCodeMargin * 2)
let qrExpandScale = desiredSize / self.qrCodeView.frame.width
let currentQRCenterY = self.view.convert(
self.qrCodeView.center,
from: self.qrCodeView.superview
).y
let desiredQRCenterY = self.contentLayoutGuide.layoutFrame.minY + 0.4 * self.contentLayoutGuide.layoutFrame.height
let qrExpandYOffset = desiredQRCenterY - currentQRCenterY
self.qrCodeView.layer.anchorPoint = .init(x: 0.5, y: 0.5)
self.qrCodeView.transform = .scale(qrExpandScale)
.concatenating(.translate(CGPoint(x: 0, y: qrExpandYOffset)))
let expandedQRCodeBottom = desiredQRCenterY + (desiredSize / 2)
let desiredButtonTop = expandedQRCodeBottom + 16
let currentButtonTop = self.view.convert(
self.expansionButton.frame.origin,
from: self.expansionButton.superview
).y
let buttonYOffset = max(0, desiredButtonTop - currentButtonTop)
self.expansionButton.transform = .translate(CGPoint(x: 0, y: buttonYOffset))
let desiredLabelTop = desiredButtonTop + self.expansionButton.frame.height + 16
let currentLabelTop = self.view.convert(
self.explanationLabel2.frame.origin,
from: self.explanationLabel2.superview
).y
let labelYOffset = max(0, desiredLabelTop - currentLabelTop)
self.explanationLabel2.transform = .translate(CGPoint(x: 0, y: labelYOffset))
self.explanationLabel2.textColor = .Signal.secondaryLabel.resolvedColor(with: darkTraitCollection)
self.compactQRCodeContainer.backgroundColor = .clear
self.view.backgroundColor = .Signal.background.resolvedColor(with: darkTraitCollection)
self.closeButton.alpha = 1
otherViews.forEach { $0.alpha = 0 }
// Expand/collapse button
self.expansionButton.configuration?.title = OWSLocalizedString(
"DEVICE_TRANSFER_CONTRACT_QR_CODE_BUTTON",
comment: "Button shown to contract a QR code and exit the fullscreen view."
)
self.expansionButton.configuration?.image = Theme.iconImage(.minimize16)
self.expansionButton.configuration?.baseForegroundColor = Theme.darkThemePrimaryColor
self.expansionButton.configuration?.baseBackgroundColor = Theme.darkThemeSecondaryBackgroundColor
} else {
self.qrCodeView.transform = .identity
self.expansionButton.transform = .identity
self.explanationLabel2.transform = .identity
self.explanationLabel2.textColor = .Signal.secondaryLabel
self.compactQRCodeContainer.backgroundColor = .Signal.secondaryBackground
self.view.backgroundColor = .Signal.background
self.closeButton.alpha = 0
otherViews.forEach { $0.alpha = 1 }
// Expand/collapse button
self.expansionButton.configuration?.title = OWSLocalizedString(
"DEVICE_TRANSFER_EXPAND_QR_CODE_BUTTON",
comment: "Button shown to expand a QR code and view it fullscreen."
)
self.expansionButton.configuration?.image = Theme.iconImage(.maximize16)
self.expansionButton.configuration?.baseForegroundColor = .Signal.label
self.expansionButton.configuration?.baseBackgroundColor = .clear
}
}
guard animated else {
animations()
view.isUserInteractionEnabled = true
return
}
UIView.animate(withDuration: 0.2, animations: animations) { _ in
self.view.isUserInteractionEnabled = true
}
}
@objc
private func toggleQRCodeExpansion() {
isQRCodeExpanded = !isQRCodeExpanded
updateExpansionState(animated: true)
setNeedsStatusBarAppearanceUpdate()
}
@objc
private func compactQRCode() {
guard isQRCodeExpanded else { return }
toggleQRCodeExpansion()
}
// MARK: - Events
weak var permissionActionSheetController: ActionSheetController?
func didTapHelp() {
guard !isQRCodeExpanded else { return }
let turnOnView = TurnOnPermissionView(
title: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_TITLE",
comment: "Title for local network permission action sheet"
),
message: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_BODY",
comment: "Body for local network permission action sheet"
),
steps: [
.init(
icon: #imageLiteral(resourceName: "settings-app-icon-32"),
text: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_STEP_ONE",
comment: "First step for local network permission action sheet"
)
),
.init(
icon: UIImage(resource: UIApplication.shared.currentAppIcon.previewImageResource),
text: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_STEP_TWO",
comment: "Second step for local network permission action sheet"
)
),
.init(
icon: UIImage(imageLiteralResourceName: "toggle-32"),
text: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_STEP_THREE",
comment: "Third step for local network permission action sheet"
)
)
],
button: UIButton(
configuration: .largePrimary(title: OWSLocalizedString(
"LOCAL_NETWORK_PERMISSION_ACTION_SHEET_NEED_HELP",
comment: "A button asking the user if they need further help getting their transfer working."
)),
primaryAction: UIAction { [weak self] _ in
self?.didTapContactSupport()
}
)
)
let actionSheetController = ActionSheetController()
permissionActionSheetController = actionSheetController
actionSheetController.customHeader = turnOnView
actionSheetController.isCancelable = true
presentActionSheet(actionSheetController)
}
func didTapCancel() {
guard !isQRCodeExpanded else { return }
Logger.info("")
guard let navigationController else {
return owsFailBeta("unexpectedly missing nav controller")
}
navigationController.popViewController(animated: true)
}
func didTapContactSupport() {
guard !isQRCodeExpanded else { return }
Logger.info("")
permissionActionSheetController?.dismiss(animated: true)
permissionActionSheetController = nil
ContactSupportActionSheet.present(
emailFilter: .deviceTransfer,
logDumper: .fromGlobals(),
fromViewController: self
)
}
// MARK: - Constants
private enum Constants {
static let compactQRCodeWidthMultiple: CGFloat = 0.6
static let compactQRCodeMinSize: CGFloat = 182
static let expandedQRCodeMargin: CGFloat = 8
}
}
extension RegistrationTransferQRCodeViewController: DeviceTransferServiceObserver {
func deviceTransferServiceDiscoveredNewDevice(peerId: MCPeerID, discoveryInfo: [String: String]?) {}
func deviceTransferServiceDidStartTransfer(progress: Progress) {
let view = RegistrationTransferProgressViewController(progress: progress)
navigationController?.pushViewController(view, animated: true)
}
func deviceTransferServiceDidEndTransfer(error: DeviceTransferService.Error?) {
if let error = error {
owsFailDebug("unexpected error while rendering QR code \(error)")
}
}
func deviceTransferServiceDidRequestAppRelaunch() {
if CurrentAppContext().frontmostViewController() == self {
owsFail("Relaunch not supported from QR screen")
}
}
}

View File

@@ -91,8 +91,6 @@ class ConversationSplitViewController: UISplitViewController, ConversationSplit
object: UIDevice.current
)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: .OWSApplicationDidBecomeActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didStartTransfer), name: .outgoingDeviceTransferDidStart, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didEndTransfer), name: .outgoingDeviceTransferDidEnd, object: nil)
applyTheme()
}
@@ -101,6 +99,14 @@ class ConversationSplitViewController: UISplitViewController, ConversationSplit
fatalError("init(coder:) has not been implemented")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let windowScene = view.window?.windowScene, windowScene.activationState == .foregroundActive {
lastActiveInterfaceOrientation = windowScene.interfaceOrientation
}
}
@objc
private func applyTheme() {
view.backgroundColor = Theme.isDarkThemeEnabled ? UIColor(rgbHex: 0x292929) : UIColor(rgbHex: 0xd6d6d6)
@@ -124,19 +130,6 @@ class ConversationSplitViewController: UISplitViewController, ConversationSplit
}
}
@objc
private func didStartTransfer() {
// Disable the device transfer listener while the new device restore flow is active
AppEnvironment.shared.deviceTransferServiceRef.removeObserver(self)
AppEnvironment.shared.deviceTransferServiceRef.stopListeningForNewDevices()
}
@objc
private func didEndTransfer() {
AppEnvironment.shared.deviceTransferServiceRef.addObserver(self)
AppEnvironment.shared.deviceTransferServiceRef.startListeningForNewDevices()
}
func closeSelectedConversation(animated: Bool) {
guard let selectedConversationViewController = selectedConversationViewController else { return }
@@ -739,44 +732,3 @@ private class NoSelectedConversationViewController: OWSViewController {
logoImageView.autoCenterInSuperview()
}
}
extension ConversationSplitViewController: DeviceTransferServiceObserver {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !BuildFlags.Backups.registrationFlow {
AppEnvironment.shared.deviceTransferServiceRef.addObserver(self)
AppEnvironment.shared.deviceTransferServiceRef.startListeningForNewDevices()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let windowScene = view.window?.windowScene, windowScene.activationState == .foregroundActive {
lastActiveInterfaceOrientation = windowScene.interfaceOrientation
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if !BuildFlags.Backups.registrationFlow {
AppEnvironment.shared.deviceTransferServiceRef.removeObserver(self)
AppEnvironment.shared.deviceTransferServiceRef.stopListeningForNewDevices()
}
}
func deviceTransferServiceDiscoveredNewDevice(peerId: MCPeerID, discoveryInfo: [String: String]?) {
guard deviceTransferNavController?.presentingViewController == nil else { return }
let navController = OutgoingDeviceTransferNavigationController()
deviceTransferNavController = navController
navController.present(fromViewController: self)
}
func deviceTransferServiceDidStartTransfer(progress: Progress) {}
func deviceTransferServiceDidEndTransfer(error: DeviceTransferService.Error?) {}
func deviceTransferServiceDidRequestAppRelaunch() {}
}

View File

@@ -2617,36 +2617,21 @@
/* The explanation for the device transfer 'choice' view when linking a device */
"DEVICE_TRANSFER_CHOICE_LINKED_EXPLANATION" = "Choose how youd like to transfer your message history and account data";
/* The body for the device transfer 'choice' view 'register' option */
"DEVICE_TRANSFER_CHOICE_REGISTER_BODY" = "Continue without transferring your account and messages";
/* The body for the device transfer 'choice' view 'register' option when linking a device when message syncing is available */
"DEVICE_TRANSFER_CHOICE_REGISTER_LINKED_BODY_LINK_AND_SYNC" = "Youll have the option to transfer messages and recent media from your phone";
/* The title for the device transfer 'choice' view 'register' option when linking a device */
"DEVICE_TRANSFER_CHOICE_REGISTER_LINKED_TITLE" = "Add as New Device";
/* The title for the device transfer 'choice' view 'register' option */
"DEVICE_TRANSFER_CHOICE_REGISTER_TITLE" = "Register without Transferring";
/* The title for the device transfer 'choice' view */
"DEVICE_TRANSFER_CHOICE_TITLE" = "Transfer Account & Messages";
/* The body for the device transfer 'choice' view 'transfer' option */
"DEVICE_TRANSFER_CHOICE_TRANSFER_BODY" = "Transfer your account and message history from your old iOS device";
/* The body for the device transfer 'choice' view 'transfer' option when linking a device */
"DEVICE_TRANSFER_CHOICE_TRANSFER_LINKED_BODY" = "Transfer your account and message history from your old iOS device";
/* The title for the device transfer 'choice' view 'transfer' option when linking a device */
"DEVICE_TRANSFER_CHOICE_TRANSFER_LINKED_TITLE" = "Transfer from Another iPad";
/* The title for the device transfer 'choice' view 'transfer' option */
"DEVICE_TRANSFER_CHOICE_TRANSFER_TITLE" = "Transfer from iOS Device";
/* Button shown to contract a QR code and exit the fullscreen view. */
"DEVICE_TRANSFER_CONTRACT_QR_CODE_BUTTON" = "View smaller";
/* An error indicating that the other device closed signal mid-transfer and it could not complete */
"DEVICE_TRANSFER_ERROR_BACKGROUNDED" = "The transfer couldn't complete. Make sure both devices have Signal open and try again.";
@@ -2668,9 +2653,6 @@
/* An error indicating the user must update their device before trying to transfer. */
"DEVICE_TRANSFER_ERROR_UNSUPPORTED_VERSION" = "Update to the latest version of Signal and try again";
/* Button shown to expand a QR code and view it fullscreen. */
"DEVICE_TRANSFER_EXPAND_QR_CODE_BUTTON" = "View larger";
/* The explanation on the action sheet prompting the user if they want to transfer their device. */
"DEVICE_TRANSFER_PROMPT_EXPLANATION" = "Transfer your Signal account to a new iOS device. Make sure your new device is nearby and turned on.";
@@ -2680,9 +2662,6 @@
/* The explanation for the device transfer qr code view */
"DEVICE_TRANSFER_QRCODE_EXPLANATION" = "Bring your old device nearby, make sure Wi-Fi and Bluetooth are enabled on it, and scan this QR code with it.";
/* The second explanation for the device transfer qr code view */
"DEVICE_TRANSFER_QRCODE_EXPLANATION2" = "Position this QR code in the frame of your other iOS device.";
/* A prompt to provide further explanation if the user is not seeing the transfer on both devices. */
"DEVICE_TRANSFER_QRCODE_NOT_SEEING" = "Not seeing the prompt on your old device?";
@@ -7132,12 +7111,6 @@
/* Label for the country code field */
"REGISTRATION_DEFAULT_COUNTRY_NAME" = "Country Code";
/* If a user is installing Signal on a new phone, they may be asked whether they want to transfer their account from their old device. This is a description on the screen that asks them this question. */
"REGISTRATION_DEVICE_TRANSFER_CHOICE_EXPLANATION" = "Continuing will disable Signal on other devices currently registered with the same phone number.";
/* If a user is installing Signal on a new phone, they may be asked whether they want to transfer their account from their old device. This is the title on the screen that asks them this question. */
"REGISTRATION_DEVICE_TRANSFER_CHOICE_TITLE" = "Transfer Account & Messages";
/* Description for the screen that allows users to enter their recovery key. */
"REGISTRATION_ENTER_BACKUP_KEY_DESCRIPTION" = "Your recovery key is a 64-character code required to recover your account and data.";

View File

@@ -31,8 +31,6 @@ public enum BuildFlags {
public static let linkedPhones = build <= .internal
public static let preRegDeviceTransfer = build <= .dev
public static let isPrerelease = build <= .beta
public static let shouldUseTestIntervals = build <= .beta
@@ -42,7 +40,6 @@ public enum BuildFlags {
public static let periodicallyCheckDatabaseIntegrity: Bool = false
public enum Backups {
public static let registrationFlow = true
public static let showMegaphones = build <= .internal
public static let showOptimizeMedia = build <= .dev