mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Combine the Server Selection screen into the Server Confirmation screen and add autocompletion when entering an account provider. (#5949)
* Adjust login flow to exclusively use the ServerSelectionScreen * Move async code to server selection vm * ServerSelection supports pro picker options * Use homeserver address for both confirmation and picker modes * rename ServerSelectionScreenMode * remove ServerConfirmationScreen It's been superseded by the ServerSelectionScreen * rename userInput * Update strings with latest design strings * Add server input autocompletion * Store previous servers in AppSettings and autofill on server signin * Audit changes for logging * Enforce lowercase server history storage * Update tests * Update Xcode project * PR review fixups * Add HomeserverHistoryManager * Review fixups 2 * Review fixups 3
This commit is contained in:
@@ -21,6 +21,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let homeserverHistoryManager: HomeserverHistoryManager
|
||||
private let appHooks: AppHooks
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
@@ -34,10 +35,9 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
/// The screen used for the whole QR Code flow.
|
||||
case qrCodeLoginScreen
|
||||
|
||||
/// The screen to continue authentication with the current server.
|
||||
case serverConfirmationScreen
|
||||
/// The screen to choose a different server.
|
||||
/// The screen used to choose a server
|
||||
case serverSelectionScreen
|
||||
|
||||
/// The screen to login with a password.
|
||||
case loginScreen
|
||||
|
||||
@@ -60,17 +60,12 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
/// The user would like to login with a QR code.
|
||||
case loginWithQR
|
||||
/// Show the server confirmation screen.
|
||||
case confirmServer(AuthenticationFlow)
|
||||
case selectServer(AuthenticationFlow)
|
||||
|
||||
/// The QR login flow was aborted.
|
||||
case cancelledLoginWithQR
|
||||
/// The user aborted manual login.
|
||||
case cancelledServerConfirmation
|
||||
|
||||
/// The user would like to enter a different server.
|
||||
case changeServer(AuthenticationFlow)
|
||||
/// The user is no longer selecting a server.
|
||||
case dismissedServerSelection
|
||||
case cancelledServerSelection
|
||||
|
||||
/// Show the screen to login with password (with the optional login hint in the `userInfo`).
|
||||
case continueWithPassword
|
||||
@@ -113,6 +108,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
self.appMediator = appMediator
|
||||
self.appSettings = appSettings
|
||||
homeserverHistoryManager = HomeserverHistoryManager(appSettings: appSettings)
|
||||
self.appHooks = appHooks
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
@@ -155,10 +151,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .qrCodeLoginScreen:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
stateMachine.tryEvent(.cancelledLoginWithQR) // Needs to be handled manually.
|
||||
case .serverConfirmationScreen:
|
||||
navigationStackCoordinator.popToRoot(animated: animated)
|
||||
case .serverSelectionScreen:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
navigationStackCoordinator.popToRoot(animated: animated)
|
||||
case .loginScreen:
|
||||
navigationStackCoordinator.popToRoot(animated: animated)
|
||||
@@ -202,28 +195,20 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
// Manual Authentication
|
||||
|
||||
stateMachine.addRoutes(event: .confirmServer(.login), transitions: [.startScreen => .serverConfirmationScreen]) { [weak self] _ in
|
||||
self?.showServerConfirmationScreen(authenticationFlow: .login)
|
||||
}
|
||||
stateMachine.addRoutes(event: .confirmServer(.register), transitions: [.startScreen => .serverConfirmationScreen]) { [weak self] _ in
|
||||
self?.showServerConfirmationScreen(authenticationFlow: .register)
|
||||
}
|
||||
stateMachine.addRoutes(event: .cancelledServerConfirmation, transitions: [.serverConfirmationScreen => .startScreen])
|
||||
|
||||
stateMachine.addRoutes(event: .changeServer(.login), transitions: [.serverConfirmationScreen => .serverSelectionScreen]) { [weak self] _ in
|
||||
stateMachine.addRoutes(event: .selectServer(.login), transitions: [.startScreen => .serverSelectionScreen]) { [weak self] _ in
|
||||
self?.showServerSelectionScreen(authenticationFlow: .login)
|
||||
}
|
||||
stateMachine.addRoutes(event: .changeServer(.register), transitions: [.serverConfirmationScreen => .serverSelectionScreen]) { [weak self] _ in
|
||||
stateMachine.addRoutes(event: .selectServer(.register), transitions: [.startScreen => .serverSelectionScreen]) { [weak self] _ in
|
||||
self?.showServerSelectionScreen(authenticationFlow: .register)
|
||||
}
|
||||
stateMachine.addRoutes(event: .dismissedServerSelection, transitions: [.serverSelectionScreen => .serverConfirmationScreen])
|
||||
stateMachine.addRoutes(event: .cancelledServerSelection, transitions: [.serverSelectionScreen => .startScreen])
|
||||
|
||||
stateMachine.addRoutes(event: .continueWithPassword, transitions: [.serverConfirmationScreen => .loginScreen,
|
||||
stateMachine.addRoutes(event: .continueWithPassword, transitions: [.serverSelectionScreen => .loginScreen,
|
||||
.startScreen => .loginScreen]) { [weak self] context in
|
||||
let loginHint = context.userInfo as? String
|
||||
self?.showLoginScreen(loginHint: loginHint, fromState: context.fromState)
|
||||
}
|
||||
stateMachine.addRoutes(event: .cancelledPasswordLogin(previousState: .serverConfirmationScreen), transitions: [.loginScreen => .serverConfirmationScreen])
|
||||
stateMachine.addRoutes(event: .cancelledPasswordLogin(previousState: .serverSelectionScreen), transitions: [.loginScreen => .serverSelectionScreen])
|
||||
stateMachine.addRoutes(event: .cancelledPasswordLogin(previousState: .startScreen), transitions: [.loginScreen => .startScreen])
|
||||
|
||||
// Bug Report
|
||||
@@ -243,7 +228,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
// Completion
|
||||
|
||||
stateMachine.addRoutes(event: .signedIn, transitions: [.qrCodeLoginScreen => .complete,
|
||||
.serverConfirmationScreen => .complete, // OAuth authentication
|
||||
.serverSelectionScreen => .complete, // OAuth authentication
|
||||
.startScreen => .complete, // Direct OAuth authentication
|
||||
.loginScreen => .complete]) { [weak self] context in
|
||||
guard let userSession = context.userInfo as? UserSessionProtocol else { fatalError("The user session wasn't included in the context") }
|
||||
@@ -292,9 +277,9 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .loginWithQR:
|
||||
stateMachine.tryEvent(.loginWithQR)
|
||||
case .login:
|
||||
stateMachine.tryEvent(.confirmServer(.login))
|
||||
stateMachine.tryEvent(.selectServer(.login))
|
||||
case .register:
|
||||
stateMachine.tryEvent(.confirmServer(.register))
|
||||
stateMachine.tryEvent(.selectServer(.register))
|
||||
|
||||
case .loginDirectlyWithOAuth(let oAuthData, let window):
|
||||
showOAuthAuthentication(oAuthData: oAuthData, presentationAnchor: window)
|
||||
@@ -336,7 +321,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .signInManually:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
stateMachine.tryEvent(.cancelledLoginWithQR)
|
||||
stateMachine.tryEvent(.confirmServer(.login))
|
||||
stateMachine.tryEvent(.selectServer(.login))
|
||||
case .signedIn(let userSession):
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
DispatchQueue.main.async {
|
||||
@@ -355,42 +340,15 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
// MARK: - Manual Authentication
|
||||
|
||||
private func showServerConfirmationScreen(authenticationFlow: AuthenticationFlow) {
|
||||
private func showServerSelectionScreen(authenticationFlow: AuthenticationFlow) {
|
||||
// Reset the service back to the default homeserver before continuing. This ensures
|
||||
// we check that registration is supported if it was previously configured for login.
|
||||
authenticationService.reset()
|
||||
|
||||
let parameters = ServerConfirmationScreenCoordinatorParameters(authenticationService: authenticationService,
|
||||
authenticationFlow: authenticationFlow,
|
||||
appSettings: appSettings,
|
||||
userIndicatorController: userIndicatorController)
|
||||
let coordinator = ServerConfirmationScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .continueWithOAuth(let oAuthData, let window):
|
||||
showOAuthAuthentication(oAuthData: oAuthData, presentationAnchor: window)
|
||||
case .continueWithPassword:
|
||||
stateMachine.tryEvent(.continueWithPassword)
|
||||
case .changeServer:
|
||||
stateMachine.tryEvent(.changeServer(authenticationFlow))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.cancelledServerConfirmation)
|
||||
}
|
||||
}
|
||||
|
||||
private func showServerSelectionScreen(authenticationFlow: AuthenticationFlow) {
|
||||
let navigationCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
|
||||
authenticationFlow: authenticationFlow,
|
||||
appSettings: appSettings,
|
||||
homeserverHistoryManager: homeserverHistoryManager,
|
||||
userIndicatorController: userIndicatorController)
|
||||
let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
|
||||
|
||||
@@ -399,17 +357,16 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .updated:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .continueWithOAuth(let oAuthData, let window):
|
||||
showOAuthAuthentication(oAuthData: oAuthData, presentationAnchor: window)
|
||||
case .continueWithPassword:
|
||||
stateMachine.tryEvent(.continueWithPassword)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationCoordinator.setRootCoordinator(coordinator)
|
||||
navigationStackCoordinator.setSheetCoordinator(navigationCoordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedServerSelection)
|
||||
navigationStackCoordinator.push(coordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.cancelledServerSelection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,5 +464,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
||||
delegate?.authenticationFlowCoordinator(didLoginWithSession: userSession)
|
||||
|
||||
let newServer = authenticationService.homeserver.value.address
|
||||
homeserverHistoryManager.addServerToList(newServer)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user