189 lines
6.2 KiB
Swift
189 lines
6.2 KiB
Swift
//
|
|
// Copyright 2025 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import SignalServiceKit
|
|
import SignalUI
|
|
import SwiftUI
|
|
|
|
class BaseQuickRestoreQRCodeViewController:
|
|
OWSViewController,
|
|
OWSNavigationChildController,
|
|
ProvisioningSocketManagerUIDelegate
|
|
{
|
|
private var provisioningSocketManager: ProvisioningSocketManager
|
|
private var model: RotatingQRCodeView.Model
|
|
|
|
override init() {
|
|
self.provisioningSocketManager = ProvisioningSocketManager(linkType: .quickRestore)
|
|
self.model = RotatingQRCodeView.Model(
|
|
urlDisplayMode: .loading,
|
|
onRefreshButtonPressed: { [weak provisioningSocketManager] in
|
|
provisioningSocketManager?.reset()
|
|
},
|
|
)
|
|
super.init()
|
|
|
|
self.provisioningSocketManager.delegate = self
|
|
self.navigationItem.hidesBackButton = true
|
|
}
|
|
|
|
private lazy var hostingController = UIHostingController(rootView: ContentStack(
|
|
model: model,
|
|
cancelAction: { [weak self] in
|
|
self?.cancel()
|
|
},
|
|
))
|
|
|
|
func cancel() {
|
|
provisioningSocketManager.stop()
|
|
}
|
|
|
|
func reset() {
|
|
provisioningSocketManager.reset()
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
view.backgroundColor = .Signal.background
|
|
|
|
addChild(hostingController)
|
|
view.addSubview(hostingController.view)
|
|
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate([
|
|
hostingController.view.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
|
|
hostingController.view.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor),
|
|
hostingController.view.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
|
|
hostingController.view.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor),
|
|
])
|
|
hostingController.didMove(toParent: self)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
provisioningSocketManager.reset()
|
|
}
|
|
|
|
func waitForMessage() async throws -> RegistrationProvisioningMessage {
|
|
return try await provisioningSocketManager.waitForMessage()
|
|
}
|
|
|
|
// MARK: ProvisioningSocketManagerUIDelegate
|
|
|
|
func provisioningSocketManager(
|
|
_ provisioningSocketManager: ProvisioningSocketManager,
|
|
didUpdateProvisioningURL url: URL,
|
|
) {
|
|
self.model.updateURLDisplayMode(.loaded(url))
|
|
}
|
|
|
|
func provisioningSocketManagerDidPauseQRRotation(_ provisioningSocketManager: ProvisioningSocketManager) {
|
|
self.model.updateURLDisplayMode(.refreshButton)
|
|
}
|
|
|
|
// MARK: OWSNavigationChildController
|
|
|
|
var preferredNavigationBarStyle: OWSNavigationBarStyle { .solid }
|
|
|
|
var navbarBackgroundColorOverride: UIColor? { .clear }
|
|
}
|
|
|
|
// MARK: - SwiftUI
|
|
|
|
private struct ContentStack: View {
|
|
@ObservedObject var model: RotatingQRCodeView.Model
|
|
|
|
let cancelAction: () -> Void
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 36) {
|
|
Text(OWSLocalizedString(
|
|
"REGISTRATION_SCAN_QR_CODE_TITLE",
|
|
comment: "Title for screen containing QR code that users scan with their old phone when they want to transfer/restore their message history to a new device.",
|
|
))
|
|
.font(.title.weight(.semibold))
|
|
.multilineTextAlignment(.center)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
RotatingQRCodeView(model: model)
|
|
.padding(.horizontal, 40)
|
|
|
|
TutorialStack()
|
|
|
|
Button(CommonStrings.cancelButton) {
|
|
self.cancelAction()
|
|
}
|
|
.buttonStyle(Registration.UI.MediumSecondaryButtonStyle())
|
|
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
|
|
.padding(.bottom, NSDirectionalEdgeInsets.buttonContainerLayoutMargins.bottom)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct TutorialStack: View {
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 24) {
|
|
Label(
|
|
OWSLocalizedString(
|
|
"REGISTRATION_SCAN_QR_CODE_TUTORIAL_OPEN_SIGNAL",
|
|
comment: "Tutorial text describing the first step to scanning the restore/transfer QR code with your old phone: opening Signal",
|
|
),
|
|
image: "device-phone",
|
|
)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Label(
|
|
OWSLocalizedString(
|
|
"REGISTRATION_SCAN_QR_CODE_TUTORIAL_TAP_CAMERA",
|
|
comment: "Tutorial text describing the second step to scanning the restore/transfer QR code with your old phone: tap the camera icon",
|
|
),
|
|
image: "camera",
|
|
)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
Label(
|
|
OWSLocalizedString(
|
|
"REGISTRATION_SCAN_QR_CODE_TUTORIAL_SCAN",
|
|
comment: "Tutorial text describing the third step to scanning the restore/transfer QR code with your old phone: scan the code",
|
|
),
|
|
image: "qr_code",
|
|
)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
.foregroundStyle(.secondary)
|
|
.padding(.horizontal, 8)
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
@available(iOS 17, *)
|
|
#Preview {
|
|
@Previewable @State var displayMode: RotatingQRCodeView.Model.URLDisplayMode = .loading
|
|
|
|
let url1 = URL(string: "https://signal.org")!
|
|
let url2 = URL(string: "https://support.signal.org")!
|
|
let cycle: () async -> Void = { @MainActor in
|
|
displayMode = .loading
|
|
try? await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
|
|
displayMode = .loaded(url1)
|
|
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 3)
|
|
displayMode = .loaded(url2)
|
|
try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 3)
|
|
displayMode = .refreshButton
|
|
}
|
|
|
|
ContentStack(
|
|
model: .init(
|
|
urlDisplayMode: displayMode,
|
|
onRefreshButtonPressed: { Task { await cycle() } },
|
|
),
|
|
cancelAction: {},
|
|
)
|
|
.task {
|
|
await cycle()
|
|
}
|
|
}
|
|
#endif
|