From 6dc66ec777e0a68b33b00a6b2bec0f54f5ae6a73 Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:16:11 +0000 Subject: [PATCH] Remove the global UserIndicatorController.alertInfo, replacing it with local alertInfo usage. (#5087) --- .../Sources/Application/AppCoordinator.swift | 12 ++--- .../NavigationRootCoordinator.swift | 4 ++ .../UserSessionFlowCoordinator.swift | 46 +++++++++---------- .../Mocks/Generated/GeneratedMocks.swift | 1 - .../UserIndicatorController.swift | 2 - .../UserIndicatorControllerProtocol.swift | 1 - .../UserIndicatorPresenter.swift | 1 - .../OIDCAuthenticationPresenter.swift | 23 ++++++++-- .../CreateRoomScreenModels.swift | 1 + .../CreateRoomScreenViewModel.swift | 4 +- .../InviteUsersScreenViewModel.swift | 6 +-- .../RoomDetailsEditScreenModels.swift | 3 ++ .../RoomDetailsEditScreenViewModel.swift | 10 ++-- .../Screens/RoomScreen/RoomScreenModels.swift | 5 ++ .../RoomScreen/RoomScreenViewModel.swift | 4 +- .../Screens/RoomScreen/View/RoomScreen.swift | 1 + .../UserDetailsEditScreenModels.swift | 3 ++ .../UserDetailsEditScreenViewModel.swift | 10 ++-- .../Screens/Timeline/TimelineModels.swift | 3 ++ .../Screens/Timeline/TimelineViewModel.swift | 30 ++++++------ .../RoomDetailsEditScreenViewModelTests.swift | 2 +- 21 files changed, 101 insertions(+), 71 deletions(-) diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 91a5c097d..0a4a8be66 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -226,12 +226,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg guard let confirmationParameters = url.confirmationParameters else { return false } - ServiceLocator.shared.userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.dialogConfirmLinkTitle, - message: L10n.dialogConfirmLinkMessage(confirmationParameters.displayString, - confirmationParameters.internalURL.absoluteString), - primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil), - secondaryButton: .init(title: L10n.actionContinue) { openURLAction(confirmationParameters.internalURL) }) + navigationRootCoordinator.alertInfo = .init(id: .init(), + title: L10n.dialogConfirmLinkTitle, + message: L10n.dialogConfirmLinkMessage(confirmationParameters.displayString, + confirmationParameters.internalURL.absoluteString), + primaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil), + secondaryButton: .init(title: L10n.actionContinue) { openURLAction(confirmationParameters.internalURL) }) return true } diff --git a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift index a9d318fd8..8456ec3b3 100644 --- a/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationRootCoordinator.swift @@ -67,6 +67,9 @@ import SwiftUI overlayModule?.coordinator } + /// The lowest-level `AlertInfo`, directly available to the root of the app. + var alertInfo: AlertInfo? + /// Sets or replaces the presented coordinator /// - Parameter coordinator: the coordinator to display func setRootCoordinator(_ coordinator: (any CoordinatorProtocol)?, animated: Bool = true, dismissalCallback: (() -> Void)? = nil) { @@ -159,6 +162,7 @@ private struct NavigationRootCoordinatorView: View { ZStack { rootCoordinator.rootModule?.coordinator?.toPresentable() } + .alert(item: $rootCoordinator.alertInfo) .animation(.elementDefault, value: rootCoordinator.rootModule) .sheet(item: $rootCoordinator.sheetModule) { module in module.coordinator?.toPresentable() diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 67a435d3e..6195a3226 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -485,7 +485,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { let secureBackupController = userSession.clientProxy.secureBackupController guard case let .success(isLastDevice) = await userSession.clientProxy.isOnlyDeviceLeft() else { - flowParameters.userIndicatorController.alertInfo = .init(id: .init()) + navigationRootCoordinator.alertInfo = .init(id: .init()) return } @@ -495,26 +495,26 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } guard secureBackupController.recoveryState.value == .enabled else { - flowParameters.userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenSignoutRecoveryDisabledTitle, - message: L10n.screenSignoutRecoveryDisabledSubtitle, - primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in - self?.actionsSubject.send(.logout) - }, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in - self?.chatsTabFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) - }) + navigationRootCoordinator.alertInfo = .init(id: .init(), + title: L10n.screenSignoutRecoveryDisabledTitle, + message: L10n.screenSignoutRecoveryDisabledSubtitle, + primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in + self?.actionsSubject.send(.logout) + }, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in + self?.chatsTabFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) + }) return } guard secureBackupController.keyBackupState.value == .enabled else { - flowParameters.userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenSignoutKeyBackupDisabledTitle, - message: L10n.screenSignoutKeyBackupDisabledSubtitle, - primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in - self?.actionsSubject.send(.logout) - }, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in - self?.chatsTabFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) - }) + navigationRootCoordinator.alertInfo = .init(id: .init(), + title: L10n.screenSignoutKeyBackupDisabledTitle, + message: L10n.screenSignoutKeyBackupDisabledSubtitle, + primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in + self?.actionsSubject.send(.logout) + }, secondaryButton: .init(title: L10n.commonSettings, role: .cancel) { [weak self] in + self?.chatsTabFlowCoordinator.handleAppRoute(.chatBackupSettings, animated: true) + }) return } @@ -522,12 +522,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { } private func logout() { - flowParameters.userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenSignoutConfirmationDialogTitle, - message: L10n.screenSignoutConfirmationDialogContent, - primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in - self?.actionsSubject.send(.logout) - }) + navigationRootCoordinator.alertInfo = .init(id: .init(), + title: L10n.screenSignoutConfirmationDialogTitle, + message: L10n.screenSignoutConfirmationDialogContent, + primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in + self?.actionsSubject.send(.logout) + }) } private func presentSecureBackupLogoutConfirmationScreen() { diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 55b0643cc..165a64739 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -19524,7 +19524,6 @@ class UserIdentityProxyMock: UserIdentityProxyProtocol, @unchecked Sendable { } class UserIndicatorControllerMock: UserIndicatorControllerProtocol, @unchecked Sendable { var window: UIWindow? - var alertInfo: AlertInfo? //MARK: - submitIndicator diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift index 8e8fdff1a..e11cf3d9a 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorController.swift @@ -32,8 +32,6 @@ class UserIndicatorController: ObservableObject, UserIndicatorControllerProtocol } } - @Published var alertInfo: AlertInfo? - var window: UIWindow? { didSet { let hostingController = UIHostingController(rootView: UserIndicatorPresenter(userIndicatorController: self).statusBarHidden(ProcessInfo.isRunningUITests)) diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift index 2ebba6355..3bcd71145 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorControllerProtocol.swift @@ -15,7 +15,6 @@ protocol UserIndicatorControllerProtocol: CoordinatorProtocol { func retractAllIndicators() var window: UIWindow? { get set } - var alertInfo: AlertInfo? { get set } } extension UserIndicatorControllerProtocol { diff --git a/ElementX/Sources/Other/UserIndicator/UserIndicatorPresenter.swift b/ElementX/Sources/Other/UserIndicator/UserIndicatorPresenter.swift index fe5158ea6..805489dc8 100644 --- a/ElementX/Sources/Other/UserIndicator/UserIndicatorPresenter.swift +++ b/ElementX/Sources/Other/UserIndicator/UserIndicatorPresenter.swift @@ -28,6 +28,5 @@ struct UserIndicatorPresenter: View { } } } - .alert(item: $userIndicatorController.alertInfo) } } diff --git a/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift b/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift index db27f3f14..3ca623249 100644 --- a/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift +++ b/ElementX/Sources/Screens/Authentication/OIDCAuthenticationPresenter.swift @@ -59,7 +59,7 @@ class OIDCAuthenticationPresenter: NSObject { let errorDescription = error.map(String.init(describing:)) ?? "Unknown error" MXLog.error("Missing callback URL from the web authentication session: \(errorDescription)") - userIndicatorController.alertInfo = AlertInfo(id: UUID()) + showFailureIndicator() await authenticationService.abortOIDCLogin(data: oidcData) return .failure(.oidcError(.unknown)) } @@ -76,7 +76,7 @@ class OIDCAuthenticationPresenter: NSObject { return .failure(.oidcError(.userCancellation)) case .failure(let error): MXLog.error("Error occurred: \(error)") - userIndicatorController.alertInfo = AlertInfo(id: UUID()) + showFailureIndicator() return .failure(error) } } @@ -85,10 +85,16 @@ class OIDCAuthenticationPresenter: NSObject { activeSession?.cancel() } - private static let loadingIndicatorID = "\(OIDCAuthenticationPresenter.self)-Loading" + private var loadingIndicatorID: String { + "\(Self.self)-Loading" + } + + private var failureIndicatorID: String { + "\(Self.self)-Failure" + } private func startLoading(delay: Duration? = nil) { - userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorID, + userIndicatorController.submitIndicator(UserIndicator(id: loadingIndicatorID, type: .modal, title: L10n.commonLoading, persistent: true), @@ -96,7 +102,14 @@ class OIDCAuthenticationPresenter: NSObject { } private func stopLoading() { - userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorID) + userIndicatorController.retractIndicatorWithId(loadingIndicatorID) + } + + private func showFailureIndicator() { + userIndicatorController.submitIndicator(UserIndicator(id: failureIndicatorID, + type: .toast, + title: L10n.errorUnknown, + iconName: "xmark")) } } diff --git a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift index 184c3e575..ccf201fac 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenModels.swift @@ -10,6 +10,7 @@ import SwiftUI enum CreateRoomScreenErrorType: Error { case failedCreatingRoom + case failedProcessingMedia case failedUploadingMedia case fileTooLarge case mediaFileError diff --git a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift index 84bd6fcd0..c312ed812 100644 --- a/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoomScreen/CreateRoomScreenViewModel.swift @@ -116,7 +116,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen do { guard case let .success(maxUploadSize) = await userSession.clientProxy.maxMediaUploadSize else { MXLog.error("Failed to get max upload size") - userIndicatorController.alertInfo = AlertInfo(id: .init()) + state.bindings.alertInfo = .init(id: .unknown) return } let mediaInfo = try await mediaUploadingPreprocessor.processMedia(at: fileURL, maxUploadSize: maxUploadSize).get() @@ -128,7 +128,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen break } } catch { - userIndicatorController.alertInfo = AlertInfo(id: .init()) + state.bindings.alertInfo = .init(id: .failedProcessingMedia) } hideLoadingIndicator() } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 36918cc52..33679ee52 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -105,9 +105,9 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr return } - userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.commonUnableToInviteTitle, - message: L10n.commonUnableToInviteMessage) + state.bindings.alertInfo = .init(id: .unknown, + title: L10n.commonUnableToInviteTitle, + message: L10n.commonUnableToInviteMessage) } } diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift index fd24d6bf2..dc7ba1d48 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift @@ -69,7 +69,10 @@ struct RoomDetailsEditScreenViewStateBindings { } enum RoomDetailsEditScreenAlertType { + case failedProcessingMedia case unsavedChanges + case saveError + case unknown } enum RoomDetailsEditScreenViewAction { diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index 74b0a45b3..a7c57bcb5 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -86,7 +86,7 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe guard case let .success(maxUploadSize) = await clientProxy.maxMediaUploadSize else { MXLog.error("Failed to get max upload size") - userIndicatorController.alertInfo = .init(id: .init()) + state.bindings.alertInfo = .init(id: .unknown) return } let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize) @@ -95,7 +95,7 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe case .success(.image): state.localMedia = try? mediaResult.get() case .failure, .success: - userIndicatorController.alertInfo = .init(id: .init()) + state.bindings.alertInfo = .init(id: .failedProcessingMedia) } } } @@ -161,9 +161,9 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe actionsSubject.send(.saveFinished) } catch { - userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenRoomDetailsEditionErrorTitle, - message: L10n.screenRoomDetailsEditionError) + state.bindings.alertInfo = .init(id: .saveError, + title: L10n.screenRoomDetailsEditionErrorTitle, + message: L10n.screenRoomDetailsEditionError) } } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index fbc6d9658..1db931364 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -92,6 +92,11 @@ struct RoomScreenViewState: BindableState { struct RoomScreenViewStateBindings { /// The view model used to present a QuickLook media preview. var mediaPreviewViewModel: TimelineMediaPreviewViewModel? + var alertInfo: AlertInfo? +} + +enum RoomScreenAlertType { + case unknown } enum RoomScreenFooterViewAction { diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 5ef834065..b0aa5184e 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -286,7 +286,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol showLoadingIndicator() if case .failure = await clientProxy.pinUserIdentity(userID) { - userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError) + state.bindings.alertInfo = .init(id: .unknown, title: L10n.commonError) } } @@ -298,7 +298,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol showLoadingIndicator() if case .failure = await clientProxy.withdrawUserIdentityVerification(userID) { - userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError) + state.bindings.alertInfo = .init(id: .unknown, title: L10n.commonError) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 050c14ebe..5d90105a2 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -67,6 +67,7 @@ struct RoomScreen: View { .toolbar { toolbar } .toolbarBackground(.visible, for: .navigationBar) // Fix the toolbar's background. .overlay { loadingIndicator } + .alert(item: $context.alertInfo) .timelineMediaPreview(viewModel: $context.mediaPreviewViewModel) .track(screen: .Room) .sentryTrace("\(Self.self)") diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift index d920a15e2..941222f2b 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenModels.swift @@ -52,7 +52,10 @@ struct UserDetailsEditScreenViewStateBindings { } enum UserDetailsEditScreenAlertType { + case failedProcessingMedia case unsavedChanges + case saveError + case unknown } enum UserDetailsEditScreenViewAction { diff --git a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift index f176549fa..9abc722ad 100644 --- a/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/UserDetailsEditScreen/UserDetailsEditScreenViewModel.swift @@ -92,7 +92,7 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe guard case let .success(maxUploadSize) = await clientProxy.maxMediaUploadSize else { MXLog.error("Failed to get max upload size") - userIndicatorController.alertInfo = .init(id: .init()) + state.bindings.alertInfo = .init(id: .unknown) return } let mediaResult = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize) @@ -101,7 +101,7 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe case .success(.image): state.localMedia = try? mediaResult.get() case .failure, .success: - userIndicatorController.alertInfo = .init(id: .init()) + state.bindings.alertInfo = .init(id: .failedProcessingMedia) } } } @@ -149,9 +149,9 @@ class UserDetailsEditScreenViewModel: UserDetailsEditScreenViewModelType, UserDe actionsSubject.send(.dismiss) } catch { - userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenEditProfileErrorTitle, - message: L10n.screenEditProfileError) + state.bindings.alertInfo = .init(id: .saveError, + title: L10n.screenEditProfileErrorTitle, + message: L10n.screenEditProfileError) } } } diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 94069b646..18989834b 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -199,6 +199,9 @@ enum TimelineAlertInfoType: Hashable { case sendingFailed case encryptionAuthenticity(String) case encryptionForwarder(String) + case inviteAgain + case unableToInvite + case unknown } struct RoomMemberState { diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 4be4ae06a..78d79b959 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -579,7 +579,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { shouldShowInviteAlert .sink { [weak self] _ in - self?.showInviteAlert() + self?.displayAlert(.inviteAgain) } .store(in: &cancellables) } @@ -865,19 +865,11 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { // MARK: - Direct chats logics - private func showInviteAlert() { - userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.screenRoomInviteAgainAlertTitle, - message: L10n.screenRoomInviteAgainAlertMessage, - primaryButton: .init(title: L10n.actionInvite) { [weak self] in self?.inviteOtherDMUserBack() }, - secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) - } - private let inviteLoadingIndicatorID = UUID().uuidString private func inviteOtherDMUserBack() { guard roomProxy.infoPublisher.value.isUserAloneInDirectRoom else { - userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError) + displayAlert(.unknown) return } @@ -892,7 +884,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { members.count == 2, let otherPerson = members.first(where: { $0.userID != roomProxy.ownUserID && $0.membership == .leave }) else { - userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError) + displayAlert(.unknown) return } @@ -900,9 +892,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { case .success: break case .failure: - userIndicatorController.alertInfo = .init(id: .init(), - title: L10n.commonUnableToInviteTitle, - message: L10n.commonUnableToInviteMessage) + displayAlert(.unableToInvite) } } } @@ -1021,6 +1011,18 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { guard let self else { return } appMediator.open(appSettings.historySharingDetailsURL) }) + case .inviteAgain: + state.bindings.alertInfo = .init(id: .inviteAgain, + title: L10n.screenRoomInviteAgainAlertTitle, + message: L10n.screenRoomInviteAgainAlertMessage, + primaryButton: .init(title: L10n.actionInvite) { [weak self] in self?.inviteOtherDMUserBack() }, + secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) + case .unableToInvite: + state.bindings.alertInfo = .init(id: .unableToInvite, + title: L10n.commonUnableToInviteTitle, + message: L10n.commonUnableToInviteMessage) + case .unknown: + state.bindings.alertInfo = .init(id: .unknown, title: L10n.commonError) } } diff --git a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift index 138a6ed27..b416fc1d8 100644 --- a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift @@ -137,7 +137,7 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) viewModel.didSelectMediaUrl(url: .picturesDirectory) try? await Task.sleep(for: .milliseconds(100)) - XCTAssertNotNil(userIndicatorController.alertInfo) + XCTAssertNotNil(context.alertInfo) } func testDeleteAvatar() {