Server Confirmation Screen (#959)

* Add ServerConfirmationScreen.
This commit is contained in:
Doug
2023-05-30 10:31:18 +01:00
committed by GitHub
parent 088ca622a1
commit 74b36ce870
56 changed files with 643 additions and 201 deletions
@@ -23,8 +23,6 @@ public enum UntranslatedL10n {
public static var softLogoutClearDataSubmit: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_submit") }
/// Clear personal data
public static var softLogoutClearDataTitle: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_title") }
/// Forgot password
public static var softLogoutForgotPassword: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_forgot_password") }
/// Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device.
public static var softLogoutSigninE2eWarningNotice: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_signin_e2e_warning_notice") }
/// Your homeserver (%1$s) admin has signed you out of your account %2$s (%3$s).
@@ -26,6 +26,7 @@ struct A11yIdentifiers {
static let reportContent = ReportContent()
static let roomScreen = RoomScreen()
static let roomDetailsScreen = RoomDetailsScreen()
static let serverConfirmationScreen = ServerConfirmationScreen()
static let sessionVerificationScreen = SessionVerificationScreen()
static let softLogoutScreen = SoftLogoutScreen()
static let startChatScreen = StartChatScreen()
@@ -73,7 +74,6 @@ struct A11yIdentifiers {
let emailUsername = "login-email_username"
let password = "login-password"
let `continue` = "login-continue"
let changeServer = "login-change_server"
let oidc = "login-oidc"
let unsupportedServer = "login-unsupported_server"
}
@@ -106,6 +106,11 @@ struct A11yIdentifiers {
let unignore = "room_member_details-unignore"
}
struct ServerConfirmationScreen {
let `continue` = "server_confirmation-continue"
let changeServer = "server_confirmation-change_server"
}
struct SessionVerificationScreen {
let requestVerification = "session_verification-request_verification"
let startSasVerification = "session_verification-start_sas_verification"
@@ -115,7 +120,7 @@ struct A11yIdentifiers {
let verificationComplete = "session_verification-verification_complete"
let close = "session_verification-close"
}
struct SoftLogoutScreen {
let title = "soft_logout-title"
let message = "soft_logout-message"
@@ -14,6 +14,7 @@
// limitations under the License.
//
import Combine
import SwiftUI
@MainActor
@@ -26,6 +27,8 @@ class AuthenticationCoordinator: CoordinatorProtocol {
private let authenticationService: AuthenticationServiceProxyProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
private var cancellables: Set<AnyCancellable> = []
weak var delegate: AuthenticationCoordinatorDelegate?
init(authenticationService: AuthenticationServiceProxyProtocol,
@@ -64,17 +67,20 @@ class AuthenticationCoordinator: CoordinatorProtocol {
switch await authenticationService.configure(for: ServiceLocator.shared.settings.defaultHomeserverAddress) {
case .success:
stopLoading()
showLoginScreen()
showServerConfirmationScreen()
case .failure:
stopLoading()
showServerSelectionScreen()
showServerSelectionScreen(isModallyPresented: false)
}
}
private func showServerSelectionScreen() {
private func showServerSelectionScreen(isModallyPresented: Bool) {
let navigationCoordinator = NavigationStackCoordinator()
let userIndicatorController: UserIndicatorControllerProtocol! = isModallyPresented ? UserIndicatorController(rootCoordinator: navigationCoordinator) : ServiceLocator.shared.userIndicatorController
let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
userIndicatorController: userIndicatorController,
isModallyPresented: isModallyPresented)
let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
@@ -82,18 +88,46 @@ class AuthenticationCoordinator: CoordinatorProtocol {
switch action {
case .updated:
self.showLoginScreen()
if isModallyPresented {
navigationStackCoordinator.setSheetCoordinator(nil)
} else {
showLoginScreen()
}
case .dismiss:
MXLog.failure("ServerSelectionScreen is requesting dismiss when part of a stack.")
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
if isModallyPresented {
navigationCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
} else {
navigationStackCoordinator.push(coordinator)
}
}
private func showServerConfirmationScreen() {
let parameters = ServerConfirmationScreenCoordinatorParameters(authenticationService: authenticationService,
authenticationFlow: .login)
let coordinator = ServerConfirmationScreenCoordinator(parameters: parameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .confirm:
self.showLoginScreen()
case .changeServer:
self.showServerSelectionScreen(isModallyPresented: true)
}
}
.store(in: &cancellables)
navigationStackCoordinator.push(coordinator)
}
private func showLoginScreen() {
let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService,
navigationStackCoordinator: navigationStackCoordinator)
let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService)
let coordinator = LoginScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
@@ -20,8 +20,6 @@ import SwiftUI
struct LoginScreenCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProxyProtocol
/// The navigation router used to present the server selection screen.
let navigationStackCoordinator: NavigationStackCoordinator
}
enum LoginScreenCoordinatorAction {
@@ -37,7 +35,6 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
private let oidcAuthenticationPresenter: OIDCAuthenticationPresenter
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
var callback: (@MainActor (LoginScreenCoordinatorAction) -> Void)?
@@ -46,7 +43,7 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
init(parameters: LoginScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver)
viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver.value)
oidcAuthenticationPresenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService)
}
@@ -58,8 +55,6 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
guard let self else { return }
switch action {
case .selectServer:
self.presentServerSelectionScreen()
case .parseUsername(let username):
self.parseUsername(username)
case .forgotPassword:
@@ -193,35 +188,10 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
/// Updates the view model with a different homeserver.
private func updateViewModel() {
viewModel.update(homeserver: authenticationService.homeserver)
viewModel.update(homeserver: authenticationService.homeserver.value)
indicateSuccess()
}
/// Presents the server selection screen as a modal.
private func presentServerSelectionScreen() {
let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] action in
guard let self, let coordinator else { return }
self.serverSelectionCoordinator(coordinator, didCompleteWith: action)
}
navigationStackCoordinator.push(coordinator)
}
/// Handles the result from the server selection modal, dismissing it after updating the view.
private func serverSelectionCoordinator(_ coordinator: ServerSelectionScreenCoordinator,
didCompleteWith action: ServerSelectionScreenCoordinatorAction) {
if action == .updated {
updateViewModel()
}
navigationStackCoordinator.pop()
}
/// Shows the forgot password screen.
private func showForgotPasswordScreen() {
viewModel.displayError(.alert("Not implemented."))
@@ -17,8 +17,6 @@
import Foundation
enum LoginScreenViewModelAction: CustomStringConvertible {
/// The user would like to select another server.
case selectServer
/// Parse the username and update the homeserver if included.
case parseUsername(String)
/// The user would like to reset their password.
@@ -31,8 +29,6 @@ enum LoginScreenViewModelAction: CustomStringConvertible {
/// A string representation of the action, ignoring any associated values that could leak PII.
var description: String {
switch self {
case .selectServer:
return "selectServer"
case .parseUsername:
return "parseUsername"
case .forgotPassword:
@@ -77,8 +73,6 @@ struct LoginScreenBindings {
}
enum LoginScreenViewAction {
/// The user would like to select another server.
case selectServer
/// Parse the username to detect if a homeserver is included.
case parseUsername
/// The user would like to reset their password.
@@ -30,8 +30,6 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc
override func process(viewAction: LoginScreenViewAction) {
switch viewAction {
case .selectServer:
callback?(.selectServer)
case .parseUsername:
callback?(.parseUsername(state.bindings.username))
case .forgotPassword:
@@ -31,9 +31,6 @@ struct LoginScreen: View {
.padding(.top, UIConstants.titleTopPaddingToNavigationBar)
.padding(.bottom, 32)
serverInfo
.padding(.bottom, 32)
switch context.viewState.loginMode {
case .password:
loginForm
@@ -51,19 +48,18 @@ struct LoginScreen: View {
.alert(item: $context.alertInfo) { $0.alert }
}
/// The header containing a Welcome Back title.
/// The header containing the title and icon.
var header: some View {
Text(L10n.screenLoginTitle)
.font(.compound.headingLGBold)
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
}
/// The sever information section that includes a button to select a different server.
var serverInfo: some View {
LoginServerInfoSection(address: context.viewState.homeserver.address) {
context.send(viewAction: .selectServer)
VStack(spacing: 8) {
AuthenticationIconImage(image: Image(systemName: "lock.fill"))
.padding(.bottom, 8)
Text(L10n.screenLoginTitleWithHomeserver(context.viewState.homeserver.address))
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
}
.padding(.horizontal, 16)
}
/// The form with text fields for username and password, along with a submit button.
@@ -1,65 +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 SwiftUI
/// A view that shows information about the chosen homeserver,
/// along with an edit button to pick a different one.
struct LoginServerInfoSection: View {
// MARK: - Public
/// The address shown for the server.
let address: String
/// The action performed when tapping the edit button.
let editAction: () -> Void
// MARK: - Views
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(L10n.screenLoginServerHeader)
.font(.compound.bodySM)
.foregroundColor(.element.primaryContent)
.padding(.horizontal, 16)
Button(action: editAction) {
HStack {
Text(address)
.font(.compound.bodyMDSemibold)
.foregroundColor(.element.primaryContent)
.padding(.horizontal, 16)
.padding(.vertical)
Spacer()
Image(systemName: "chevron.right")
.font(.compound.bodyMD.bold())
.foregroundColor(.element.tertiaryContent)
.padding(.trailing, 16)
}
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system))
}
.accessibilityIdentifier(A11yIdentifiers.loginScreen.changeServer)
}
}
}
struct LoginServerInfoSection_Previews: PreviewProvider {
static var previews: some View {
LoginServerInfoSection(address: "matrix.org", editAction: { })
.padding()
}
}
@@ -0,0 +1,64 @@
//
// 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 Combine
import SwiftUI
struct ServerConfirmationScreenCoordinatorParameters {
let authenticationService: AuthenticationServiceProxyProtocol
let authenticationFlow: AuthenticationFlow
}
enum ServerConfirmationScreenCoordinatorAction {
case confirm
case changeServer
}
final class ServerConfirmationScreenCoordinator: CoordinatorProtocol {
private let parameters: ServerConfirmationScreenCoordinatorParameters
private var viewModel: ServerConfirmationScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<ServerConfirmationScreenCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<ServerConfirmationScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: ServerConfirmationScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = ServerConfirmationScreenViewModel(authenticationService: parameters.authenticationService,
authenticationFlow: parameters.authenticationFlow)
}
func start() {
viewModel.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .confirm:
self.actionsSubject.send(.confirm)
case .changeServer:
self.actionsSubject.send(.changeServer)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(ServerConfirmationScreen(context: viewModel.context))
}
}
@@ -0,0 +1,64 @@
//
// 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
enum ServerConfirmationScreenViewModelAction {
/// The user would like to continue with the current homeserver.
case confirm
/// The user would like to change to a different homeserver.
case changeServer
}
struct ServerConfirmationScreenViewState: BindableState {
/// The homeserver address input by the user.
var homeserverAddress: String
/// The flow being attempted on the selected homeserver.
let authenticationFlow: AuthenticationFlow
/// The screen's title.
var title: String {
switch authenticationFlow {
case .login:
return L10n.screenServerConfirmationTitleLogin(homeserverAddress)
case .register:
return L10n.screenServerConfirmationTitleRegister(homeserverAddress)
}
}
/// The message shown beneath the title.
var message: String {
switch authenticationFlow {
case .login:
if homeserverAddress == "matrix.org" {
return L10n.screenServerConfirmationMessageLoginMatrixDotOrg
} else if homeserverAddress == "element.io" {
return L10n.screenServerConfirmationMessageLoginElementDotIo
} else {
return ""
}
case .register:
return L10n.screenServerConfirmationMessageRegister
}
}
}
enum ServerConfirmationScreenViewAction {
/// The user would like to continue with the current homeserver.
case confirm
/// The user would like to change to a different homeserver.
case changeServer
}
@@ -0,0 +1,52 @@
//
// 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 Combine
import SwiftUI
typealias ServerConfirmationScreenViewModelType = StateStoreViewModel<ServerConfirmationScreenViewState, ServerConfirmationScreenViewAction>
class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType, ServerConfirmationScreenViewModelProtocol {
private var actionsSubject: PassthroughSubject<ServerConfirmationScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<ServerConfirmationScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(authenticationService: AuthenticationServiceProxyProtocol, authenticationFlow: AuthenticationFlow) {
super.init(initialViewState: ServerConfirmationScreenViewState(homeserverAddress: authenticationService.homeserver.value.address,
authenticationFlow: authenticationFlow))
authenticationService.homeserver
.receive(on: DispatchQueue.main)
.sink { [weak self] homeserver in
guard let self else { return }
state.homeserverAddress = homeserver.address
}
.store(in: &cancellables)
}
// MARK: - Public
override func process(viewAction: ServerConfirmationScreenViewAction) {
switch viewAction {
case .confirm:
actionsSubject.send(.confirm)
case .changeServer:
actionsSubject.send(.changeServer)
}
}
}
@@ -0,0 +1,23 @@
//
// 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 Combine
@MainActor
protocol ServerConfirmationScreenViewModelProtocol {
var actions: AnyPublisher<ServerConfirmationScreenViewModelAction, Never> { get }
var context: ServerConfirmationScreenViewModelType.Context { get }
}
@@ -0,0 +1,99 @@
//
// 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 SwiftUI
struct ServerConfirmationScreen: View {
@ObservedObject var context: ServerConfirmationScreenViewModel.Context
var body: some View {
ScrollView {
header
.padding(.top, UIConstants.iconTopPaddingToNavigationBar)
.padding(.horizontal, 16)
.readableFrame()
}
.background(Color.element.background.ignoresSafeArea())
.safeAreaInset(edge: .bottom) {
buttons
.padding(.horizontal, 16)
.padding(.vertical, 16)
.readableFrame()
.background(Color.element.background.ignoresSafeArea())
}
}
/// The main content of the view to be shown in a scroll view.
var header: some View {
VStack(spacing: 8) {
AuthenticationIconImage(image: Image(systemName: "person.crop.circle.fill"))
.padding(.bottom, 8)
Text(context.viewState.title)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
.fixedSize(horizontal: false, vertical: true)
Text(context.viewState.message)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.element.tertiaryContent)
}
.padding(.horizontal, 16)
}
/// The action buttons shown at the bottom of the view.
var buttons: some View {
VStack(spacing: 24) {
Button { context.send(viewAction: .confirm) } label: {
Text(L10n.actionContinue)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.continue)
Button { context.send(viewAction: .changeServer) } label: {
Text(L10n.screenServerConfirmationChangeServer)
.font(.compound.bodyLGSemibold)
.padding(.vertical, 14)
}
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.changeServer)
}
}
}
// MARK: - Previews
struct ServerConfirmationScreen_Previews: PreviewProvider {
static let loginViewModel = ServerConfirmationScreenViewModel(authenticationService: MockAuthenticationServiceProxy(),
authenticationFlow: .login)
static let registerViewModel = ServerConfirmationScreenViewModel(authenticationService: MockAuthenticationServiceProxy(),
authenticationFlow: .register)
static var previews: some View {
NavigationStack {
ServerConfirmationScreen(context: loginViewModel.context)
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Login")
NavigationStack {
ServerConfirmationScreen(context: registerViewModel.context)
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Register")
}
}
@@ -39,7 +39,7 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
init(parameters: ServerSelectionScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.address,
viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.value.address,
isModallyPresented: parameters.isModallyPresented)
userIndicatorController = parameters.userIndicatorController
}
@@ -53,7 +53,7 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
let homeserver = parameters.authenticationService.homeserver
viewModel = SoftLogoutScreenViewModel(credentials: parameters.credentials,
homeserver: homeserver,
homeserver: homeserver.value,
keyBackupNeeded: parameters.keyBackupNeeded)
oidcAuthenticationPresenter = OIDCAuthenticationPresenter(authenticationService: parameters.authenticationService)
@@ -87,7 +87,7 @@ struct SoftLogoutScreen: View {
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.password)
Button { context.send(viewAction: .forgotPassword) } label: {
Text(UntranslatedL10n.softLogoutForgotPassword)
Text(L10n.actionForgotPassword)
.font(.compound.bodyLG)
}
.frame(maxWidth: .infinity, alignment: .trailing)
@@ -14,6 +14,7 @@
// limitations under the License.
//
import Combine
import Foundation
import MatrixRustSDK
@@ -21,7 +22,9 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
private let authenticationService: AuthenticationService
private let userSessionStore: UserSessionStoreProtocol
private(set) var homeserver = LoginHomeserver(address: ServiceLocator.shared.settings.defaultHomeserverAddress, loginMode: .unknown)
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never> = .init(LoginHomeserver(address: ServiceLocator.shared.settings.defaultHomeserverAddress,
loginMode: .unknown))
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
init(userSessionStore: UserSessionStoreProtocol) {
self.userSessionStore = userSessionStore
@@ -60,7 +63,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
}
}
self.homeserver = homeserver
homeserverSubject.send(homeserver)
return .success(())
} catch AuthenticationError.SlidingSyncNotAvailable {
MXLog.info("User entered a homeserver that isn't configured for sliding sync.")
@@ -17,6 +17,14 @@
import Foundation
import MatrixRustSDK
/// Represents a particular authentication flow.
enum AuthenticationFlow {
/// The flow for signing in to an existing account.
case login
/// The flow for creating a new account.
case register
}
enum AuthenticationServiceError: Error {
/// An error occurred during OIDC authentication.
case oidcError(OIDCError)
@@ -29,7 +37,8 @@ enum AuthenticationServiceError: Error {
}
protocol AuthenticationServiceProxyProtocol {
var homeserver: LoginHomeserver { get }
/// The currently configured homeserver.
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
/// Sets up the service for login on the specified homeserver address.
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError>
@@ -14,27 +14,33 @@
// limitations under the License.
//
import Combine
import Foundation
import MatrixRustSDK
class MockAuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
let validCredentials = (username: "alice", password: "12345678")
private(set) var homeserver: LoginHomeserver = .mockMatrixDotOrg
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
init(homeserver: LoginHomeserver = .mockMatrixDotOrg) {
homeserverSubject = .init(homeserver)
}
func configure(for homeserverAddress: String) async -> Result<Void, AuthenticationServiceError> {
// Map the address to the mock homeservers
if LoginHomeserver.mockMatrixDotOrg.address.contains(homeserverAddress) {
homeserver = .mockMatrixDotOrg
homeserverSubject.send(.mockMatrixDotOrg)
return .success(())
} else if LoginHomeserver.mockOIDC.address.contains(homeserverAddress) {
homeserver = .mockOIDC
homeserverSubject.send(.mockOIDC)
return .success(())
} else if LoginHomeserver.mockBasicServer.address.contains(homeserverAddress) {
homeserver = .mockBasicServer
homeserverSubject.send(.mockBasicServer)
return .success(())
} else if LoginHomeserver.mockUnsupported.address.contains(homeserverAddress) {
homeserver = .mockUnsupported
homeserverSubject.send(.mockUnsupported)
return .success(())
} else {
// Otherwise fail with an invalid server.
@@ -75,8 +75,19 @@ class MockScreen: Identifiable {
switch id {
case .login:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator))
let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy()))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverConfirmationLogin:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ServerConfirmationScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(homeserver: .mockMatrixDotOrg),
authenticationFlow: .login))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverConfirmationRegister:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ServerConfirmationScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(homeserver: .mockOIDC),
authenticationFlow: .register))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverSelection:
@@ -18,6 +18,8 @@ import Foundation
enum UITestsScreenIdentifier: String {
case login
case serverConfirmationLogin
case serverConfirmationRegister
case serverSelection
case serverSelectionNonModal
case authenticationFlow