diff --git a/Riot/Modules/Onboarding/AuthenticationCoordinator.swift b/Riot/Modules/Onboarding/AuthenticationCoordinator.swift index 355cec9a4..ae9d72f0b 100644 --- a/Riot/Modules/Onboarding/AuthenticationCoordinator.swift +++ b/Riot/Modules/Onboarding/AuthenticationCoordinator.swift @@ -281,7 +281,7 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc let localizedPolicies = terms?.policiesData(forLanguage: Bundle.mxk_language(), defaultLanguage: Bundle.mxk_fallbackLanguage()) let parameters = AuthenticationTermsCoordinatorParameters(registrationWizard: registrationWizard, localizedPolicies: localizedPolicies ?? [], - homeserverAddress: homeserver.addressFromUser ?? homeserver.address) + homeserverAddress: homeserver.displayableAddress) let coordinator = AuthenticationTermsCoordinator(parameters: parameters) coordinator.callback = { [weak self] result in self?.registrationStageDidComplete(with: result) diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift new file mode 100644 index 000000000..bd92001aa --- /dev/null +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationHomeserverViewData.swift @@ -0,0 +1,75 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// Information about a homeserver that is ready for display in the authentication flow. +struct AuthenticationHomeserverViewData: Equatable { + /// The homeserver string to be shown to the user. + let address: String + /// Whether or not the homeserver is matrix.org. + let isMatrixDotOrg: Bool + /// Whether or not to display the username and password text fields during login. + let showLoginForm: Bool + /// Whether or not to display the username and password text fields during registration. + let showRegistrationForm: Bool + /// The supported SSO login options. + let ssoIdentityProviders: [SSOIdentityProvider] +} + +// MARK: - Mocks + +extension AuthenticationHomeserverViewData { + /// A mock homeserver that is configured just like matrix.org. + static var mockMatrixDotOrg: AuthenticationHomeserverViewData { + AuthenticationHomeserverViewData(address: "matrix.org", + isMatrixDotOrg: true, + showLoginForm: true, + showRegistrationForm: true, + ssoIdentityProviders: [ + SSOIdentityProvider(id: "1", name: "Apple", brand: "apple", iconURL: nil), + SSOIdentityProvider(id: "2", name: "Facebook", brand: "facebook", iconURL: nil), + SSOIdentityProvider(id: "3", name: "GitHub", brand: "github", iconURL: nil), + SSOIdentityProvider(id: "4", name: "GitLab", brand: "gitlab", iconURL: nil), + SSOIdentityProvider(id: "5", name: "Google", brand: "google", iconURL: nil) + ]) + } + + /// A mock homeserver that supports login and registration via a password but has no SSO providers. + static var mockBasicServer: AuthenticationHomeserverViewData { + AuthenticationHomeserverViewData(address: "example.com", + isMatrixDotOrg: false, + showLoginForm: true, + showRegistrationForm: true, + ssoIdentityProviders: []) + } + + /// A mock homeserver that supports only supports authentication via a single SSO provider. + static var mockEnterpriseSSO: AuthenticationHomeserverViewData { + AuthenticationHomeserverViewData(address: "company.com", + isMatrixDotOrg: false, + showLoginForm: false, + showRegistrationForm: false, + ssoIdentityProviders: [SSOIdentityProvider(id: "test", name: "SAML", brand: nil, iconURL: nil)]) + } +} + +struct HomeserverAddress { + /// Ensures the address contains a scheme, otherwise makes it `https`. + static func sanitized(_ address: String) -> String { + !address.contains("://") ? "https://\(address.lowercased())" : address.lowercased() + } +} diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift index 63f5642f3..2aa8df779 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift @@ -25,7 +25,7 @@ struct AuthenticationServerInfoSection: View { // MARK: - Public let address: String - let description: String? + let showMatrixDotOrgInfo: Bool let editAction: () -> Void // MARK: - Views @@ -42,8 +42,8 @@ struct AuthenticationServerInfoSection: View { .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) - if let description = description { - Text(description) + if showMatrixDotOrgInfo { + Text(VectorL10n.authenticationServerInfoMatrixDescription) .font(theme.fonts.caption1) .foregroundColor(theme.colors.tertiaryContent) .accessibilityIdentifier("serverDescriptionText") diff --git a/RiotSwiftUI/Modules/Authentication/Common/HomeserverAddress.swift b/RiotSwiftUI/Modules/Authentication/Common/HomeserverAddress.swift deleted file mode 100644 index d0bca43e0..000000000 --- a/RiotSwiftUI/Modules/Authentication/Common/HomeserverAddress.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -struct HomeserverAddress { - /// Ensures the address contains a scheme, otherwise makes it `https`. - static func sanitized(_ address: String) -> String { - !address.contains("://") ? "https://\(address.lowercased())" : address.lowercased() - } - - /// Strips the `https://` away from the address (but leaves `http://`) for display in labels. - /// - /// `http://` is left in the string to make it clear when a chosen server doesn't use SSL. - static func displayable(_ address: String) -> String { - address.replacingOccurrences(of: "https://", with: "") - } -} diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift index 4251cab79..0ba85300f 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift @@ -188,8 +188,7 @@ class AuthenticationService: NSObject { let state = AuthenticationState(flow: .login, homeserver: .init(address: loginFlow.homeserverAddress, addressFromUser: homeserverAddress, - preferredLoginMode: loginFlow.loginMode, - loginModeSupportedTypes: loginFlow.supportedLoginTypes)) + preferredLoginMode: loginFlow.loginMode)) return (client, state) } @@ -205,8 +204,7 @@ class AuthenticationService: NSObject { let loginFlow = try await getLoginFlowResult(client: session.matrixRestClient) let state = AuthenticationState(flow: .login, homeserver: .init(address: loginFlow.homeserverAddress, - preferredLoginMode: loginFlow.loginMode, - loginModeSupportedTypes: loginFlow.supportedLoginTypes)) + preferredLoginMode: loginFlow.loginMode)) return (client, state) } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift index 1e470e372..637e5abe4 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift @@ -40,11 +40,14 @@ struct AuthenticationState { var address: String /// The homeserver address as input by the user (it can differ to the well-known request). var addressFromUser: String? + /// The homeserver's address formatted to be displayed to the user in labels, text fields etc. + var displayableAddress: String { + let address = addressFromUser ?? address + return address.replacingOccurrences(of: "https://", with: "") // Only remove https. Leave http to indicate the server doesn't use SSL. + } /// The preferred login mode for the server var preferredLoginMode: LoginMode = .unknown - /// Supported types for the login. - var loginModeSupportedTypes = [MXLoginFlow]() /// The response returned when querying the homeserver for registration flows. var registrationFlow: RegistrationResult? @@ -54,5 +57,13 @@ struct AuthenticationState { guard let url = URL(string: address) else { return false } return url.host == "matrix.org" || url.host == "matrix-client.matrix.org" } + + var viewData: AuthenticationHomeserverViewData { + AuthenticationHomeserverViewData(address: displayableAddress, + isMatrixDotOrg: isMatrixDotOrg, + showLoginForm: preferredLoginMode.supportsPasswordFlow, + showRegistrationForm: registrationFlow != nil, + ssoIdentityProviders: preferredLoginMode.ssoIdentityProviders ?? []) + } } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index c3ca7ea45..e2f3988f5 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -32,24 +32,14 @@ enum AuthenticationLoginViewModelResult { // MARK: View struct AuthenticationLoginViewState: BindableState { - /// The address of the homeserver. - var homeserverAddress: String - /// Whether or not to show the username and password text fields with the next button - var showLoginForm: Bool - /// An array containing the available SSO options for login. - var ssoIdentityProviders: [SSOIdentityProvider] + /// Data about the selected homeserver. + var homeserver: AuthenticationHomeserverViewData /// View state that can be bound to from SwiftUI. var bindings: AuthenticationLoginBindings - /// A description that can be shown for the currently selected homeserver. - var serverDescription: String? { - guard homeserverAddress == "matrix.org" else { return nil } - return VectorL10n.authenticationServerInfoMatrixDescription - } - /// Whether to show any SSO buttons. var showSSOButtons: Bool { - !ssoIdentityProviders.isEmpty + !homeserver.ssoIdentityProviders.isEmpty } /// `true` if it is possible to continue, otherwise `false`. diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index aab6edbc2..7d0adedd0 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -30,12 +30,9 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica // MARK: - Setup - init(homeserverAddress: String, showLoginForm: Bool = true, ssoIdentityProviders: [SSOIdentityProvider]) { + init(homeserver: AuthenticationHomeserverViewData) { let bindings = AuthenticationLoginBindings() - let viewState = AuthenticationLoginViewState(homeserverAddress: HomeserverAddress.displayable(homeserverAddress), - showLoginForm: showLoginForm, - ssoIdentityProviders: ssoIdentityProviders, - bindings: bindings) + let viewState = AuthenticationLoginViewState(homeserver: homeserver, bindings: bindings) super.init(initialViewState: viewState) } @@ -57,10 +54,8 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica } } - @MainActor func update(homeserverAddress: String, showLoginForm: Bool, ssoIdentityProviders: [SSOIdentityProvider]) { - state.homeserverAddress = HomeserverAddress.displayable(homeserverAddress) - state.showLoginForm = showLoginForm - state.ssoIdentityProviders = ssoIdentityProviders + @MainActor func update(homeserver: AuthenticationHomeserverViewData) { + state.homeserver = homeserver } @MainActor func displayError(_ type: AuthenticationLoginErrorType) { diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift index a9552cf4e..669b9189a 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift @@ -22,11 +22,7 @@ protocol AuthenticationLoginViewModelProtocol { var context: AuthenticationLoginViewModelType.Context { get } /// Update the view with new homeserver information. - /// - Parameters: - /// - homeserverAddress: The homeserver string to be shown to the user. - /// - showLoginForm: Whether or not to display the username and password text fields. - /// - ssoIdentityProviders: The supported SSO login options. - @MainActor func update(homeserverAddress: String, showLoginForm: Bool, ssoIdentityProviders: [SSOIdentityProvider]) + @MainActor func update(homeserver: AuthenticationHomeserverViewData) /// Display an error to the user. @MainActor func displayError(_ type: AuthenticationLoginErrorType) diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index b7f989876..8e062b764 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -67,9 +67,7 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { self.parameters = parameters let homeserver = parameters.authenticationService.state.homeserver - let viewModel = AuthenticationLoginViewModel(homeserverAddress: homeserver.addressFromUser ?? homeserver.address, - showLoginForm: homeserver.preferredLoginMode.supportsPasswordFlow, - ssoIdentityProviders: parameters.loginMode.ssoIdentityProviders ?? []) + let viewModel = AuthenticationLoginViewModel(homeserver: homeserver.viewData) authenticationLoginViewModel = viewModel let view = AuthenticationLoginScreen(viewModel: viewModel.context) @@ -223,10 +221,9 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { } } + /// Updates the view model to reflect any changes made to the homeserver. @MainActor private func updateViewModel() { let homeserver = authenticationService.state.homeserver - authenticationLoginViewModel.update(homeserverAddress: homeserver.addressFromUser ?? homeserver.address, - showLoginForm: homeserver.preferredLoginMode.supportsPasswordFlow, - ssoIdentityProviders: homeserver.preferredLoginMode.ssoIdentityProviders ?? []) + authenticationLoginViewModel.update(homeserver: homeserver.viewData) } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift b/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift index 6b42c3856..fd0afb87d 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/MockAuthenticationLoginScreenState.swift @@ -38,23 +38,15 @@ enum MockAuthenticationLoginScreenState: MockScreenState, CaseIterable { let viewModel: AuthenticationLoginViewModel switch self { case .matrixDotOrg: - viewModel = AuthenticationLoginViewModel(homeserverAddress: "https://matrix.org", ssoIdentityProviders: [ - SSOIdentityProvider(id: "1", name: "Apple", brand: "apple", iconURL: nil), - SSOIdentityProvider(id: "2", name: "Facebook", brand: "facebook", iconURL: nil), - SSOIdentityProvider(id: "3", name: "GitHub", brand: "github", iconURL: nil), - SSOIdentityProvider(id: "4", name: "GitLab", brand: "gitlab", iconURL: nil), - SSOIdentityProvider(id: "5", name: "Google", brand: "google", iconURL: nil) - ]) + viewModel = AuthenticationLoginViewModel(homeserver: .mockMatrixDotOrg) case .passwordOnly: - viewModel = AuthenticationLoginViewModel(homeserverAddress: "https://example.com", ssoIdentityProviders: []) + viewModel = AuthenticationLoginViewModel(homeserver: .mockBasicServer) case .passwordWithCredentials: - viewModel = AuthenticationLoginViewModel(homeserverAddress: "https://example.com", ssoIdentityProviders: []) + viewModel = AuthenticationLoginViewModel(homeserver: .mockBasicServer) viewModel.context.username = "alice" viewModel.context.password = "password" case .ssoOnly: - viewModel = AuthenticationLoginViewModel(homeserverAddress: "https://company.com", - showLoginForm: false, - ssoIdentityProviders: [SSOIdentityProvider(id: "test", name: "SAML", brand: nil, iconURL: nil)]) + viewModel = AuthenticationLoginViewModel(homeserver: .mockEnterpriseSSO) } diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 47ef0845d..672e9931b 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -47,11 +47,11 @@ struct AuthenticationLoginScreen: View { .frame(height: 1) .padding(.vertical, 21) - if viewModel.viewState.showLoginForm { + if viewModel.viewState.homeserver.showLoginForm { loginForm } - if viewModel.viewState.showLoginForm && viewModel.viewState.showSSOButtons { + if viewModel.viewState.homeserver.showLoginForm && viewModel.viewState.showSSOButtons { Text(VectorL10n.or) .foregroundColor(theme.colors.secondaryContent) .padding(.top, 16) @@ -82,8 +82,8 @@ struct AuthenticationLoginScreen: View { /// The sever information section that includes a button to select a different server. var serverInfo: some View { - AuthenticationServerInfoSection(address: viewModel.viewState.homeserverAddress, - description: viewModel.viewState.serverDescription) { + AuthenticationServerInfoSection(address: viewModel.viewState.homeserver.address, + showMatrixDotOrgInfo: viewModel.viewState.homeserver.isMatrixDotOrg) { viewModel.send(viewAction: .selectServer) } } @@ -129,7 +129,7 @@ struct AuthenticationLoginScreen: View { /// A list of SSO buttons that can be used for login. var ssoButtons: some View { VStack(spacing: 16) { - ForEach(viewModel.viewState.ssoIdentityProviders) { provider in + ForEach(viewModel.viewState.homeserver.ssoIdentityProviders) { provider in AuthenticationSSOButton(provider: provider) { viewModel.send(viewAction: .continueWithSSO(id: provider.id)) } diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift index c86f20f32..83228358e 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationModels.swift @@ -30,12 +30,8 @@ enum AuthenticationRegistrationViewModelResult { // MARK: View struct AuthenticationRegistrationViewState: BindableState { - /// The address of the homeserver. - var homeserverAddress: String - /// Whether or not to show the username and password text fields with the next button - var showRegistrationForm: Bool - /// An array containing the available SSO options for login. - var ssoIdentityProviders: [SSOIdentityProvider] + /// Data about the selected homeserver. + var homeserver: AuthenticationHomeserverViewData /// View state that can be bound to from SwiftUI. var bindings: AuthenticationRegistrationBindings /// Whether or not the username field has been edited yet. @@ -55,15 +51,9 @@ struct AuthenticationRegistrationViewState: BindableState { usernameErrorMessage ?? VectorL10n.authenticationRegistrationUsernameFooter } - /// A description that can be shown for the currently selected homeserver. - var serverDescription: String? { - guard homeserverAddress == "matrix.org" else { return nil } - return VectorL10n.authenticationServerInfoMatrixDescription - } - /// Whether to show any SSO buttons. var showSSOButtons: Bool { - !ssoIdentityProviders.isEmpty + !homeserver.ssoIdentityProviders.isEmpty } /// Whether the current `username` is valid. diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift index 5b12823f3..67dc8f7ee 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModel.swift @@ -31,12 +31,9 @@ class AuthenticationRegistrationViewModel: AuthenticationRegistrationViewModelTy // MARK: - Setup - init(homeserverAddress: String, showRegistrationForm: Bool = true, ssoIdentityProviders: [SSOIdentityProvider]) { + init(homeserver: AuthenticationHomeserverViewData) { let bindings = AuthenticationRegistrationBindings() - let viewState = AuthenticationRegistrationViewState(homeserverAddress: HomeserverAddress.displayable(homeserverAddress), - showRegistrationForm: showRegistrationForm, - ssoIdentityProviders: ssoIdentityProviders, - bindings: bindings) + let viewState = AuthenticationRegistrationViewState(homeserver: homeserver, bindings: bindings) super.init(initialViewState: viewState) } @@ -60,10 +57,8 @@ class AuthenticationRegistrationViewModel: AuthenticationRegistrationViewModelTy } } - @MainActor func update(homeserverAddress: String, showRegistrationForm: Bool, ssoIdentityProviders: [SSOIdentityProvider]) { - state.homeserverAddress = HomeserverAddress.displayable(homeserverAddress) - state.showRegistrationForm = showRegistrationForm - state.ssoIdentityProviders = ssoIdentityProviders + @MainActor func update(homeserver: AuthenticationHomeserverViewData) { + state.homeserver = homeserver } @MainActor func displayError(_ type: AuthenticationRegistrationErrorType) { diff --git a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift index a10a9a4d7..3315fa13c 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/AuthenticationRegistrationViewModelProtocol.swift @@ -22,11 +22,7 @@ protocol AuthenticationRegistrationViewModelProtocol { var context: AuthenticationRegistrationViewModelType.Context { get } /// Update the view with new homeserver information. - /// - Parameters: - /// - homeserverAddress: The homeserver string to be shown to the user. - /// - showRegistrationForm: Whether or not to display the username and password text fields. - /// - ssoIdentityProviders: The supported SSO login options. - @MainActor func update(homeserverAddress: String, showRegistrationForm: Bool, ssoIdentityProviders: [SSOIdentityProvider]) + @MainActor func update(homeserver: AuthenticationHomeserverViewData) /// Display an error to the user. @MainActor func displayError(_ type: AuthenticationRegistrationErrorType) diff --git a/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift index 4bfa45cd5..3b4aa1c62 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/Coordinator/AuthenticationRegistrationCoordinator.swift @@ -69,9 +69,7 @@ final class AuthenticationRegistrationCoordinator: Coordinator, Presentable { self.parameters = parameters let homeserver = parameters.authenticationService.state.homeserver - let viewModel = AuthenticationRegistrationViewModel(homeserverAddress: homeserver.addressFromUser ?? homeserver.address, - showRegistrationForm: homeserver.registrationFlow != nil, - ssoIdentityProviders: parameters.loginMode.ssoIdentityProviders ?? []) + let viewModel = AuthenticationRegistrationViewModel(homeserver: homeserver.viewData) authenticationRegistrationViewModel = viewModel let view = AuthenticationRegistrationScreen(viewModel: viewModel.context) @@ -226,9 +224,7 @@ final class AuthenticationRegistrationCoordinator: Coordinator, Presentable { didCompleteWith result: AuthenticationServerSelectionCoordinatorResult) { if result == .updated { let homeserver = authenticationService.state.homeserver - authenticationRegistrationViewModel.update(homeserverAddress: homeserver.addressFromUser ?? homeserver.address, - showRegistrationForm: homeserver.registrationFlow != nil, - ssoIdentityProviders: homeserver.preferredLoginMode.ssoIdentityProviders ?? []) + authenticationRegistrationViewModel.update(homeserver: homeserver.viewData) } navigationRouter.dismissModule(animated: true) { [weak self] in diff --git a/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift b/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift index 7f5721fa9..561fda0b8 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/MockAuthenticationRegistrationScreenState.swift @@ -39,27 +39,19 @@ enum MockAuthenticationRegistrationScreenState: MockScreenState, CaseIterable { let viewModel: AuthenticationRegistrationViewModel switch self { case .matrixDotOrg: - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "https://matrix.org", ssoIdentityProviders: [ - SSOIdentityProvider(id: "1", name: "Apple", brand: "apple", iconURL: nil), - SSOIdentityProvider(id: "2", name: "Facebook", brand: "facebook", iconURL: nil), - SSOIdentityProvider(id: "3", name: "GitHub", brand: "github", iconURL: nil), - SSOIdentityProvider(id: "4", name: "GitLab", brand: "gitlab", iconURL: nil), - SSOIdentityProvider(id: "5", name: "Google", brand: "google", iconURL: nil) - ]) + viewModel = AuthenticationRegistrationViewModel(homeserver: .mockMatrixDotOrg) case .passwordOnly: - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "https://example.com", ssoIdentityProviders: []) + viewModel = AuthenticationRegistrationViewModel(homeserver: .mockBasicServer) case .passwordWithCredentials: - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "https://example.com", ssoIdentityProviders: []) + viewModel = AuthenticationRegistrationViewModel(homeserver: .mockBasicServer) viewModel.context.username = "alice" viewModel.context.password = "password" case .passwordWithUsernameError: - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "https://example.com", ssoIdentityProviders: []) + viewModel = AuthenticationRegistrationViewModel(homeserver: .mockBasicServer) viewModel.state.hasEditedUsername = true Task { await viewModel.displayError(.usernameUnavailable(VectorL10n.authInvalidUserName)) } case .ssoOnly: - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "https://company.com", - showRegistrationForm: false, - ssoIdentityProviders: [SSOIdentityProvider(id: "test", name: "SAML", brand: nil, iconURL: nil)]) + viewModel = AuthenticationRegistrationViewModel(homeserver: .mockEnterpriseSSO) } diff --git a/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift b/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift index 0d78b9b1e..9ce6cfbbc 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/Test/Unit/AuthenticationRegistrationViewModelTests.swift @@ -20,78 +20,36 @@ import Combine @testable import RiotSwiftUI @MainActor class AuthenticationRegistrationViewModelTests: XCTestCase { + let defaultHomeserver = AuthenticationHomeserverViewData.mockMatrixDotOrg var viewModel: AuthenticationRegistrationViewModelProtocol! var context: AuthenticationRegistrationViewModelType.Context! @MainActor override func setUp() async throws { - viewModel = AuthenticationRegistrationViewModel(homeserverAddress: "", ssoIdentityProviders: []) + viewModel = AuthenticationRegistrationViewModel(homeserver: defaultHomeserver) context = viewModel.context } func testMatrixDotOrg() { - // Given matrix.org with some SSO providers. - let address = "https://matrix.org" - let ssoProviders = [ - SSOIdentityProvider(id: "apple", name: "Apple", brand: "Apple", iconURL: nil), - SSOIdentityProvider(id: "google", name: "Google", brand: "Google", iconURL: nil), - SSOIdentityProvider(id: "github", name: "Github", brand: "Github", iconURL: nil) - ] + // Given the initial view model configured for matrix.org with some SSO providers. + let homeserver = defaultHomeserver - // When updating the view model with the server. - viewModel.update(homeserverAddress: address, showRegistrationForm: true, ssoIdentityProviders: ssoProviders) - - // Then the form should show the server description along with the username and password fields and the SSO buttons. - XCTAssertEqual(context.viewState.homeserverAddress, "matrix.org", "The homeserver address should have the https scheme stripped away.") - XCTAssertEqual(context.viewState.serverDescription, VectorL10n.authenticationRegistrationMatrixDescription, "A description should be shown for matrix.org.") - XCTAssertTrue(context.viewState.showRegistrationForm, "The username and password section should be shown.") + // Then the view state should contain a homeserver that matches matrix.org and shows SSO buttons. + XCTAssertEqual(context.viewState.homeserver, homeserver, "The homeserver data should match the original.") XCTAssertTrue(context.viewState.showSSOButtons, "The SSO buttons should be shown.") } func testBasicServer() { // Given a basic server example.com that only supports password registration. - let address = "https://example.com" + let homeserver = AuthenticationHomeserverViewData.mockBasicServer // When updating the view model with the server. - viewModel.update(homeserverAddress: address, showRegistrationForm: true, ssoIdentityProviders: []) + viewModel.update(homeserver: homeserver) - // Then the form should only show the username and password section. - XCTAssertEqual(context.viewState.homeserverAddress, "example.com", "The homeserver address should have the https scheme stripped away.") - XCTAssertNil(context.viewState.serverDescription, "A description should not be shown when the server isn't matrix.org.") - XCTAssertTrue(context.viewState.showRegistrationForm, "The username and password section should be shown.") + // Then the view state should be updated with the homeserver and hide the SSO buttons. + XCTAssertEqual(context.viewState.homeserver, homeserver, "The homeserver data should should match the new homeserver.") XCTAssertFalse(context.viewState.showSSOButtons, "The SSO buttons should not be shown.") } - func testUnsecureServer() { - // Given a server that uses http for communication. - let address = "http://testserver.local" - - // When updating the view model with the server. - viewModel.update(homeserverAddress: address, showRegistrationForm: true, ssoIdentityProviders: []) - - // Then the form should only show the username and password section. - XCTAssertEqual(context.viewState.homeserverAddress, address, "The homeserver address should show the http scheme.") - XCTAssertNil(context.viewState.serverDescription, "A description should not be shown when the server isn't matrix.org.") - } - - func testSSOOnlyServer() { - // Given matrix.org with some SSO providers. - let address = "https://example.com" - let ssoProviders = [ - SSOIdentityProvider(id: "apple", name: "Apple", brand: "Apple", iconURL: nil), - SSOIdentityProvider(id: "google", name: "Google", brand: "Google", iconURL: nil), - SSOIdentityProvider(id: "github", name: "Github", brand: "Github", iconURL: nil) - ] - - // When updating the view model with the server. - viewModel.update(homeserverAddress: address, showRegistrationForm: false, ssoIdentityProviders: ssoProviders) - - // Then the form should show the server description along with the username and password fields and the SSO buttons. - XCTAssertEqual(context.viewState.homeserverAddress, "example.com", "The homeserver address should have the https scheme stripped away.") - XCTAssertNil(context.viewState.serverDescription, "A description should not be shown when the server isn't matrix.org.") - XCTAssertFalse(context.viewState.showRegistrationForm, "The username and password section should not be shown.") - XCTAssertTrue(context.viewState.showSSOButtons, "The SSO buttons should be shown.") - } - func testUsernameError() async { // Given a form with a valid username. context.username = "bob" diff --git a/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift b/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift index b0c137ceb..c2106e299 100644 --- a/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Registration/View/AuthenticationRegistrationScreen.swift @@ -45,11 +45,11 @@ struct AuthenticationRegistrationScreen: View { .frame(height: 1) .padding(.vertical, 21) - if viewModel.viewState.showRegistrationForm { + if viewModel.viewState.homeserver.showRegistrationForm { registrationForm } - if viewModel.viewState.showRegistrationForm && viewModel.viewState.showSSOButtons { + if viewModel.viewState.homeserver.showRegistrationForm && viewModel.viewState.showSSOButtons { Text(VectorL10n.or) .foregroundColor(theme.colors.secondaryContent) .padding(.top, 16) @@ -90,8 +90,8 @@ struct AuthenticationRegistrationScreen: View { /// The sever information section that includes a button to select a different server. var serverInfo: some View { - AuthenticationServerInfoSection(address: viewModel.viewState.homeserverAddress, - description: viewModel.viewState.serverDescription) { + AuthenticationServerInfoSection(address: viewModel.viewState.homeserver.address, + showMatrixDotOrgInfo: viewModel.viewState.homeserver.isMatrixDotOrg) { viewModel.send(viewAction: .selectServer) } } @@ -135,7 +135,7 @@ struct AuthenticationRegistrationScreen: View { /// A list of SSO buttons that can be used for login. var ssoButtons: some View { VStack(spacing: 16) { - ForEach(viewModel.viewState.ssoIdentityProviders) { provider in + ForEach(viewModel.viewState.homeserver.ssoIdentityProviders) { provider in AuthenticationSSOButton(provider: provider) { viewModel.send(viewAction: .continueWithSSO(id: provider.id)) } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift index 4484fbcad..ca68ce068 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionViewModel.swift @@ -33,7 +33,7 @@ class AuthenticationServerSelectionViewModel: AuthenticationServerSelectionViewM // MARK: - Setup init(homeserverAddress: String, hasModalPresentation: Bool) { - let bindings = AuthenticationServerSelectionBindings(homeserverAddress: HomeserverAddress.displayable(homeserverAddress)) + let bindings = AuthenticationServerSelectionBindings(homeserverAddress: homeserverAddress) super.init(initialViewState: AuthenticationServerSelectionViewState(bindings: bindings, hasModalPresentation: hasModalPresentation)) } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift index 23a62f658..3ad2b4866 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift @@ -58,7 +58,7 @@ final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable { self.parameters = parameters let homeserver = parameters.authenticationService.state.homeserver - let viewModel = AuthenticationServerSelectionViewModel(homeserverAddress: homeserver.addressFromUser ?? homeserver.address, + let viewModel = AuthenticationServerSelectionViewModel(homeserverAddress: homeserver.displayableAddress, hasModalPresentation: parameters.hasModalPresentation) let view = AuthenticationServerSelectionScreen(viewModel: viewModel.context) authenticationServerSelectionViewModel = viewModel diff --git a/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift index 252406a0e..04a3bac6f 100644 --- a/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Terms/Coordinator/AuthenticationTermsCoordinator.swift @@ -59,7 +59,7 @@ final class AuthenticationTermsCoordinator: Coordinator, Presentable { @MainActor init(parameters: AuthenticationTermsCoordinatorParameters) { self.parameters = parameters - let subtitle = HomeserverAddress.displayable(parameters.homeserverAddress) + let subtitle = parameters.homeserverAddress let policies = parameters.localizedPolicies.compactMap { AuthenticationTermsPolicy(url: $0.url, title: $0.name, subtitle: subtitle) } let viewModel = AuthenticationTermsViewModel(policies: policies)