Handle media previews and invite avatars through the account data (#4142)

* added account data media display policy handling to NSE

* pr suggestions

* update compound

* code improvement
This commit is contained in:
Mauro
2025-05-28 19:51:34 +02:00
committed by GitHub
parent 256292c5a9
commit aef91b5bc9
24 changed files with 626 additions and 61 deletions
@@ -39,6 +39,9 @@ class ClientProxy: ClientProxyProtocol {
// periphery:ignore - required for instance retention in the rust codebase
private var sendQueueListenerTaskHandle: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var mediaPreviewConfigListenerTaskHandle: TaskHandle?
private var delegateHandle: TaskHandle?
// These following summary providers both operate on the same allRooms() list but
@@ -132,6 +135,16 @@ class ClientProxy: ClientProxyProtocol {
verificationStateSubject.asCurrentValuePublisher()
}
private let timelineMediaVisibilitySubject = CurrentValueSubject<TimelineMediaVisibility, Never>(.always)
var timelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never> {
timelineMediaVisibilitySubject.asCurrentValuePublisher()
}
private let hideInviteAvatarsSubject = CurrentValueSubject<Bool, Never>(false)
var hideInviteAvatarsPublisher: CurrentValuePublisher<Bool, Never> {
hideInviteAvatarsSubject.asCurrentValuePublisher()
}
var roomsToAwait: Set<String> = []
private let sendQueueStatusSubject = CurrentValueSubject<Bool, Never>(false)
@@ -230,6 +243,10 @@ class ClientProxy: ClientProxyProtocol {
MXLog.error("Failed setting media retention policy with error: \(error)")
}
}
Task {
mediaPreviewConfigListenerTaskHandle = await createMediaPreviewConfigObserver()
}
}
var userID: String {
@@ -724,6 +741,16 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func fetchMediaPreviewConfiguration() async -> Result<MediaPreviewConfig?, ClientProxyError> {
do {
let config = try await client.fetchMediaPreviewConfig()
return .success(config)
} catch {
MXLog.error("Failed fetching media preview config with error: \(error)")
return .failure(.sdkError(error))
}
}
// MARK: Ignored users
@@ -798,6 +825,28 @@ class ClientProxy: ClientProxyProtocol {
return users.elements
}
// MARK: Moderation & Safety
func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result<Void, ClientProxyError> {
do {
try await client.setMediaPreviewDisplayPolicy(policy: value.rustValue)
return .success(())
} catch {
MXLog.error("Failed to set timeline media visibility: \(error)")
return .failure(.sdkError(error))
}
}
func setHideInviteAvatars(_ value: Bool) async -> Result<Void, ClientProxyError> {
do {
try await client.setInviteAvatarsDisplayPolicy(policy: value ? .off : .on)
return .success(())
} catch {
MXLog.error("Failed to set hide invite avatars: \(error)")
return .failure(.sdkError(error))
}
}
// MARK: - Private
private func cacheAccountURL() async {
@@ -867,6 +916,26 @@ class ClientProxy: ClientProxyProtocol {
}
})
}
private func createMediaPreviewConfigObserver() async -> TaskHandle? {
do {
return try await client.subscribeToMediaPreviewConfig(listener: SDKListener { [weak self] config in
guard let self else { return }
if let config {
timelineMediaVisibilitySubject.send(config.mediaPreviewVisibility)
hideInviteAvatarsSubject.send(config.hideInviteAvatars)
} else {
// return default values
timelineMediaVisibilitySubject.send(.always)
hideInviteAvatarsSubject.send(false)
}
})
} catch {
MXLog.error("Failed creating media preview config observer: \(error)")
return nil
}
}
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.state(listener: SDKListener { [weak self] state in
@@ -1133,3 +1202,38 @@ private struct ClientProxyServices {
self.roomListService = roomListService
}
}
private extension MediaPreviewConfig {
var mediaPreviewVisibility: TimelineMediaVisibility {
switch mediaPreviews {
case .on:
.always
case .private:
.privateOnly
case .off:
.never
}
}
var hideInviteAvatars: Bool {
switch inviteAvatars {
case .off:
true
case .on:
false
}
}
}
private extension TimelineMediaVisibility {
var rustValue: MediaPreviews {
switch self {
case .always:
.on
case .never:
.off
case .privateOnly:
.private
}
}
}
@@ -63,6 +63,13 @@ enum SessionVerificationState {
case unverified
}
// The `Decodable` conformance is just for the purpose of migration
enum TimelineMediaVisibility: Decodable {
case always
case privateOnly
case never
}
// sourcery: AutoMockable
protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> { get }
@@ -93,6 +100,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
/// We delay fetching this until after the first sync. Nil until then
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get }
var timelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never> { get }
var hideInviteAvatarsPublisher: CurrentValuePublisher<Bool, Never> { get }
var pusherNotificationClientIdentifier: String? { get }
var roomSummaryProvider: RoomSummaryProviderProtocol { get }
@@ -185,6 +196,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func isAliasAvailable(_ alias: String) async -> Result<Bool, ClientProxyError>
@discardableResult func clearCaches() async -> Result<Void, ClientProxyError>
func fetchMediaPreviewConfiguration() async -> Result<MediaPreviewConfig?, ClientProxyError>
// MARK: - Ignored users
@@ -210,4 +223,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
func userIdentity(for userID: String) async -> Result<UserIdentityProxyProtocol?, ClientProxyError>
// MARK: - Moderation & Safety
func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result<Void, ClientProxyError>
func setHideInviteAvatars(_ value: Bool) async -> Result<Void, ClientProxyError>
}