Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to simplify this as discussed in #4131 (comment)

Ah crap, that wasn't what the comment suggested 🙈

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -127,7 +125,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
}

func start() {
stateMachine.tryEvent(.start(allowOtherAccountProviders: appSettings.allowOtherAccountProviders))
stateMachine.tryEvent(.start)
}

func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -341,6 +338,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {

let parameters = ServerConfirmationScreenCoordinatorParameters(authenticationService: authenticationService,
authenticationFlow: authenticationFlow,
appSettings: appSettings,
userIndicatorController: userIndicatorController)
let coordinator = ServerConfirmationScreenCoordinator(parameters: parameters)

Expand Down Expand Up @@ -437,15 +435,15 @@ 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,
userSession: nil))
coordinator.actionsPublisher.sink { [weak self] action in
switch action {
case .complete:
self?.stateMachine.tryEvent(.bugReportFlowComplete(previousState: fromState))
self?.stateMachine.tryEvent(.bugReportFlowComplete)
}
}
.store(in: &cancellables)
Expand Down
2 changes: 2 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions ElementX/Sources/Other/AccessibilityIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ enum A11yIdentifiers {
struct ServerConfirmationScreen {
let `continue` = "server_confirmation-continue"
let changeServer = "server_confirmation-change_server"
let serverPicker = "server_confirmation-server_picker"
}

struct SessionVerificationScreen {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SwiftUI
struct ServerConfirmationScreenCoordinatorParameters {
let authenticationService: AuthenticationServiceProtocol
let authenticationFlow: AuthenticationFlow
let appSettings: AppSettings
let userIndicatorController: UserIndicatorControllerProtocol
}

Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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>?
}
Expand Down
Loading
Loading