// // Copyright 2022-2024 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. // import Combine import SwiftUI typealias AuthenticationStartScreenViewModelType = StateStoreViewModelV2 class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType, AuthenticationStartScreenViewModelProtocol { private let authenticationService: AuthenticationServiceProtocol private let provisioningParameters: AccountProvisioningParameters? private let appSettings: AppSettings private let userIndicatorController: UserIndicatorControllerProtocol private var actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } init(authenticationService: AuthenticationServiceProtocol, provisioningParameters: AccountProvisioningParameters?, isBugReportServiceEnabled: Bool, appSettings: AppSettings, userIndicatorController: UserIndicatorControllerProtocol) { self.authenticationService = authenticationService self.provisioningParameters = provisioningParameters self.appSettings = appSettings self.userIndicatorController = userIndicatorController // We only show the "Sign in to …" button when using a provisioning link. let showCreateAccountButton = appSettings.showCreateAccountButton && provisioningParameters == nil let showQRCodeLoginButton = !ProcessInfo.processInfo.isiOSAppOnMac && provisioningParameters == nil super.init(initialViewState: AuthenticationStartScreenViewState(serverName: provisioningParameters?.accountProvider, showCreateAccountButton: showCreateAccountButton, showQRCodeLoginButton: showQRCodeLoginButton, showReportProblemButton: isBugReportServiceEnabled)) } override func process(viewAction: AuthenticationStartScreenViewAction) { switch viewAction { case .updateWindow(let window): guard state.window != window else { return } state.window = window case .loginWithQR: actionsSubject.send(.loginWithQR) case .login: Task { await login() } case .register: actionsSubject.send(.register) case .reportProblem: actionsSubject.send(.reportProblem) } } // MARK: - Private private func login() async { if let provisioningParameters { await configureProvisionedServer(with: provisioningParameters) } else { actionsSubject.send(.login) // No need to configure anything here, continue the flow. } } private func configureProvisionedServer(with provisioningParameters: AccountProvisioningParameters) async { startLoading() defer { stopLoading() } guard case .success = await authenticationService.configure(for: provisioningParameters.accountProvider, flow: .login) else { // As the server was provisioned, we don't worry about the specifics and show a generic error to the user. displayError() return } guard authenticationService.homeserver.value.loginMode.supportsOIDCFlow else { actionsSubject.send(.loginDirectlyWithPassword(loginHint: provisioningParameters.loginHint)) return } guard let window = state.window else { displayError() return } switch await authenticationService.urlForOIDCLogin(loginHint: provisioningParameters.loginHint) { case .success(let oidcData): actionsSubject.send(.loginDirectlyWithOIDC(data: oidcData, window: window)) case .failure: displayError() } } private let loadingIndicatorID = "\(AuthenticationStartScreenViewModel.self)-Loading" private func startLoading() { userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true)) } private func stopLoading() { userIndicatorController.retractIndicatorWithId(loadingIndicatorID) } private func displayError() { state.bindings.alertInfo = AlertInfo(id: .genericError) } }