Make LinkNewDeviceScreen fall back to the App Lock PIN code when there's no passcode. (#5794)

* Fall back to the AppLock PIN code when there's no passcode.

* Add extra tests on the LinkNewDeviceFlowCoordinator.

* Misc tweaks for clarification.

* Regenerate snapshots.
This commit is contained in:
Doug
2026-07-01 10:13:18 +01:00
committed by GitHub
parent 7097ceb910
commit 123661537a
38 changed files with 296 additions and 21 deletions
@@ -242,6 +242,8 @@ class AppLockFlowCoordinator: CoordinatorProtocol {
stateMachine.tryEvent(.didUnlockWithPIN)
case .forceLogout:
stateMachine.tryEvent(.forceLogout)
case .cancelVerifyDeviceOwner:
fatalError("Cancelling is not allowed when unlocking the app.")
}
}
.store(in: &cancellables)
@@ -10,6 +10,8 @@ import Foundation
enum LinkNewDeviceFlowCoordinatorAction {
case requestOAuthAuthorisation(URL, OAuthAccountSettingsPresenter.Continuation)
/// The user failed to remember their App Lock PIN.
case forceLogout
case dismiss
}
@@ -58,6 +60,8 @@ class LinkNewDeviceFlowCoordinator: FlowCoordinatorProtocol {
presentQRCodeScreen(mode: .linkMobile(progressPublisher, flowParameters.userSession.clientProxy))
case .linkDesktopComputer:
presentQRCodeScreen(mode: .linkDesktop(flowParameters.userSession.clientProxy.linkNewDeviceService()))
case .verifyWithAppLockPIN(let continuation):
presentAppLockScreen(continuation: continuation)
case .dismiss:
actionsSubject.send(.dismiss)
}
@@ -67,6 +71,33 @@ class LinkNewDeviceFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setRootCoordinator(coordinator)
}
private func presentAppLockScreen(continuation: CheckedContinuation<Bool, Never>) {
// We can use the AppLockScreen as a fallback here. It might seem weird but in the end it only calls
// AppLockService.unlock which is a no-op when the app is already unlocked.
let stackCoordinator = NavigationStackCoordinator()
let coordinator = AppLockScreenCoordinator(parameters: .init(appLockService: appLockService, mode: .verifyDeviceOwner))
coordinator.actions
.sink { [weak self] action in
guard let self else { return }
switch action {
case .appUnlocked:
continuation.resume(returning: true)
navigationStackCoordinator.setFullScreenCoverCoordinator(nil)
case .cancelVerifyDeviceOwner:
continuation.resume(returning: false)
navigationStackCoordinator.setFullScreenCoverCoordinator(nil)
case .forceLogout:
actionsSubject.send(.forceLogout)
}
}
.store(in: &cancellables)
stackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setFullScreenCoverCoordinator(stackCoordinator)
}
private func presentQRCodeScreen(mode: QRCodeLoginScreenMode) {
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(mode: mode,
canSignInManually: false, // No need to worry about this when linking a device.
@@ -192,6 +192,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setSheetCoordinator(nil)
case .requestOAuthAuthorisation(let url, let continuation):
presentAccountManagementURL(url, continuation: continuation)
case .forceLogout:
actionsSubject.send(.forceLogout)
}
}
.store(in: &cancellables)