diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 09a46983a..3a9ad7485 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -46,6 +46,7 @@ enum ClientProxyMockError: Error { } extension ClientProxyMock { + // swiftlint:disable:next function_body_length convenience init(_ configuration: ClientProxyMockConfiguration) { self.init() @@ -91,6 +92,8 @@ extension ClientProxyMock { loadUserProfileIfNeededReturnValue = .success(()) setUserDisplayNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) setUserAvatarMediaReturnValue = .success(()) + isUserStatusSupportedReturnValue = .success(false) + setUserStatusReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) removeUserAvatarReturnValue = .success(()) isAliasAvailableReturnValue = .success(true) searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false)) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 147029e01..b48775c25 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -3235,6 +3235,34 @@ nonisolated class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return removeUserAvatarReturnValue } } + //MARK: - isUserStatusSupported + + private let isUserStatusSupportedCallsCountLock = NSLock() + private nonisolated(unsafe) var isUserStatusSupportedUnderlyingCallsCount = 0 + var isUserStatusSupportedCallsCount: Int { + get { isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount } } + set { isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount = newValue } } + } + var isUserStatusSupportedCalled: Bool { + return isUserStatusSupportedCallsCount > 0 + } + + private let isUserStatusSupportedReturnValueLock = NSLock() + private nonisolated(unsafe) var isUserStatusSupportedUnderlyingReturnValue: Result! + var isUserStatusSupportedReturnValue: Result! { + get { isUserStatusSupportedReturnValueLock.withLock { isUserStatusSupportedUnderlyingReturnValue } } + set { isUserStatusSupportedReturnValueLock.withLock { isUserStatusSupportedUnderlyingReturnValue = newValue } } + } + nonisolated(unsafe) var isUserStatusSupportedClosure: (() async -> Result)? + + @concurrent func isUserStatusSupported() async -> Result { + isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount += 1 } + if let isUserStatusSupportedClosure = isUserStatusSupportedClosure { + return await isUserStatusSupportedClosure() + } else { + return isUserStatusSupportedReturnValue + } + } //MARK: - setUserStatus private let setUserStatusCallsCountLock = NSLock() diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift index b26ee5f13..29c06a6f4 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift @@ -36,7 +36,7 @@ enum SettingsScreenSecuritySectionMode { struct SettingsScreenViewState: BindableState { var deviceID: String? var userProfile: UserProfile - var showUserStatus: Bool + var showUserStatus = false var showLinkNewDeviceButton: Bool var accountProfileURL: URL? var showAccountDeactivation: Bool diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 2a1cbe380..1ef4bd394 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -29,7 +29,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID, userProfile: userSession.clientProxy.userProfilePublisher.value, - showUserStatus: appSettings.userStatusEnabled, showLinkNewDeviceButton: appSettings.linkNewDeviceEnabled, showAccountDeactivation: userSession.clientProxy.canDeactivateAccount, showDeveloperOptions: appSettings.developerOptionsEnabled, @@ -86,9 +85,12 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo .store(in: &cancellables) Task { + if appSettings.userStatusEnabled, case .success(true) = await userSession.clientProxy.isUserStatusSupported() { + state.showUserStatus = true + } await userSession.clientProxy.loadUserProfileIfNeeded() - await state.accountProfileURL = userSession.clientProxy.accountURL(action: .profile) } + Task { await state.accountProfileURL = userSession.clientProxy.accountURL(action: .profile) } } override func process(viewAction: SettingsScreenViewAction) { diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index c59af281d..9fd565ca1 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -281,7 +281,11 @@ class ClientProxy: ClientProxyProtocol { try await client.setUtdDelegate(utdDelegate: ClientDecryptionErrorDelegate(actionsSubject: actionsSubject)) - let canSubscribeToUserProfile = appSettings.userStatusEnabled // TODO: @pixlwave to add a /versions check too. + let canSubscribeToUserProfile = if appSettings.userStatusEnabled, case .success(true) = await isUserStatusSupported() { + true + } else { + false + } if !canSubscribeToUserProfile { loadUserAvatarURLFromCache() @@ -753,6 +757,15 @@ class ClientProxy: ClientProxyProtocol { } } + func isUserStatusSupported() async -> Result { + do { + return try await .success(client.isUserStatusSupported()) + } catch { + MXLog.error("Failed detecting user status support with error: \(error)") + return .failure(.sdkError(error)) + } + } + // periphery:ignore - might be useful to have func setUserStatus(_ status: UserStatus.Raw) async -> Result { do { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index ccf5d649d..4c0931c10 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -226,6 +226,7 @@ protocol ClientProxyProtocol: AnyObject { func setUserDisplayName(_ name: String) async -> Result func setUserAvatar(media: MediaInfo) async -> Result func removeUserAvatar() async -> Result + func isUserStatusSupported() async -> Result func setUserStatus(_ status: UserStatus.Raw) async -> Result func removeUserStatus() async -> Result diff --git a/SDKMocks/Sources/Generated/SDKGeneratedMocks.swift b/SDKMocks/Sources/Generated/SDKGeneratedMocks.swift index 8dcfc341e..4a1fc320d 100644 --- a/SDKMocks/Sources/Generated/SDKGeneratedMocks.swift +++ b/SDKMocks/Sources/Generated/SDKGeneratedMocks.swift @@ -1835,6 +1835,39 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { } } + //MARK: - isUserStatusSupported + + open var isUserStatusSupportedThrowableError: Error? + private let isUserStatusSupportedCallsCountLock = NSLock() + private var isUserStatusSupportedUnderlyingCallsCount = 0 + open var isUserStatusSupportedCallsCount: Int { + get { isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount } } + set { isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount = newValue } } + } + open var isUserStatusSupportedCalled: Bool { + return isUserStatusSupportedCallsCount > 0 + } + + private let isUserStatusSupportedReturnValueLock = NSLock() + open var isUserStatusSupportedUnderlyingReturnValue: Bool! + open var isUserStatusSupportedReturnValue: Bool! { + get { isUserStatusSupportedReturnValueLock.withLock { isUserStatusSupportedUnderlyingReturnValue } } + set { isUserStatusSupportedReturnValueLock.withLock { isUserStatusSupportedUnderlyingReturnValue = newValue } } + } + open var isUserStatusSupportedClosure: (() async throws -> Bool)? + + open override func isUserStatusSupported() async throws -> Bool { + if let error = isUserStatusSupportedThrowableError { + throw error + } + isUserStatusSupportedCallsCountLock.withLock { isUserStatusSupportedUnderlyingCallsCount += 1 } + if let isUserStatusSupportedClosure = isUserStatusSupportedClosure { + return try await isUserStatusSupportedClosure() + } else { + return isUserStatusSupportedReturnValue + } + } + //MARK: - joinRoomById open var joinRoomByIdRoomIdThrowableError: Error?