mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Configure the AuthenticationService later now that we have 2 flows on the start screen. (#3316)
* Don't query the homeserver until confirming it (or selecting a different one). * Setup the infrastructure to test AuthenticationService. Implement basic tests for configuration & password login. * Use the real AuthenticationService with a mock Client in all of the tests. * Add tests for the ServerConfirmationScreenViewModel. * Remove redundant view state and test for it.
This commit is contained in:
+5
-1
@@ -11,6 +11,8 @@ import SwiftUI
|
||||
struct ServerConfirmationScreenCoordinatorParameters {
|
||||
let authenticationService: AuthenticationServiceProtocol
|
||||
let authenticationFlow: AuthenticationFlow
|
||||
let slidingSyncLearnMoreURL: URL
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
enum ServerConfirmationScreenCoordinatorAction {
|
||||
@@ -29,7 +31,9 @@ final class ServerConfirmationScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
init(parameters: ServerConfirmationScreenCoordinatorParameters) {
|
||||
viewModel = ServerConfirmationScreenViewModel(authenticationService: parameters.authenticationService,
|
||||
authenticationFlow: parameters.authenticationFlow)
|
||||
authenticationFlow: parameters.authenticationFlow,
|
||||
slidingSyncLearnMoreURL: parameters.slidingSyncLearnMoreURL,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
||||
+21
-15
@@ -19,11 +19,11 @@ struct ServerConfirmationScreenViewState: BindableState {
|
||||
var homeserverAddress: String
|
||||
/// The flow being attempted on the selected homeserver.
|
||||
let authenticationFlow: AuthenticationFlow
|
||||
/// Whether or not the homeserver supports registration.
|
||||
var homeserverSupportsRegistration = false
|
||||
/// The presentation anchor used for OIDC authentication.
|
||||
var window: UIWindow?
|
||||
|
||||
var bindings = ServerConfirmationScreenBindings()
|
||||
|
||||
/// The screen's title.
|
||||
var title: String {
|
||||
switch authenticationFlow {
|
||||
@@ -46,23 +46,16 @@ struct ServerConfirmationScreenViewState: BindableState {
|
||||
""
|
||||
}
|
||||
case .register:
|
||||
if canContinue {
|
||||
L10n.screenServerConfirmationMessageRegister
|
||||
} else {
|
||||
L10n.errorAccountCreationNotPossible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not it is valid to continue the flow.
|
||||
var canContinue: Bool {
|
||||
switch authenticationFlow {
|
||||
case .login: true
|
||||
case .register: homeserverSupportsRegistration
|
||||
L10n.screenServerConfirmationMessageRegister
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerConfirmationScreenBindings {
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<ServerConfirmationScreenAlert>?
|
||||
}
|
||||
|
||||
enum ServerConfirmationScreenViewAction {
|
||||
/// Updates the window used as the OIDC presentation anchor.
|
||||
case updateWindow(UIWindow)
|
||||
@@ -71,3 +64,16 @@ enum ServerConfirmationScreenViewAction {
|
||||
/// The user would like to change to a different homeserver.
|
||||
case changeServer
|
||||
}
|
||||
|
||||
enum ServerConfirmationScreenAlert: Hashable {
|
||||
/// An alert that informs the user that a server could not be found.
|
||||
case homeserverNotFound
|
||||
/// An alert that informs the user about a bad well-known file.
|
||||
case invalidWellKnown(String)
|
||||
/// An alert that allows the user to learn about sliding sync.
|
||||
case slidingSync
|
||||
/// An alert that informs the user that registration isn't supported.
|
||||
case registration
|
||||
/// An unknown error has occurred.
|
||||
case unknownError
|
||||
}
|
||||
|
||||
+85
-13
@@ -11,46 +11,118 @@ import SwiftUI
|
||||
typealias ServerConfirmationScreenViewModelType = StateStoreViewModel<ServerConfirmationScreenViewState, ServerConfirmationScreenViewAction>
|
||||
|
||||
class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType, ServerConfirmationScreenViewModelProtocol {
|
||||
let authenticationService: AuthenticationServiceProtocol
|
||||
let authenticationFlow: AuthenticationFlow
|
||||
let slidingSyncLearnMoreURL: URL
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private var actionsSubject: PassthroughSubject<ServerConfirmationScreenViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<ServerConfirmationScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(authenticationService: AuthenticationServiceProtocol, authenticationFlow: AuthenticationFlow) {
|
||||
let homeserver = authenticationService.homeserver.value
|
||||
init(authenticationService: AuthenticationServiceProtocol,
|
||||
authenticationFlow: AuthenticationFlow,
|
||||
slidingSyncLearnMoreURL: URL,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.authenticationService = authenticationService
|
||||
self.authenticationFlow = authenticationFlow
|
||||
self.slidingSyncLearnMoreURL = slidingSyncLearnMoreURL
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
let homeserver = authenticationService.homeserver.value
|
||||
super.init(initialViewState: ServerConfirmationScreenViewState(homeserverAddress: homeserver.address,
|
||||
authenticationFlow: authenticationFlow,
|
||||
homeserverSupportsRegistration: homeserver.supportsRegistration))
|
||||
authenticationFlow: authenticationFlow))
|
||||
|
||||
authenticationService.homeserver
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] homeserver in
|
||||
guard let self else { return }
|
||||
state.homeserverAddress = homeserver.address
|
||||
state.homeserverSupportsRegistration = homeserver.supportsRegistration
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ServerConfirmationScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .updateWindow(let window):
|
||||
guard state.window != window else { return }
|
||||
Task { state.window = window }
|
||||
case .confirm:
|
||||
actionsSubject.send(.confirm)
|
||||
Task { await configureAndContinue() }
|
||||
case .changeServer:
|
||||
actionsSubject.send(.changeServer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LoginHomeserver {
|
||||
var supportsRegistration: Bool {
|
||||
loginMode == .oidc || (address == "matrix.org" && registrationHelperURL != nil)
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func configureAndContinue() async {
|
||||
let homeserver = authenticationService.homeserver.value
|
||||
|
||||
// If the login mode is unknown, the service hasn't be configured and we need to do it now.
|
||||
// Otherwise we can continue the flow as server selection has been performed and succeeded.
|
||||
guard homeserver.loginMode == .unknown || authenticationService.flow != authenticationFlow else {
|
||||
actionsSubject.send(.confirm)
|
||||
return
|
||||
}
|
||||
|
||||
startLoading()
|
||||
defer { stopLoading() }
|
||||
|
||||
switch await authenticationService.configure(for: homeserver.address, flow: authenticationFlow) {
|
||||
case .success:
|
||||
actionsSubject.send(.confirm)
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case .invalidServer, .invalidHomeserverAddress:
|
||||
displayError(.homeserverNotFound)
|
||||
case .invalidWellKnown(let error):
|
||||
displayError(.invalidWellKnown(error))
|
||||
case .slidingSyncNotAvailable:
|
||||
displayError(.slidingSync)
|
||||
case .registrationNotSupported:
|
||||
displayError(.registration)
|
||||
default:
|
||||
displayError(.unknownError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startLoading(label: String = L10n.commonLoading) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(type: .modal,
|
||||
title: label,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func stopLoading() {
|
||||
userIndicatorController.retractAllIndicators()
|
||||
}
|
||||
|
||||
private func displayError(_ type: ServerConfirmationScreenAlert) {
|
||||
switch type {
|
||||
case .homeserverNotFound:
|
||||
state.bindings.alertInfo = AlertInfo(id: .homeserverNotFound,
|
||||
title: L10n.errorUnknown,
|
||||
message: L10n.screenChangeServerErrorInvalidHomeserver)
|
||||
case .invalidWellKnown(let error):
|
||||
state.bindings.alertInfo = AlertInfo(id: .invalidWellKnown(error),
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenChangeServerErrorInvalidWellKnown(error))
|
||||
case .slidingSync:
|
||||
let openURL = { UIApplication.shared.open(self.slidingSyncLearnMoreURL) }
|
||||
state.bindings.alertInfo = AlertInfo(id: .slidingSync,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenChangeServerErrorNoSlidingSyncMessage,
|
||||
primaryButton: .init(title: L10n.actionLearnMore, role: .cancel, action: openURL),
|
||||
secondaryButton: .init(title: L10n.actionCancel, action: nil))
|
||||
case .registration:
|
||||
state.bindings.alertInfo = AlertInfo(id: .registration,
|
||||
title: L10n.errorUnknown,
|
||||
message: L10n.errorAccountCreationNotPossible)
|
||||
case .unknownError:
|
||||
state.bindings.alertInfo = AlertInfo(id: .unknownError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -19,6 +19,7 @@ struct ServerConfirmationScreen: View {
|
||||
}
|
||||
.background()
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.alert(item: $context.alertInfo)
|
||||
.introspect(.window, on: .supportedVersions) { window in
|
||||
context.send(viewAction: .updateWindow(window))
|
||||
}
|
||||
@@ -53,7 +54,6 @@ struct ServerConfirmationScreen: View {
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.continue)
|
||||
.disabled(!context.viewState.canContinue)
|
||||
|
||||
Button { context.send(viewAction: .changeServer) } label: {
|
||||
Text(L10n.screenServerConfirmationChangeServer)
|
||||
@@ -68,10 +68,8 @@ struct ServerConfirmationScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let loginViewModel = ServerConfirmationScreenViewModel(authenticationService: MockAuthenticationService(),
|
||||
authenticationFlow: .login)
|
||||
static let registerViewModel = ServerConfirmationScreenViewModel(authenticationService: MockAuthenticationService(),
|
||||
authenticationFlow: .register)
|
||||
static let loginViewModel = makeViewModel(flow: .login)
|
||||
static let registerViewModel = makeViewModel(flow: .register)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
@@ -86,4 +84,11 @@ struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
.previewDisplayName("Register")
|
||||
}
|
||||
|
||||
static func makeViewModel(flow: AuthenticationFlow) -> ServerConfirmationScreenViewModel {
|
||||
ServerConfirmationScreenViewModel(authenticationService: AuthenticationService.mock,
|
||||
authenticationFlow: flow,
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user