Show an account provider picker on the server confirmation screen when required. (#4137)

* Add a picker mode to the ServerConfirmationScreen.

* Hook up the account provider picker in authentication the flow.

Simplify the authentication flow coordinator, removing the restricted state.

* UI/Snapshot tests.
This commit is contained in:
Doug
2025-05-20 17:21:08 +01:00
committed by GitHub
parent 18519c15dd
commit 5197e41928
31 changed files with 436 additions and 109 deletions
@@ -993,6 +993,7 @@
"screen_server_confirmation_message_login_matrix_dot_org" = "Matrix is an open network for secure, decentralised communication.";
"screen_server_confirmation_message_register" = "This is where your conversations will live — just like you would use an email provider to keep your emails.";
"screen_server_confirmation_title_login" = "You’re about to sign in to %1$@";
"screen_server_confirmation_title_picker_mode" = "Choose account provider";
"screen_server_confirmation_title_register" = "You’re about to create an account on %1$@";
"screen_session_verification_cancelled_subtitle" = "Something doesn’t seem right. Either the request timed out or the request was denied.";
"screen_session_verification_compare_emojis_subtitle" = "Confirm that the emojis below match those shown on your other session.";
@@ -31,8 +31,6 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
/// The initial screen shown when you first launch the app.
case startScreen
/// The initial screen with the selection of account provider having been restricted.
case restrictedStartScreen
/// The screen used for the whole QR Code flow.
case qrCodeLoginScreen
@@ -55,7 +53,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
enum Event: EventType {
/// The flow is being started.
case start(allowOtherAccountProviders: Bool)
case start
/// Modify the flow using the provisioning parameters in the `userInfo`.
case applyProvisioningParameters
@@ -68,7 +66,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
case reportProblem
/// The QR login flow was aborted.
case cancelledLoginWithQR(previousState: State)
case cancelledLoginWithQR
/// The user aborted manual login.
case cancelledServerConfirmation
@@ -87,7 +85,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
case cancelledPasswordLogin(previousState: State)
/// The user has finished reporting a problem (or viewing the logs).
case bugReportFlowComplete(previousState: State)
case bugReportFlowComplete
/// The user has successfully signed in. The new session can be found in the `userInfo`.
case signedIn
@@ -127,7 +125,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
}
func start() {
stateMachine.tryEvent(.start(allowOtherAccountProviders: appSettings.allowOtherAccountProviders))
stateMachine.tryEvent(.start)
}
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
@@ -150,11 +148,11 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
func clearRoute(animated: Bool) {
switch stateMachine.state {
case .initial, .startScreen, .restrictedStartScreen:
case .initial, .startScreen:
break
case .qrCodeLoginScreen:
navigationStackCoordinator.setSheetCoordinator(nil)
stateMachine.tryEvent(.cancelledLoginWithQR(previousState: .initial)) // Needs to be handled manually.
stateMachine.tryEvent(.cancelledLoginWithQR) // Needs to be handled manually.
case .serverConfirmationScreen:
navigationStackCoordinator.popToRoot(animated: animated)
case .serverSelectionScreen:
@@ -175,27 +173,22 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - Setup
private func configureStateMachine() {
stateMachine.addRoutes(event: .start(allowOtherAccountProviders: true), transitions: [.initial => .startScreen]) { [weak self] _ in
self?.showStartScreen(fromState: .initial)
}
stateMachine.addRoutes(event: .start(allowOtherAccountProviders: false), transitions: [.initial => .restrictedStartScreen]) { [weak self] _ in
stateMachine.addRoutes(event: .start, transitions: [.initial => .startScreen]) { [weak self] _ in
self?.showStartScreen(fromState: .initial)
}
stateMachine.addRoutes(event: .applyProvisioningParameters, transitions: [.initial => .restrictedStartScreen,
.startScreen => .restrictedStartScreen]) { [weak self] context in
stateMachine.addRoutes(event: .applyProvisioningParameters, transitions: [.initial => .startScreen,
.startScreen => .startScreen]) { [weak self] context in
guard let provisioningParameters = context.userInfo as? AccountProvisioningParameters else { fatalError("The authentication configuration is missing.") }
self?.showStartScreen(fromState: context.fromState, applying: provisioningParameters)
}
// QR Code
stateMachine.addRoutes(event: .loginWithQR, transitions: [.startScreen => .qrCodeLoginScreen,
.restrictedStartScreen => .qrCodeLoginScreen]) { [weak self] context in
self?.showQRCodeLoginScreen(fromState: context.fromState)
stateMachine.addRoutes(event: .loginWithQR, transitions: [.startScreen => .qrCodeLoginScreen]) { [weak self] _ in
self?.showQRCodeLoginScreen()
}
stateMachine.addRoutes(event: .cancelledLoginWithQR(previousState: .startScreen), transitions: [.qrCodeLoginScreen => .startScreen])
stateMachine.addRoutes(event: .cancelledLoginWithQR(previousState: .restrictedStartScreen), transitions: [.qrCodeLoginScreen => .restrictedStartScreen])
stateMachine.addRoutes(event: .cancelledLoginWithQR, transitions: [.qrCodeLoginScreen => .startScreen])
// Manual Authentication
@@ -216,31 +209,29 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.addRoutes(event: .dismissedServerSelection, transitions: [.serverSelectionScreen => .serverConfirmationScreen])
stateMachine.addRoutes(event: .continueWithOIDC, transitions: [.serverConfirmationScreen => .oidcAuthentication,
.restrictedStartScreen => .oidcAuthentication]) { [weak self] context in
.startScreen => .oidcAuthentication]) { [weak self] context in
guard let (oidcData, window) = context.userInfo as? (OIDCAuthorizationDataProxy, UIWindow) else {
fatalError("Missing the OIDC data and presentation anchor.")
}
self?.showOIDCAuthentication(oidcData: oidcData, presentationAnchor: window, fromState: context.fromState)
}
stateMachine.addRoutes(event: .cancelledOIDCAuthentication(previousState: .serverConfirmationScreen), transitions: [.oidcAuthentication => .serverConfirmationScreen])
stateMachine.addRoutes(event: .cancelledOIDCAuthentication(previousState: .restrictedStartScreen), transitions: [.oidcAuthentication => .restrictedStartScreen])
stateMachine.addRoutes(event: .cancelledOIDCAuthentication(previousState: .startScreen), transitions: [.oidcAuthentication => .startScreen])
stateMachine.addRoutes(event: .continueWithPassword, transitions: [.serverConfirmationScreen => .loginScreen,
.restrictedStartScreen => .loginScreen]) { [weak self] context in
.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: .restrictedStartScreen), transitions: [.loginScreen => .restrictedStartScreen])
stateMachine.addRoutes(event: .cancelledPasswordLogin(previousState: .startScreen), transitions: [.loginScreen => .startScreen])
// Bug Report
stateMachine.addRoutes(event: .reportProblem, transitions: [.startScreen => .bugReportFlow,
.restrictedStartScreen => .bugReportFlow]) { [weak self] context in
self?.startBugReportFlow(fromState: context.fromState)
stateMachine.addRoutes(event: .reportProblem, transitions: [.startScreen => .bugReportFlow]) { [weak self] _ in
self?.startBugReportFlow()
}
stateMachine.addRoutes(event: .bugReportFlowComplete(previousState: .startScreen), transitions: [.bugReportFlow => .startScreen])
stateMachine.addRoutes(event: .bugReportFlowComplete(previousState: .restrictedStartScreen), transitions: [.bugReportFlow => .restrictedStartScreen])
stateMachine.addRoutes(event: .bugReportFlowComplete, transitions: [.bugReportFlow => .startScreen])
// Completion
@@ -251,6 +242,12 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
self?.userHasSignedIn(userSession: userSession)
}
// Logging
stateMachine.addAnyHandler(.any => .any) { context in
MXLog.dev("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(String(describing: context.event))`.")
}
// Unhandled
stateMachine.addErrorHandler { context in
@@ -302,9 +299,9 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - QR Code
private func showQRCodeLoginScreen(fromState: State) {
private func showQRCodeLoginScreen() {
let coordinator = QRCodeLoginScreenCoordinator(parameters: .init(qrCodeLoginService: qrCodeLoginService,
canSignInManually: fromState != .restrictedStartScreen,
canSignInManually: appSettings.allowOtherAccountProviders, // No need to worry about provisioning links as we hide QR login.
orientationManager: appMediator.windowManager,
appMediator: appMediator))
coordinator.actionsPublisher.sink { [weak self] action in
@@ -314,11 +311,11 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
switch action {
case .signInManually:
navigationStackCoordinator.setSheetCoordinator(nil)
stateMachine.tryEvent(.cancelledLoginWithQR(previousState: fromState))
stateMachine.tryEvent(.cancelledLoginWithQR)
stateMachine.tryEvent(.confirmServer(.login))
case .cancel:
navigationStackCoordinator.setSheetCoordinator(nil)
stateMachine.tryEvent(.cancelledLoginWithQR(previousState: fromState))
stateMachine.tryEvent(.cancelledLoginWithQR)
case .done(let userSession):
navigationStackCoordinator.setSheetCoordinator(nil)
// Since the qr code login flow includes verification
@@ -341,6 +338,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
let parameters = ServerConfirmationScreenCoordinatorParameters(authenticationService: authenticationService,
authenticationFlow: authenticationFlow,
appSettings: appSettings,
userIndicatorController: userIndicatorController)
let coordinator = ServerConfirmationScreenCoordinator(parameters: parameters)
@@ -437,7 +435,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
// MARK: - Bug Report
private func startBugReportFlow(fromState: State) {
private func startBugReportFlow() {
let coordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator),
userIndicatorController: userIndicatorController,
bugReportService: bugReportService,
@@ -445,7 +443,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
coordinator.actionsPublisher.sink { [weak self] action in
switch action {
case .complete:
self?.stateMachine.tryEvent(.bugReportFlowComplete(previousState: fromState))
self?.stateMachine.tryEvent(.bugReportFlowComplete)
}
}
.store(in: &cancellables)
+2
View File
@@ -2458,6 +2458,8 @@ internal enum L10n {
internal static func screenServerConfirmationTitleLogin(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_server_confirmation_title_login", String(describing: p1))
}
/// Choose account provider
internal static var screenServerConfirmationTitlePickerMode: String { return L10n.tr("Localizable", "screen_server_confirmation_title_picker_mode") }
/// You’re about to create an account on %1$@
internal static func screenServerConfirmationTitleRegister(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_server_confirmation_title_register", String(describing: p1))
@@ -1,4 +1,4 @@
// Generated using Sourcery 2.2.6 — https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.2.7 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable all
@@ -215,6 +215,7 @@ enum A11yIdentifiers {
struct ServerConfirmationScreen {
let `continue` = "server_confirmation-continue"
let changeServer = "server_confirmation-change_server"
let serverPicker = "server_confirmation-server_picker"
}
struct SessionVerificationScreen {
@@ -11,6 +11,7 @@ import SwiftUI
struct ServerConfirmationScreenCoordinatorParameters {
let authenticationService: AuthenticationServiceProtocol
let authenticationFlow: AuthenticationFlow
let appSettings: AppSettings
let userIndicatorController: UserIndicatorControllerProtocol
}
@@ -30,7 +31,14 @@ final class ServerConfirmationScreenCoordinator: CoordinatorProtocol {
}
init(parameters: ServerConfirmationScreenCoordinatorParameters) {
let mode = if parameters.appSettings.allowOtherAccountProviders {
ServerConfirmationScreenMode.confirmation(parameters.authenticationService.homeserver.value.address)
} else {
ServerConfirmationScreenMode.picker(parameters.appSettings.accountProviders)
}
viewModel = ServerConfirmationScreenViewModel(authenticationService: parameters.authenticationService,
mode: mode,
authenticationFlow: parameters.authenticationFlow,
userIndicatorController: parameters.userIndicatorController)
}
@@ -16,9 +16,16 @@ enum ServerConfirmationScreenViewModelAction {
case changeServer
}
enum ServerConfirmationScreenMode: Equatable {
/// The user is confirming the displayed account provider (or can enter their own).
case confirmation(String)
/// The user is only allowed to pick from a list of account providers.
case picker([String])
}
struct ServerConfirmationScreenViewState: BindableState {
/// The homeserver address input by the user.
var homeserverAddress: String
/// Whether the screen is configured to confirm a single account provider or pick one from a list of many.
var mode: ServerConfirmationScreenMode
/// The flow being attempted on the selected homeserver.
let authenticationFlow: AuthenticationFlow
/// The presentation anchor used for OIDC authentication.
@@ -28,17 +35,24 @@ struct ServerConfirmationScreenViewState: BindableState {
/// The screen's title.
var title: String {
switch authenticationFlow {
case .login:
return L10n.screenServerConfirmationTitleLogin(homeserverAddress)
case .register:
return L10n.screenServerConfirmationTitleRegister(homeserverAddress)
switch mode {
case .confirmation(let accountProvider):
switch authenticationFlow {
case .login:
L10n.screenServerConfirmationTitleLogin(accountProvider)
case .register:
L10n.screenServerConfirmationTitleRegister(accountProvider)
}
case .picker:
L10n.screenServerConfirmationTitlePickerMode
}
}
/// The message shown beneath the title.
var message: String {
switch authenticationFlow {
var message: String? {
guard case let .confirmation(homeserverAddress) = mode else { return nil }
return switch authenticationFlow {
case .login:
if homeserverAddress == "matrix.org" {
L10n.screenServerConfirmationMessageLoginMatrixDotOrg
@@ -54,6 +68,8 @@ struct ServerConfirmationScreenViewState: BindableState {
}
struct ServerConfirmationScreenBindings {
/// The chosen server when in `.picker` mode, otherwise `nil`.
var pickerSelection: String?
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<ServerConfirmationScreenAlert>?
}
@@ -22,21 +22,29 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
}
init(authenticationService: AuthenticationServiceProtocol,
mode: ServerConfirmationScreenMode,
authenticationFlow: AuthenticationFlow,
userIndicatorController: UserIndicatorControllerProtocol) {
self.authenticationService = authenticationService
self.authenticationFlow = authenticationFlow
self.userIndicatorController = userIndicatorController
let homeserver = authenticationService.homeserver.value
super.init(initialViewState: ServerConfirmationScreenViewState(homeserverAddress: homeserver.address,
authenticationFlow: authenticationFlow))
let pickerSelection: String? = switch mode {
case .picker(let providers): providers[0]
case .confirmation: nil
}
authenticationService.homeserver
.receive(on: DispatchQueue.main)
.map(\.address)
.weakAssign(to: \.state.homeserverAddress, on: self)
.store(in: &cancellables)
super.init(initialViewState: ServerConfirmationScreenViewState(mode: mode,
authenticationFlow: authenticationFlow,
bindings: .init(pickerSelection: pickerSelection)))
if case .confirmation = mode {
authenticationService.homeserver
.receive(on: DispatchQueue.main)
.map { .confirmation($0.address) }
.weakAssign(to: \.state.mode, on: self)
.store(in: &cancellables)
}
}
override func process(viewAction: ServerConfirmationScreenViewAction) {
@@ -45,7 +53,10 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
guard state.window != window else { return }
Task { state.window = window }
case .confirm:
Task { await confirmHomeserver() }
switch state.mode {
case .confirmation: Task { await confirmServer() }
case .picker: Task { await pickServer() }
}
case .changeServer:
actionsSubject.send(.changeServer)
}
@@ -53,16 +64,18 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
// MARK: - Private
private func confirmHomeserver() async {
private func confirmServer() 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.
// If the login mode is unknown, the service hasn't been 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 {
await fetchLoginURLIfNeededAndContinue()
return
}
// Note: We don't show the spinner until now as it isn't needed if the service is already
// configured and we're about to use password based login
startLoading()
defer { stopLoading() }
@@ -87,6 +100,32 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
}
}
private func pickServer() async {
guard let accountProvider = state.bindings.pickerSelection else {
fatalError("It shouldn't be possible to confirm without a selection.")
}
// Don't bother reconfiguring the service if it has already been done for the selected server.
let homeserver = authenticationService.homeserver.value
guard homeserver.loginMode == .unknown || homeserver.address != accountProvider else {
await fetchLoginURLIfNeededAndContinue()
return
}
// Note: We don't show the spinner until now as it isn't needed if the service is already
// configured and we're about to use password based login
startLoading()
defer { stopLoading() }
switch await authenticationService.configure(for: accountProvider, flow: authenticationFlow) {
case .success:
await fetchLoginURLIfNeededAndContinue()
case .failure:
// When the servers are hard-coded they should have a valid configuration, so show a generic error.
displayError(.unknownError)
}
}
private func fetchLoginURLIfNeededAndContinue() async {
guard authenticationService.homeserver.value.loginMode.supportsOIDCFlow else {
actionsSubject.send(.continueWithPassword)
@@ -11,14 +11,38 @@ import SwiftUI
struct ServerConfirmationScreen: View {
@Bindable var context: ServerConfirmationScreenViewModel.Context
private var backgroundColor: Color {
switch context.viewState.mode {
case .confirmation: .compound.bgCanvasDefault
case .picker: .compound.bgSubtleSecondaryLevel0
}
}
private var headerIcon: KeyPath<CompoundIcons, Image> {
switch context.viewState.mode {
case .confirmation: \.userProfileSolid
case .picker: \.homeSolid
}
}
private var headerIconStyle: BigIcon.Style {
switch context.viewState.mode {
case .confirmation: .defaultSolid
case .picker: .default
}
}
var body: some View {
FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar) {
header
VStack(spacing: 36) {
header
mainContent
}
} bottomContent: {
buttons
}
.background()
.backgroundStyle(.compound.bgCanvasDefault)
.backgroundStyle(backgroundColor)
.alert(item: $context.alertInfo)
.introspect(.window, on: .supportedVersions) { window in
context.send(viewAction: .updateWindow(window))
@@ -28,8 +52,7 @@ struct ServerConfirmationScreen: View {
/// The main content of the view to be shown in a scroll view.
var header: some View {
VStack(spacing: 8) {
Image(systemSymbol: .personCropCircleFill)
.bigIcon()
BigIcon(icon: headerIcon, style: headerIconStyle)
.padding(.bottom, 8)
Text(context.viewState.title)
@@ -38,14 +61,26 @@ struct ServerConfirmationScreen: View {
.foregroundColor(.compound.textPrimary)
.fixedSize(horizontal: false, vertical: true)
Text(context.viewState.message)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
if let message = context.viewState.message {
Text(message)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
}
.padding(.horizontal, 16)
}
@ViewBuilder
var mainContent: some View {
if case .picker(let accountProviders) = context.viewState.mode {
FakeInlinePicker(items: accountProviders,
icon: \.host,
selection: $context.pickerSelection)
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.serverPicker)
}
}
/// The action buttons shown at the bottom of the view.
var buttons: some View {
VStack(spacing: 16) {
@@ -55,21 +90,53 @@ struct ServerConfirmationScreen: View {
.buttonStyle(.compound(.primary))
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.continue)
Button { context.send(viewAction: .changeServer) } label: {
Text(L10n.screenServerConfirmationChangeServer)
.font(.compound.bodyLGSemibold)
.padding(14)
if case .confirmation = context.viewState.mode {
Button { context.send(viewAction: .changeServer) } label: {
Text(L10n.screenServerConfirmationChangeServer)
.font(.compound.bodyLGSemibold)
.padding(14)
}
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.changeServer)
}
.accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.changeServer)
}
}
}
// This is such a hack. I hate it!
// But… We're not in a List/Form, the compound picker doesn't
// support icons and this screen's design might change so 🤷‍♂️.
private struct FakeInlinePicker: View {
let items: [String]
let icon: KeyPath<CompoundIcons, Image>
@Binding var selection: String?
var body: some View {
VStack(spacing: 0) {
ForEach(items, id: \.self) { item in
ListRow(label: .default(title: item, icon: icon),
kind: .selection(isSelected: selection == item) {
selection = item
})
.overlay(alignment: .bottom) {
if item != items.last {
Divider()
.hidden()
.overlay(Color.compound._borderInteractiveSecondaryAlpha)
.padding(.leading, 54)
}
}
}
}
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
// MARK: - Previews
struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview {
static let loginViewModel = makeViewModel(flow: .login)
static let registerViewModel = makeViewModel(flow: .register)
static let loginViewModel = makeViewModel(mode: .confirmation("matrix.org"), flow: .login)
static let registerViewModel = makeViewModel(mode: .confirmation("matrix.org"), flow: .register)
static let pickerViewModel = makeViewModel(mode: .picker(["dept1.company.com", "dept2.company.com", "dept3.company.com"]), flow: .login)
static var previews: some View {
NavigationStack {
@@ -83,10 +150,17 @@ struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview {
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Register")
NavigationStack {
ServerConfirmationScreen(context: pickerViewModel.context)
.toolbar(.visible, for: .navigationBar)
}
.previewDisplayName("Picker")
}
static func makeViewModel(flow: AuthenticationFlow) -> ServerConfirmationScreenViewModel {
static func makeViewModel(mode: ServerConfirmationScreenMode, flow: AuthenticationFlow) -> ServerConfirmationScreenViewModel {
ServerConfirmationScreenViewModel(authenticationService: AuthenticationService.mock,
mode: mode,
authenticationFlow: flow,
userIndicatorController: UserIndicatorControllerMock())
}
@@ -77,14 +77,8 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
// MARK: - Private
private func login() async {
if !appSettings.allowOtherAccountProviders {
if appSettings.accountProviders.count == 1 {
await configureAccountProvider(appSettings.accountProviders[0])
} else {
fatalError("WIP: Account provider picker not implemented.")
}
} else if let provisioningParameters {
await configureAccountProvider(provisioningParameters.accountProvider, loginHint: provisioningParameters.loginHint)
if let serverName = state.serverName {
await configureAccountProvider(serverName, loginHint: provisioningParameters?.loginHint)
} else {
actionsSubject.send(.login) // No need to configure anything here, continue the flow.
}
@@ -120,11 +120,12 @@ class MockScreen: Identifiable {
userIndicatorController: ServiceLocator.shared.userIndicatorController))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .authenticationFlow, .provisionedAuthenticationFlow, .singleProviderAuthenticationFlow:
case .authenticationFlow, .provisionedAuthenticationFlow, .singleProviderAuthenticationFlow, .multipleProvidersAuthenticationFlow:
let appSettings: AppSettings! = ServiceLocator.shared.settings
if id == .singleProviderAuthenticationFlow {
appSettings.override(accountProviders: ["example.com"],
if id == .singleProviderAuthenticationFlow || id == .multipleProvidersAuthenticationFlow {
let accountProviders = id == .singleProviderAuthenticationFlow ? ["example.com"] : ["guest.example.com", "example.com"]
appSettings.override(accountProviders: accountProviders,
allowOtherAccountProviders: false,
pushGatewayBaseURL: appSettings.pushGatewayBaseURL,
oidcRedirectURL: appSettings.oidcRedirectURL,
@@ -18,6 +18,7 @@ enum UITestsScreenIdentifier: String {
case authenticationFlow
case provisionedAuthenticationFlow
case singleProviderAuthenticationFlow
case multipleProvidersAuthenticationFlow
case bugReport
case createPoll
case createRoom