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
+1 -1
View File
@@ -8836,7 +8836,7 @@
repositoryURL = "https://github.com/element-hq/compound-ios"; repositoryURL = "https://github.com/element-hq/compound-ios";
requirement = { requirement = {
kind = revision; kind = revision;
revision = 81ba8bd8b3971beac252129c5466d7eac2f2ec31; revision = 139d4f3d1ed19cc513686e5e39750ecef54c8403;
}; };
}; };
F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = { F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = {
@@ -15,7 +15,7 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/compound-ios", "location" : "https://github.com/element-hq/compound-ios",
"state" : { "state" : {
"revision" : "81ba8bd8b3971beac252129c5466d7eac2f2ec31" "revision" : "139d4f3d1ed19cc513686e5e39750ecef54c8403"
} }
}, },
{ {
@@ -203,8 +203,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
"state" : { "state" : {
"revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a", "revision" : "3dd282d3269b061853a3b3bcd23a509d2aa166ce",
"version" : "5.3.0" "version" : "6.2.0"
} }
}, },
{ {
@@ -30,11 +30,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
private var userSession: UserSessionProtocol? { private var userSession: UserSessionProtocol? {
didSet { didSet {
userSessionObserver?.cancel() userSessionObserver?.cancel()
if userSession != nil { if let userSession {
configureElementCallService() configureElementCallService()
configureNotificationManager() configureNotificationManager()
observeUserSessionChanges() observeUserSessionChanges()
startSync() startSync()
performSettingsToAccountDataMigration(userSession: userSession)
} }
} }
} }
@@ -399,13 +400,50 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
Tracing.deleteLogFiles() Tracing.deleteLogFiles()
MXLog.info("Migrating to v1.6.7, log files have been wiped") MXLog.info("Migrating to v1.6.7, log files have been wiped")
} }
}
if oldVersion < Version(25, 4, 2) { // This could be removed once the adotpion of 25.06.x is widespread.
MXLog.info("Migrating to v25.04.2, checking if hideTimelineMedia flag can be migrated to timelineMediaVisibility") private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) {
// Migration for the old `hideTimelineMedia` flag. guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else {
if let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier), return
let hideTimelineMedia = userDefaults.value(forKey: "hideTimelineMedia") as? Bool { }
appSettings.timelineMediaVisibility = hideTimelineMedia ? .never : .always
let hideInviteAvatars = userDefaults.value(forKey: "hideInviteAvatars") as? Bool
let timelineMediaVisibility = userDefaults
.data(forKey: "timelineMediaVisibility")
.flatMap {
try? JSONDecoder().decode(TimelineMediaVisibility.self, from: $0)
}
let hideTimelineMedia = userDefaults.value(forKey: "hideTimelineMedia") as? Bool
guard hideInviteAvatars != nil || timelineMediaVisibility != nil || hideTimelineMedia != nil else {
// No migration needed, no local settings found.
return
}
Task {
switch await userSession.clientProxy.fetchMediaPreviewConfiguration() {
case let .success(config):
guard config == nil else {
// Found a server configuration, no need to migrate.
userDefaults.removeObject(forKey: "hideInviteAvatars")
userDefaults.removeObject(forKey: "timelineMediaVisibility")
userDefaults.removeObject(forKey: "hideTimelineMedia")
return
}
if let hideInviteAvatars, case .success = await userSession.clientProxy.setHideInviteAvatars(hideInviteAvatars) {
userDefaults.removeObject(forKey: "hideInviteAvatars")
}
if let timelineMediaVisibility, case .success = await userSession.clientProxy.setTimelineMediaVisibility(timelineMediaVisibility) {
userDefaults.removeObject(forKey: "timelineMediaVisibility")
} else if let hideTimelineMedia, case .success = await userSession.clientProxy.setTimelineMediaVisibility(hideTimelineMedia ? .never : .always) {
userDefaults.removeObject(forKey: "hideTimelineMedia")
}
case let .failure(error):
MXLog.error("Could not perform migration, failed to fetch media preview config: \(error)")
return
} }
} }
} }
@@ -17,8 +17,6 @@ protocol CommonSettingsProtocol {
var logLevel: LogLevel { get } var logLevel: LogLevel { get }
var traceLogPacks: Set<TraceLogPack> { get } var traceLogPacks: Set<TraceLogPack> { get }
var enableOnlySignedDeviceIsolationMode: Bool { get } var enableOnlySignedDeviceIsolationMode: Bool { get }
var hideInviteAvatars: Bool { get }
var timelineMediaVisibility: TimelineMediaVisibility { get }
var hideQuietNotificationAlerts: Bool { get } var hideQuietNotificationAlerts: Bool { get }
} }
@@ -46,8 +44,6 @@ final class AppSettings {
case optimizeMediaUploads case optimizeMediaUploads
case appAppearance case appAppearance
case sharePresence case sharePresence
case hideInviteAvatars
case timelineMediaVisibility
case isNewBloomEnabled case isNewBloomEnabled
case elementCallBaseURLOverride case elementCallBaseURLOverride
@@ -368,20 +364,8 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store))
var enableOnlySignedDeviceIsolationMode var enableOnlySignedDeviceIsolationMode
@UserPreference(key: UserDefaultsKeys.hideInviteAvatars, defaultValue: false, storageType: .userDefaults(store))
var hideInviteAvatars
@UserPreference(key: UserDefaultsKeys.timelineMediaVisibility, defaultValue: TimelineMediaVisibility.always, storageType: .userDefaults(store))
var timelineMediaVisibility
@UserPreference(key: UserDefaultsKeys.hideQuietNotificationAlerts, defaultValue: false, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.hideQuietNotificationAlerts, defaultValue: false, storageType: .userDefaults(store))
var hideQuietNotificationAlerts var hideQuietNotificationAlerts
} }
extension AppSettings: CommonSettingsProtocol { } extension AppSettings: CommonSettingsProtocol { }
enum TimelineMediaVisibility: Codable {
case always
case privateOnly
case never
}
@@ -226,7 +226,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
private func presentAdvancedSettings() { private func presentAdvancedSettings() {
let coordinator = AdvancedSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings, let coordinator = AdvancedSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
analytics: parameters.analytics)) analytics: parameters.analytics,
clientProxy: parameters.userSession.clientProxy,
userIndicatorController: parameters.userIndicatorController))
navigationStackCoordinator.push(coordinator) navigationStackCoordinator.push(coordinator)
} }
@@ -17,6 +17,9 @@ struct ClientProxyMockConfiguration {
var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol? var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol?
var recoveryState: SecureBackupRecoveryState = .enabled var recoveryState: SecureBackupRecoveryState = .enabled
var timelineMediaVisibility = TimelineMediaVisibility.always
var hideInviteAvatars = false
} }
enum ClientProxyMockError: Error { enum ClientProxyMockError: Error {
@@ -94,5 +97,8 @@ extension ClientProxyMock {
userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init())) userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init()))
underlyingIsReportRoomSupported = true underlyingIsReportRoomSupported = true
underlyingTimelineMediaVisibilityPublisher = CurrentValueSubject<TimelineMediaVisibility, Never>(configuration.timelineMediaVisibility).asCurrentValuePublisher()
underlyingHideInviteAvatarsPublisher = CurrentValueSubject<Bool, Never>(configuration.hideInviteAvatars).asCurrentValuePublisher()
} }
} }
@@ -2271,6 +2271,16 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
set(value) { underlyingIgnoredUsersPublisher = value } set(value) { underlyingIgnoredUsersPublisher = value }
} }
var underlyingIgnoredUsersPublisher: CurrentValuePublisher<[String]?, Never>! var underlyingIgnoredUsersPublisher: CurrentValuePublisher<[String]?, Never>!
var timelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never> {
get { return underlyingTimelineMediaVisibilityPublisher }
set(value) { underlyingTimelineMediaVisibilityPublisher = value }
}
var underlyingTimelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never>!
var hideInviteAvatarsPublisher: CurrentValuePublisher<Bool, Never> {
get { return underlyingHideInviteAvatarsPublisher }
set(value) { underlyingHideInviteAvatarsPublisher = value }
}
var underlyingHideInviteAvatarsPublisher: CurrentValuePublisher<Bool, Never>!
var pusherNotificationClientIdentifier: String? var pusherNotificationClientIdentifier: String?
var roomSummaryProvider: RoomSummaryProviderProtocol { var roomSummaryProvider: RoomSummaryProviderProtocol {
get { return underlyingRoomSummaryProvider } get { return underlyingRoomSummaryProvider }
@@ -4363,6 +4373,70 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
return clearCachesReturnValue return clearCachesReturnValue
} }
} }
//MARK: - fetchMediaPreviewConfiguration
var fetchMediaPreviewConfigurationUnderlyingCallsCount = 0
var fetchMediaPreviewConfigurationCallsCount: Int {
get {
if Thread.isMainThread {
return fetchMediaPreviewConfigurationUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = fetchMediaPreviewConfigurationUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
fetchMediaPreviewConfigurationUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
fetchMediaPreviewConfigurationUnderlyingCallsCount = newValue
}
}
}
}
var fetchMediaPreviewConfigurationCalled: Bool {
return fetchMediaPreviewConfigurationCallsCount > 0
}
var fetchMediaPreviewConfigurationUnderlyingReturnValue: Result<MediaPreviewConfig?, ClientProxyError>!
var fetchMediaPreviewConfigurationReturnValue: Result<MediaPreviewConfig?, ClientProxyError>! {
get {
if Thread.isMainThread {
return fetchMediaPreviewConfigurationUnderlyingReturnValue
} else {
var returnValue: Result<MediaPreviewConfig?, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = fetchMediaPreviewConfigurationUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
fetchMediaPreviewConfigurationUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
fetchMediaPreviewConfigurationUnderlyingReturnValue = newValue
}
}
}
}
var fetchMediaPreviewConfigurationClosure: (() async -> Result<MediaPreviewConfig?, ClientProxyError>)?
func fetchMediaPreviewConfiguration() async -> Result<MediaPreviewConfig?, ClientProxyError> {
fetchMediaPreviewConfigurationCallsCount += 1
if let fetchMediaPreviewConfigurationClosure = fetchMediaPreviewConfigurationClosure {
return await fetchMediaPreviewConfigurationClosure()
} else {
return fetchMediaPreviewConfigurationReturnValue
}
}
//MARK: - ignoreUser //MARK: - ignoreUser
var ignoreUserUnderlyingCallsCount = 0 var ignoreUserUnderlyingCallsCount = 0
@@ -5103,6 +5177,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
return userIdentityForReturnValue return userIdentityForReturnValue
} }
} }
//MARK: - setTimelineMediaVisibility
var setTimelineMediaVisibilityUnderlyingCallsCount = 0
var setTimelineMediaVisibilityCallsCount: Int {
get {
if Thread.isMainThread {
return setTimelineMediaVisibilityUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = setTimelineMediaVisibilityUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
setTimelineMediaVisibilityUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
setTimelineMediaVisibilityUnderlyingCallsCount = newValue
}
}
}
}
var setTimelineMediaVisibilityCalled: Bool {
return setTimelineMediaVisibilityCallsCount > 0
}
var setTimelineMediaVisibilityReceivedValue: TimelineMediaVisibility?
var setTimelineMediaVisibilityReceivedInvocations: [TimelineMediaVisibility] = []
var setTimelineMediaVisibilityUnderlyingReturnValue: Result<Void, ClientProxyError>!
var setTimelineMediaVisibilityReturnValue: Result<Void, ClientProxyError>! {
get {
if Thread.isMainThread {
return setTimelineMediaVisibilityUnderlyingReturnValue
} else {
var returnValue: Result<Void, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = setTimelineMediaVisibilityUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
setTimelineMediaVisibilityUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
setTimelineMediaVisibilityUnderlyingReturnValue = newValue
}
}
}
}
var setTimelineMediaVisibilityClosure: ((TimelineMediaVisibility) async -> Result<Void, ClientProxyError>)?
func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result<Void, ClientProxyError> {
setTimelineMediaVisibilityCallsCount += 1
setTimelineMediaVisibilityReceivedValue = value
DispatchQueue.main.async {
self.setTimelineMediaVisibilityReceivedInvocations.append(value)
}
if let setTimelineMediaVisibilityClosure = setTimelineMediaVisibilityClosure {
return await setTimelineMediaVisibilityClosure(value)
} else {
return setTimelineMediaVisibilityReturnValue
}
}
//MARK: - setHideInviteAvatars
var setHideInviteAvatarsUnderlyingCallsCount = 0
var setHideInviteAvatarsCallsCount: Int {
get {
if Thread.isMainThread {
return setHideInviteAvatarsUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = setHideInviteAvatarsUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
setHideInviteAvatarsUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
setHideInviteAvatarsUnderlyingCallsCount = newValue
}
}
}
}
var setHideInviteAvatarsCalled: Bool {
return setHideInviteAvatarsCallsCount > 0
}
var setHideInviteAvatarsReceivedValue: Bool?
var setHideInviteAvatarsReceivedInvocations: [Bool] = []
var setHideInviteAvatarsUnderlyingReturnValue: Result<Void, ClientProxyError>!
var setHideInviteAvatarsReturnValue: Result<Void, ClientProxyError>! {
get {
if Thread.isMainThread {
return setHideInviteAvatarsUnderlyingReturnValue
} else {
var returnValue: Result<Void, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = setHideInviteAvatarsUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
setHideInviteAvatarsUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
setHideInviteAvatarsUnderlyingReturnValue = newValue
}
}
}
}
var setHideInviteAvatarsClosure: ((Bool) async -> Result<Void, ClientProxyError>)?
func setHideInviteAvatars(_ value: Bool) async -> Result<Void, ClientProxyError> {
setHideInviteAvatarsCallsCount += 1
setHideInviteAvatarsReceivedValue = value
DispatchQueue.main.async {
self.setHideInviteAvatarsReceivedInvocations.append(value)
}
if let setHideInviteAvatarsClosure = setHideInviteAvatarsClosure {
return await setHideInviteAvatarsClosure(value)
} else {
return setHideInviteAvatarsReturnValue
}
}
//MARK: - loadMediaContentForSource //MARK: - loadMediaContentForSource
var loadMediaContentForSourceThrowableError: Error? var loadMediaContentForSourceThrowableError: Error?
@@ -1050,6 +1050,75 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
} }
} }
//MARK: - fetchMediaPreviewConfig
open var fetchMediaPreviewConfigThrowableError: Error?
var fetchMediaPreviewConfigUnderlyingCallsCount = 0
open var fetchMediaPreviewConfigCallsCount: Int {
get {
if Thread.isMainThread {
return fetchMediaPreviewConfigUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = fetchMediaPreviewConfigUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
fetchMediaPreviewConfigUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
fetchMediaPreviewConfigUnderlyingCallsCount = newValue
}
}
}
}
open var fetchMediaPreviewConfigCalled: Bool {
return fetchMediaPreviewConfigCallsCount > 0
}
var fetchMediaPreviewConfigUnderlyingReturnValue: MediaPreviewConfig?
open var fetchMediaPreviewConfigReturnValue: MediaPreviewConfig? {
get {
if Thread.isMainThread {
return fetchMediaPreviewConfigUnderlyingReturnValue
} else {
var returnValue: MediaPreviewConfig?? = nil
DispatchQueue.main.sync {
returnValue = fetchMediaPreviewConfigUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
fetchMediaPreviewConfigUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
fetchMediaPreviewConfigUnderlyingReturnValue = newValue
}
}
}
}
open var fetchMediaPreviewConfigClosure: (() async throws -> MediaPreviewConfig?)?
open override func fetchMediaPreviewConfig() async throws -> MediaPreviewConfig? {
if let error = fetchMediaPreviewConfigThrowableError {
throw error
}
fetchMediaPreviewConfigCallsCount += 1
if let fetchMediaPreviewConfigClosure = fetchMediaPreviewConfigClosure {
return try await fetchMediaPreviewConfigClosure()
} else {
return fetchMediaPreviewConfigReturnValue
}
}
//MARK: - getDmRoom //MARK: - getDmRoom
open var getDmRoomUserIdThrowableError: Error? open var getDmRoomUserIdThrowableError: Error?
@@ -1156,13 +1225,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
return getInviteAvatarsDisplayPolicyCallsCount > 0 return getInviteAvatarsDisplayPolicyCallsCount > 0
} }
var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars! var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars?
open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars! { open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars? {
get { get {
if Thread.isMainThread { if Thread.isMainThread {
return getInviteAvatarsDisplayPolicyUnderlyingReturnValue return getInviteAvatarsDisplayPolicyUnderlyingReturnValue
} else { } else {
var returnValue: InviteAvatars? = nil var returnValue: InviteAvatars?? = nil
DispatchQueue.main.sync { DispatchQueue.main.sync {
returnValue = getInviteAvatarsDisplayPolicyUnderlyingReturnValue returnValue = getInviteAvatarsDisplayPolicyUnderlyingReturnValue
} }
@@ -1180,9 +1249,9 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
} }
} }
} }
open var getInviteAvatarsDisplayPolicyClosure: (() async throws -> InviteAvatars)? open var getInviteAvatarsDisplayPolicyClosure: (() async throws -> InviteAvatars?)?
open override func getInviteAvatarsDisplayPolicy() async throws -> InviteAvatars { open override func getInviteAvatarsDisplayPolicy() async throws -> InviteAvatars? {
if let error = getInviteAvatarsDisplayPolicyThrowableError { if let error = getInviteAvatarsDisplayPolicyThrowableError {
throw error throw error
} }
@@ -1375,13 +1444,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
return getMediaPreviewDisplayPolicyCallsCount > 0 return getMediaPreviewDisplayPolicyCallsCount > 0
} }
var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews! var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews?
open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews! { open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews? {
get { get {
if Thread.isMainThread { if Thread.isMainThread {
return getMediaPreviewDisplayPolicyUnderlyingReturnValue return getMediaPreviewDisplayPolicyUnderlyingReturnValue
} else { } else {
var returnValue: MediaPreviews? = nil var returnValue: MediaPreviews?? = nil
DispatchQueue.main.sync { DispatchQueue.main.sync {
returnValue = getMediaPreviewDisplayPolicyUnderlyingReturnValue returnValue = getMediaPreviewDisplayPolicyUnderlyingReturnValue
} }
@@ -1399,9 +1468,9 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
} }
} }
} }
open var getMediaPreviewDisplayPolicyClosure: (() async throws -> MediaPreviews)? open var getMediaPreviewDisplayPolicyClosure: (() async throws -> MediaPreviews?)?
open override func getMediaPreviewDisplayPolicy() async throws -> MediaPreviews { open override func getMediaPreviewDisplayPolicy() async throws -> MediaPreviews? {
if let error = getMediaPreviewDisplayPolicyThrowableError { if let error = getMediaPreviewDisplayPolicyThrowableError {
throw error throw error
} }
+4
View File
@@ -30,6 +30,10 @@ extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
// MARK: ClientProxy // MARK: ClientProxy
extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? {
func onChange(mediaPreviewConfig: MediaPreviewConfig?) { onUpdateClosure(mediaPreviewConfig) }
}
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState { extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) } func onUpdate(state: SyncServiceState) { onUpdateClosure(state) }
} }
@@ -104,7 +104,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
} }
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$hideInviteAvatars userSession.clientProxy.hideInviteAvatarsPublisher
.removeDuplicates()
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.hideInviteAvatars, on: self) .weakAssign(to: \.state.hideInviteAvatars, on: self)
.store(in: &cancellables) .store(in: &cancellables)
@@ -41,7 +41,9 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), mediaProvider: mediaProvider) super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), mediaProvider: mediaProvider)
appSettings.$hideInviteAvatars clientProxy.hideInviteAvatarsPublisher
.removeDuplicates()
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.hideInviteAvatars, on: self) .weakAssign(to: \.state.hideInviteAvatars, on: self)
.store(in: &cancellables) .store(in: &cancellables)
@@ -363,9 +363,8 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview {
static func makeViewModel(mode: JoinRoomScreenMode, hideInviteAvatars: Bool = false) -> JoinRoomScreenViewModel { static func makeViewModel(mode: JoinRoomScreenMode, hideInviteAvatars: Bool = false) -> JoinRoomScreenViewModel {
let appSettings = AppSettings() let appSettings = AppSettings()
appSettings.knockingEnabled = true appSettings.knockingEnabled = true
appSettings.hideInviteAvatars = hideInviteAvatars
let clientProxy = ClientProxyMock(.init()) let clientProxy = ClientProxyMock(.init(hideInviteAvatars: hideInviteAvatars))
switch mode { switch mode {
case .unknown: case .unknown:
@@ -158,7 +158,8 @@ struct SecurityAndPrivacyScreen: View {
Section { Section {
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle), ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle),
details: .isWaiting(context.desiredSettings.isVisibileInRoomDirectory == nil), details:
context.desiredSettings.isVisibileInRoomDirectory == nil ? .isWaiting(true) : nil,
kind: context.desiredSettings.isVisibileInRoomDirectory == nil ? .label : .toggle(binding)) kind: context.desiredSettings.isVisibileInRoomDirectory == nil ? .label : .toggle(binding))
} footer: { } footer: {
Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName)) Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName))
@@ -11,13 +11,18 @@ import SwiftUI
struct AdvancedSettingsScreenCoordinatorParameters { struct AdvancedSettingsScreenCoordinatorParameters {
let appSettings: AppSettings let appSettings: AppSettings
let analytics: AnalyticsService let analytics: AnalyticsService
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
} }
final class AdvancedSettingsScreenCoordinator: CoordinatorProtocol { final class AdvancedSettingsScreenCoordinator: CoordinatorProtocol {
private var viewModel: AdvancedSettingsScreenViewModelProtocol private var viewModel: AdvancedSettingsScreenViewModelProtocol
init(parameters: AdvancedSettingsScreenCoordinatorParameters) { init(parameters: AdvancedSettingsScreenCoordinatorParameters) {
viewModel = AdvancedSettingsScreenViewModel(advancedSettings: parameters.appSettings, analytics: parameters.analytics) viewModel = AdvancedSettingsScreenViewModel(advancedSettings: parameters.appSettings,
analytics: parameters.analytics,
clientProxy: parameters.clientProxy,
userIndicatorController: parameters.userIndicatorController)
} }
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
@@ -8,6 +8,10 @@
import Foundation import Foundation
struct AdvancedSettingsScreenViewState: BindableState { struct AdvancedSettingsScreenViewState: BindableState {
var timelineMediaVisibility: TimelineMediaVisibility
var hideInviteAvatars: Bool
var isWaitingTimelineMediaVisibility = false
var isWaitingHideInviteAvatars = false
var bindings: AdvancedSettingsScreenViewStateBindings var bindings: AdvancedSettingsScreenViewStateBindings
} }
@@ -28,14 +32,14 @@ struct AdvancedSettingsScreenViewStateBindings {
enum AdvancedSettingsScreenViewAction { enum AdvancedSettingsScreenViewAction {
case optimizeMediaUploadsChanged case optimizeMediaUploadsChanged
case updateTimelineMediaVisibility(TimelineMediaVisibility)
case updateHideInviteAvatars(Bool)
} }
protocol AdvancedSettingsProtocol: AnyObject { protocol AdvancedSettingsProtocol: AnyObject {
var viewSourceEnabled: Bool { get set } var viewSourceEnabled: Bool { get set }
var appAppearance: AppAppearance { get set } var appAppearance: AppAppearance { get set }
var sharePresence: Bool { get set } var sharePresence: Bool { get set }
var timelineMediaVisibility: TimelineMediaVisibility { get set }
var hideInviteAvatars: Bool { get set }
var optimizeMediaUploads: Bool { get set } var optimizeMediaUploads: Bool { get set }
} }
@@ -12,12 +12,36 @@ typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2<AdvancedSe
class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol { class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol {
private let analytics: AnalyticsService private let analytics: AnalyticsService
private let clientProxy: ClientProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
init(advancedSettings: AdvancedSettingsProtocol, analytics: AnalyticsService) { private var timelineMediaVisibilityTask: Task<Void, Never>?
private var hideInviteAvatarsTask: Task<Void, Never>?
init(advancedSettings: AdvancedSettingsProtocol,
analytics: AnalyticsService,
clientProxy: ClientProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.analytics = analytics self.analytics = analytics
self.clientProxy = clientProxy
self.userIndicatorController = userIndicatorController
let state = AdvancedSettingsScreenViewState(bindings: .init(advancedSettings: advancedSettings)) let state = AdvancedSettingsScreenViewState(timelineMediaVisibility: clientProxy.timelineMediaVisibilityPublisher.value,
hideInviteAvatars: clientProxy.hideInviteAvatarsPublisher.value,
bindings: .init(advancedSettings: advancedSettings))
super.init(initialViewState: state) super.init(initialViewState: state)
clientProxy.timelineMediaVisibilityPublisher
.removeDuplicates()
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.timelineMediaVisibility, on: self)
.store(in: &cancellables)
clientProxy.hideInviteAvatarsPublisher
.removeDuplicates()
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.hideInviteAvatars, on: self)
.store(in: &cancellables)
} }
override func process(viewAction: AdvancedSettingsScreenViewAction) { override func process(viewAction: AdvancedSettingsScreenViewAction) {
@@ -25,6 +49,52 @@ class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, Adva
case .optimizeMediaUploadsChanged: case .optimizeMediaUploadsChanged:
// Note: Using a view action here as sinking the AppSettings publisher tracks the initial value. // Note: Using a view action here as sinking the AppSettings publisher tracks the initial value.
analytics.trackInteraction(name: state.bindings.optimizeMediaUploads ? .MobileSettingsOptimizeMediaUploadsEnabled : .MobileSettingsOptimizeMediaUploadsDisabled) analytics.trackInteraction(name: state.bindings.optimizeMediaUploads ? .MobileSettingsOptimizeMediaUploadsEnabled : .MobileSettingsOptimizeMediaUploadsDisabled)
case let .updateHideInviteAvatars(value):
hideInviteAvatarsTask = Task { [weak self] in await self?.updateHideInviteAvatars(value) }
case let .updateTimelineMediaVisibility(value):
timelineMediaVisibilityTask = Task { [weak self] in await self?.updateTimelineMediaVisibility(value) }
}
}
private func updateTimelineMediaVisibility(_ value: TimelineMediaVisibility) async {
defer {
timelineMediaVisibilityTask = nil
state.isWaitingTimelineMediaVisibility = false
}
let previousState = state.timelineMediaVisibility
state.isWaitingTimelineMediaVisibility = true
state.timelineMediaVisibility = value
// If the other value is updating wait also for it to finish
await hideInviteAvatarsTask?.value
switch await clientProxy.setTimelineMediaVisibility(value) {
case .success:
break
case .failure:
state.timelineMediaVisibility = previousState
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
}
}
private func updateHideInviteAvatars(_ value: Bool) async {
defer {
hideInviteAvatarsTask = nil
state.isWaitingHideInviteAvatars = false
}
let previousState = state.hideInviteAvatars
state.isWaitingHideInviteAvatars = true
state.hideInviteAvatars = value
// If the other value is updating wait also for it to finish
await timelineMediaVisibilityTask?.value
switch await clientProxy.setHideInviteAvatars(value) {
case .success:
break
case .failure:
state.hideInviteAvatars = previousState
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
} }
} }
} }
@@ -42,21 +42,39 @@ struct AdvancedSettingsScreen: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
@ViewBuilder
private var moderationAndSafetySection: some View { private var moderationAndSafetySection: some View {
let binding = Binding(get: {
context.viewState.hideInviteAvatars
}, set: { newValue in
context.send(viewAction: .updateHideInviteAvatars(newValue))
})
Section { Section {
ListRow(label: .plain(title: L10n.screenAdvancedSettingsHideInviteAvatarsToggleTitle), ListRow(label: .plain(title: L10n.screenAdvancedSettingsHideInviteAvatarsToggleTitle),
kind: .toggle($context.hideInviteAvatars)) details: context.viewState.isWaitingHideInviteAvatars ? .isWaiting(true) : nil,
kind: .toggle(binding))
.disabled(context.viewState.isWaitingHideInviteAvatars)
} header: { } header: {
Text(L10n.screenAdvancedSettingsModerationAndSafetySectionTitle) Text(L10n.screenAdvancedSettingsModerationAndSafetySectionTitle)
.compoundListSectionHeader() .compoundListSectionHeader()
} }
} }
@ViewBuilder
private var timelineMediaSection: some View { private var timelineMediaSection: some View {
let binding = Binding(get: {
context.viewState.timelineMediaVisibility
}, set: { newValue in
context.send(viewAction: .updateTimelineMediaVisibility(newValue))
})
Section { Section {
ListRow(label: .plain(title: L10n.screenAdvancedSettingsShowMediaTimelineTitle), ListRow(label: .plain(title: L10n.screenAdvancedSettingsShowMediaTimelineTitle),
kind: .inlinePicker(selection: $context.timelineMediaVisibility, details: .isWaiting(context.viewState.isWaitingTimelineMediaVisibility),
kind: .inlinePicker(selection: binding,
items: TimelineMediaVisibility.items)) items: TimelineMediaVisibility.items))
.disabled(context.viewState.isWaitingTimelineMediaVisibility)
} header: { } header: {
Text(L10n.screenAdvancedSettingsShowMediaTimelineTitle) Text(L10n.screenAdvancedSettingsShowMediaTimelineTitle)
.compoundListSectionHeader() .compoundListSectionHeader()
@@ -84,7 +102,9 @@ private extension AppAppearance {
struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview { struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings, static let viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics) analytics: ServiceLocator.shared.analytics,
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static var previews: some View { static var previews: some View {
NavigationStack { NavigationStack {
AdvancedSettingsScreen(context: viewModel.context) AdvancedSettingsScreen(context: viewModel.context)
@@ -88,7 +88,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
timelineControllerFactory: timelineControllerFactory, timelineControllerFactory: timelineControllerFactory,
clientProxy: clientProxy) clientProxy: clientProxy)
let hideTimelineMedia = switch appSettings.timelineMediaVisibility { let hideTimelineMedia = switch clientProxy.timelineMediaVisibilityPublisher.value {
case .always: case .always:
false false
case .privateOnly: case .privateOnly:
@@ -525,7 +525,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
.weakAssign(to: \.state.isViewSourceEnabled, on: self) .weakAssign(to: \.state.isViewSourceEnabled, on: self)
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$timelineMediaVisibility clientProxy.timelineMediaVisibilityPublisher
.removeDuplicates() .removeDuplicates()
.flatMap { [weak self] timelineMediaVisibility -> AnyPublisher<Bool, Never> in .flatMap { [weak self] timelineMediaVisibility -> AnyPublisher<Bool, Never> in
switch timelineMediaVisibility { switch timelineMediaVisibility {
@@ -538,10 +538,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
return roomProxy.infoPublisher return roomProxy.infoPublisher
.map { !$0.isPrivate } .map { !$0.isPrivate }
.removeDuplicates() .removeDuplicates()
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.hideTimelineMedia, on: self) .weakAssign(to: \.state.hideTimelineMedia, on: self)
.store(in: &cancellables) .store(in: &cancellables)
} }
@@ -39,6 +39,9 @@ class ClientProxy: ClientProxyProtocol {
// periphery:ignore - required for instance retention in the rust codebase // periphery:ignore - required for instance retention in the rust codebase
private var sendQueueListenerTaskHandle: TaskHandle? private var sendQueueListenerTaskHandle: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var mediaPreviewConfigListenerTaskHandle: TaskHandle?
private var delegateHandle: TaskHandle? private var delegateHandle: TaskHandle?
// These following summary providers both operate on the same allRooms() list but // These following summary providers both operate on the same allRooms() list but
@@ -132,6 +135,16 @@ class ClientProxy: ClientProxyProtocol {
verificationStateSubject.asCurrentValuePublisher() 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> = [] var roomsToAwait: Set<String> = []
private let sendQueueStatusSubject = CurrentValueSubject<Bool, Never>(false) private let sendQueueStatusSubject = CurrentValueSubject<Bool, Never>(false)
@@ -230,6 +243,10 @@ class ClientProxy: ClientProxyProtocol {
MXLog.error("Failed setting media retention policy with error: \(error)") MXLog.error("Failed setting media retention policy with error: \(error)")
} }
} }
Task {
mediaPreviewConfigListenerTaskHandle = await createMediaPreviewConfigObserver()
}
} }
var userID: String { var userID: String {
@@ -725,6 +742,16 @@ class ClientProxy: ClientProxyProtocol {
} }
} }
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 // MARK: Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> { func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
@@ -798,6 +825,28 @@ class ClientProxy: ClientProxyProtocol {
return users.elements 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 // MARK: - Private
private func cacheAccountURL() async { private func cacheAccountURL() async {
@@ -868,6 +917,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 { private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.state(listener: SDKListener { [weak self] state in roomListService.state(listener: SDKListener { [weak self] state in
guard let self else { return } guard let self else { return }
@@ -1133,3 +1202,38 @@ private struct ClientProxyServices {
self.roomListService = roomListService 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 case unverified
} }
// The `Decodable` conformance is just for the purpose of migration
enum TimelineMediaVisibility: Decodable {
case always
case privateOnly
case never
}
// sourcery: AutoMockable // sourcery: AutoMockable
protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var actionsPublisher: AnyPublisher<ClientProxyAction, Never> { get } 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 /// We delay fetching this until after the first sync. Nil until then
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get } var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get }
var timelineMediaVisibilityPublisher: CurrentValuePublisher<TimelineMediaVisibility, Never> { get }
var hideInviteAvatarsPublisher: CurrentValuePublisher<Bool, Never> { get }
var pusherNotificationClientIdentifier: String? { get } var pusherNotificationClientIdentifier: String? { get }
var roomSummaryProvider: RoomSummaryProviderProtocol { get } var roomSummaryProvider: RoomSummaryProviderProtocol { get }
@@ -186,6 +197,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
@discardableResult func clearCaches() async -> Result<Void, ClientProxyError> @discardableResult func clearCaches() async -> Result<Void, ClientProxyError>
func fetchMediaPreviewConfiguration() async -> Result<MediaPreviewConfig?, ClientProxyError>
// MARK: - Ignored users // MARK: - Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError>
@@ -210,4 +223,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError> func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
func userIdentity(for userID: String) async -> Result<UserIdentityProxyProtocol?, 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>
} }
+22
View File
@@ -19,6 +19,28 @@ final class NSEUserSession {
networkMonitor: nil) networkMonitor: nil)
private let delegateHandle: TaskHandle? private let delegateHandle: TaskHandle?
var mediaPreviewVisibility: MediaPreviews {
get async {
do {
return try await baseClient.getMediaPreviewDisplayPolicy() ?? .on
} catch {
MXLog.error("Failed to get media preview visibility, defaulting to on. Error: \(error)")
return .on
}
}
}
var inviteAvatarsVisibility: InviteAvatars {
get async {
do {
return try await baseClient.getInviteAvatarsDisplayPolicy() ?? .on
} catch {
MXLog.error("Failed to get invite avatars visibility, defaulting to on. Error: \(error)")
return .on
}
}
}
init(credentials: KeychainCredentials, init(credentials: KeychainCredentials,
roomID: String, roomID: String,
clientSessionDelegate: ClientSessionDelegate, clientSessionDelegate: ClientSessionDelegate,
+5 -4
View File
@@ -15,7 +15,7 @@ import Version
struct NotificationContentBuilder { struct NotificationContentBuilder {
let messageEventStringBuilder: RoomMessageEventStringBuilder let messageEventStringBuilder: RoomMessageEventStringBuilder
let settings: CommonSettingsProtocol let userSession: NSEUserSession
/// Process the given notification item proxy /// Process the given notification item proxy
/// - Parameters: /// - Parameters:
@@ -104,7 +104,7 @@ struct NotificationContentBuilder {
senderID: notificationItem.senderID, senderID: notificationItem.senderID,
senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName, senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName,
icon: icon(for: notificationItem), icon: icon(for: notificationItem),
forcePlaceholder: settings.hideInviteAvatars, forcePlaceholder: userSession.inviteAvatarsVisibility == .off,
mediaProvider: mediaProvider) mediaProvider: mediaProvider)
} }
@@ -146,8 +146,9 @@ struct NotificationContentBuilder {
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
notificationContent.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters) notificationContent.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters)
guard settings.timelineMediaVisibility == .always || let timelineMediaVisibility = await userSession.mediaPreviewVisibility
(settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate) guard timelineMediaVisibility == .on ||
(timelineMediaVisibility == .private && notificationItem.isRoomPrivate)
else { else {
return return
} }
+1 -1
View File
@@ -36,7 +36,7 @@ class NotificationHandler {
destination: .notification) destination: .notification)
notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: eventStringBuilder, notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: eventStringBuilder,
settings: settings) userSession: userSession)
} }
func processEvent(_ eventID: String, roomID: String) async { func processEvent(_ eventID: String, roomID: String) async {
+1 -1
View File
@@ -69,7 +69,7 @@ packages:
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
Compound: Compound:
url: https://github.com/element-hq/compound-ios url: https://github.com/element-hq/compound-ios
revision: 81ba8bd8b3971beac252129c5466d7eac2f2ec31 revision: 139d4f3d1ed19cc513686e5e39750ecef54c8403
# path: ../compound-ios # path: ../compound-ios
AnalyticsEvents: AnalyticsEvents:
url: https://github.com/matrix-org/matrix-analytics-events url: https://github.com/matrix-org/matrix-analytics-events