Add support for linking new devices in the QRCodeLoginScreen. (#4891)

* Adds the remaining parts for showing/scanning a QR code to link a new device.

* Refactor the QRCodeLoginService to work the same way as the LinkNewDeviceService.
This commit is contained in:
Doug
2026-01-07 12:18:39 +00:00
committed by GitHub
parent 9269f3057b
commit c1bb838517
62 changed files with 959 additions and 388 deletions
@@ -298,7 +298,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - QR Code
private func showQRCodeLoginScreen() {
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: authenticationService,
let stackCoordinator = NavigationStackCoordinator()
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(mode: .login(authenticationService),
canSignInManually: appSettings.allowOtherAccountProviders, // No need to worry about provisioning links as we hide QR login.
orientationManager: appMediator.windowManager,
appMediator: appMediator))
@@ -311,20 +312,24 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setSheetCoordinator(nil)
stateMachine.tryEvent(.cancelledLoginWithQR)
stateMachine.tryEvent(.confirmServer(.login))
case .cancel:
case .dismiss:
navigationStackCoordinator.setSheetCoordinator(nil)
stateMachine.tryEvent(.cancelledLoginWithQR)
case .done(let userSession):
case .signedIn(let userSession):
navigationStackCoordinator.setSheetCoordinator(nil)
// Since the qr code login flow includes verification
appSettings.hasRunIdentityConfirmationOnboarding = true
DispatchQueue.main.async {
self.stateMachine.tryEvent(.signedIn, userInfo: userSession)
}
case .requestOIDCAuthorisation:
fatalError("QR code login shouldn't request an OIDC flow.")
}
}
.store(in: &cancellables)
navigationStackCoordinator.setSheetCoordinator(coordinator) // Don't use the callback (interactive dismiss disabled), choose the event with the action.
stackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) // Don't use the callback (interactive dismiss disabled), choose the event with the action.
}
// MARK: - Manual Authentication
@@ -50,9 +50,9 @@ class LinkNewDeviceFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .linkMobileDevice(let progressPublisher):
break
presentQRCodeScreen(mode: .linkMobile(progressPublisher))
case .linkDesktopComputer:
break
presentQRCodeScreen(mode: .linkDesktop(flowParameters.userSession.clientProxy.linkNewDeviceService()))
case .dismiss:
actionsSubject.send(.dismiss)
}
@@ -61,4 +61,27 @@ class LinkNewDeviceFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setRootCoordinator(coordinator)
}
private func presentQRCodeScreen(mode: QRCodeLoginScreenMode) {
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(mode: mode,
canSignInManually: false, // No need to worry about this when linking a device.
orientationManager: flowParameters.appMediator.windowManager,
appMediator: flowParameters.appMediator))
coordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .signInManually, .signedIn:
fatalError("QR linking shouldn't send sign-in actions.")
case .dismiss:
navigationStackCoordinator.pop()
case .requestOIDCAuthorisation(let url):
actionsSubject.send(.requestOIDCAuthorisation(url))
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator)
}
}