mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
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:
@@ -8836,7 +8836,7 @@
|
||||
repositoryURL = "https://github.com/element-hq/compound-ios";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = 81ba8bd8b3971beac252129c5466d7eac2f2ec31;
|
||||
revision = 139d4f3d1ed19cc513686e5e39750ecef54c8403;
|
||||
};
|
||||
};
|
||||
F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/element-hq/compound-ios",
|
||||
"state" : {
|
||||
"revision" : "81ba8bd8b3971beac252129c5466d7eac2f2ec31"
|
||||
"revision" : "139d4f3d1ed19cc513686e5e39750ecef54c8403"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -203,8 +203,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
|
||||
"state" : {
|
||||
"revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a",
|
||||
"version" : "5.3.0"
|
||||
"revision" : "3dd282d3269b061853a3b3bcd23a509d2aa166ce",
|
||||
"version" : "6.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,11 +30,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
private var userSession: UserSessionProtocol? {
|
||||
didSet {
|
||||
userSessionObserver?.cancel()
|
||||
if userSession != nil {
|
||||
if let userSession {
|
||||
configureElementCallService()
|
||||
configureNotificationManager()
|
||||
observeUserSessionChanges()
|
||||
startSync()
|
||||
performSettingsToAccountDataMigration(userSession: userSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,17 +400,54 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
Tracing.deleteLogFiles()
|
||||
MXLog.info("Migrating to v1.6.7, log files have been wiped")
|
||||
}
|
||||
}
|
||||
|
||||
// This could be removed once the adotpion of 25.06.x is widespread.
|
||||
private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) {
|
||||
guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else {
|
||||
return
|
||||
}
|
||||
|
||||
if oldVersion < Version(25, 4, 2) {
|
||||
MXLog.info("Migrating to v25.04.2, checking if hideTimelineMedia flag can be migrated to timelineMediaVisibility")
|
||||
// Migration for the old `hideTimelineMedia` flag.
|
||||
if let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier),
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Clears the keychain, app support directory etc ready for a fresh use.
|
||||
/// - Parameter includingSettings: Whether to additionally wipe the user's app settings too.
|
||||
private func wipeUserData(includingSettings: Bool = false) {
|
||||
|
||||
@@ -17,8 +17,6 @@ protocol CommonSettingsProtocol {
|
||||
var logLevel: LogLevel { get }
|
||||
var traceLogPacks: Set<TraceLogPack> { get }
|
||||
var enableOnlySignedDeviceIsolationMode: Bool { get }
|
||||
var hideInviteAvatars: Bool { get }
|
||||
var timelineMediaVisibility: TimelineMediaVisibility { get }
|
||||
var hideQuietNotificationAlerts: Bool { get }
|
||||
}
|
||||
|
||||
@@ -46,8 +44,6 @@ final class AppSettings {
|
||||
case optimizeMediaUploads
|
||||
case appAppearance
|
||||
case sharePresence
|
||||
case hideInviteAvatars
|
||||
case timelineMediaVisibility
|
||||
case isNewBloomEnabled
|
||||
|
||||
case elementCallBaseURLOverride
|
||||
@@ -368,20 +364,8 @@ final class AppSettings {
|
||||
@UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store))
|
||||
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))
|
||||
var hideQuietNotificationAlerts
|
||||
}
|
||||
|
||||
extension AppSettings: CommonSettingsProtocol { }
|
||||
|
||||
enum TimelineMediaVisibility: Codable {
|
||||
case always
|
||||
case privateOnly
|
||||
case never
|
||||
}
|
||||
|
||||
@@ -226,7 +226,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
private func presentAdvancedSettings() {
|
||||
let coordinator = AdvancedSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings,
|
||||
analytics: parameters.analytics))
|
||||
analytics: parameters.analytics,
|
||||
clientProxy: parameters.userSession.clientProxy,
|
||||
userIndicatorController: parameters.userIndicatorController))
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ struct ClientProxyMockConfiguration {
|
||||
var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol?
|
||||
|
||||
var recoveryState: SecureBackupRecoveryState = .enabled
|
||||
|
||||
var timelineMediaVisibility = TimelineMediaVisibility.always
|
||||
var hideInviteAvatars = false
|
||||
}
|
||||
|
||||
enum ClientProxyMockError: Error {
|
||||
@@ -94,5 +97,8 @@ extension ClientProxyMock {
|
||||
userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init()))
|
||||
|
||||
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 }
|
||||
}
|
||||
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 roomSummaryProvider: RoomSummaryProviderProtocol {
|
||||
get { return underlyingRoomSummaryProvider }
|
||||
@@ -4363,6 +4373,70 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
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
|
||||
|
||||
var ignoreUserUnderlyingCallsCount = 0
|
||||
@@ -5103,6 +5177,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
open var getDmRoomUserIdThrowableError: Error?
|
||||
@@ -1156,13 +1225,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
|
||||
return getInviteAvatarsDisplayPolicyCallsCount > 0
|
||||
}
|
||||
|
||||
var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars!
|
||||
open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars! {
|
||||
var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars?
|
||||
open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars? {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return getInviteAvatarsDisplayPolicyUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: InviteAvatars? = nil
|
||||
var returnValue: InviteAvatars?? = nil
|
||||
DispatchQueue.main.sync {
|
||||
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 {
|
||||
throw error
|
||||
}
|
||||
@@ -1375,13 +1444,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable {
|
||||
return getMediaPreviewDisplayPolicyCallsCount > 0
|
||||
}
|
||||
|
||||
var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews!
|
||||
open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews! {
|
||||
var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews?
|
||||
open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews? {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return getMediaPreviewDisplayPolicyUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: MediaPreviews? = nil
|
||||
var returnValue: MediaPreviews?? = nil
|
||||
DispatchQueue.main.sync {
|
||||
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 {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
|
||||
|
||||
// MARK: ClientProxy
|
||||
|
||||
extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? {
|
||||
func onChange(mediaPreviewConfig: MediaPreviewConfig?) { onUpdateClosure(mediaPreviewConfig) }
|
||||
}
|
||||
|
||||
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
|
||||
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) }
|
||||
}
|
||||
|
||||
@@ -104,7 +104,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
appSettings.$hideInviteAvatars
|
||||
userSession.clientProxy.hideInviteAvatarsPublisher
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.hideInviteAvatars, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
|
||||
|
||||
super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), mediaProvider: mediaProvider)
|
||||
|
||||
appSettings.$hideInviteAvatars
|
||||
clientProxy.hideInviteAvatarsPublisher
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.hideInviteAvatars, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
@@ -363,9 +363,8 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static func makeViewModel(mode: JoinRoomScreenMode, hideInviteAvatars: Bool = false) -> JoinRoomScreenViewModel {
|
||||
let appSettings = AppSettings()
|
||||
appSettings.knockingEnabled = true
|
||||
appSettings.hideInviteAvatars = hideInviteAvatars
|
||||
|
||||
let clientProxy = ClientProxyMock(.init())
|
||||
let clientProxy = ClientProxyMock(.init(hideInviteAvatars: hideInviteAvatars))
|
||||
|
||||
switch mode {
|
||||
case .unknown:
|
||||
|
||||
+2
-1
@@ -158,7 +158,8 @@ struct SecurityAndPrivacyScreen: View {
|
||||
|
||||
Section {
|
||||
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))
|
||||
} footer: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName))
|
||||
|
||||
+6
-1
@@ -11,13 +11,18 @@ import SwiftUI
|
||||
struct AdvancedSettingsScreenCoordinatorParameters {
|
||||
let appSettings: AppSettings
|
||||
let analytics: AnalyticsService
|
||||
let clientProxy: ClientProxyProtocol
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
final class AdvancedSettingsScreenCoordinator: CoordinatorProtocol {
|
||||
private var viewModel: AdvancedSettingsScreenViewModelProtocol
|
||||
|
||||
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 {
|
||||
|
||||
+6
-2
@@ -8,6 +8,10 @@
|
||||
import Foundation
|
||||
|
||||
struct AdvancedSettingsScreenViewState: BindableState {
|
||||
var timelineMediaVisibility: TimelineMediaVisibility
|
||||
var hideInviteAvatars: Bool
|
||||
var isWaitingTimelineMediaVisibility = false
|
||||
var isWaitingHideInviteAvatars = false
|
||||
var bindings: AdvancedSettingsScreenViewStateBindings
|
||||
}
|
||||
|
||||
@@ -28,14 +32,14 @@ struct AdvancedSettingsScreenViewStateBindings {
|
||||
|
||||
enum AdvancedSettingsScreenViewAction {
|
||||
case optimizeMediaUploadsChanged
|
||||
case updateTimelineMediaVisibility(TimelineMediaVisibility)
|
||||
case updateHideInviteAvatars(Bool)
|
||||
}
|
||||
|
||||
protocol AdvancedSettingsProtocol: AnyObject {
|
||||
var viewSourceEnabled: Bool { get set }
|
||||
var appAppearance: AppAppearance { get set }
|
||||
var sharePresence: Bool { get set }
|
||||
var timelineMediaVisibility: TimelineMediaVisibility { get set }
|
||||
var hideInviteAvatars: Bool { get set }
|
||||
var optimizeMediaUploads: Bool { get set }
|
||||
}
|
||||
|
||||
|
||||
+72
-2
@@ -12,12 +12,36 @@ typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2<AdvancedSe
|
||||
|
||||
class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol {
|
||||
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.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)
|
||||
|
||||
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) {
|
||||
@@ -25,6 +49,52 @@ class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, Adva
|
||||
case .optimizeMediaUploadsChanged:
|
||||
// Note: Using a view action here as sinking the AppSettings publisher tracks the initial value.
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-3
@@ -42,21 +42,39 @@ struct AdvancedSettingsScreen: View {
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var moderationAndSafetySection: some View {
|
||||
let binding = Binding(get: {
|
||||
context.viewState.hideInviteAvatars
|
||||
}, set: { newValue in
|
||||
context.send(viewAction: .updateHideInviteAvatars(newValue))
|
||||
})
|
||||
|
||||
Section {
|
||||
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: {
|
||||
Text(L10n.screenAdvancedSettingsModerationAndSafetySectionTitle)
|
||||
.compoundListSectionHeader()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var timelineMediaSection: some View {
|
||||
let binding = Binding(get: {
|
||||
context.viewState.timelineMediaVisibility
|
||||
}, set: { newValue in
|
||||
context.send(viewAction: .updateTimelineMediaVisibility(newValue))
|
||||
})
|
||||
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.screenAdvancedSettingsShowMediaTimelineTitle),
|
||||
kind: .inlinePicker(selection: $context.timelineMediaVisibility,
|
||||
details: .isWaiting(context.viewState.isWaitingTimelineMediaVisibility),
|
||||
kind: .inlinePicker(selection: binding,
|
||||
items: TimelineMediaVisibility.items))
|
||||
.disabled(context.viewState.isWaitingTimelineMediaVisibility)
|
||||
} header: {
|
||||
Text(L10n.screenAdvancedSettingsShowMediaTimelineTitle)
|
||||
.compoundListSectionHeader()
|
||||
@@ -84,7 +102,9 @@ private extension AppAppearance {
|
||||
|
||||
struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
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 {
|
||||
NavigationStack {
|
||||
AdvancedSettingsScreen(context: viewModel.context)
|
||||
|
||||
@@ -88,7 +88,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
clientProxy: clientProxy)
|
||||
|
||||
let hideTimelineMedia = switch appSettings.timelineMediaVisibility {
|
||||
let hideTimelineMedia = switch clientProxy.timelineMediaVisibilityPublisher.value {
|
||||
case .always:
|
||||
false
|
||||
case .privateOnly:
|
||||
@@ -525,7 +525,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
.weakAssign(to: \.state.isViewSourceEnabled, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
appSettings.$timelineMediaVisibility
|
||||
clientProxy.timelineMediaVisibilityPublisher
|
||||
.removeDuplicates()
|
||||
.flatMap { [weak self] timelineMediaVisibility -> AnyPublisher<Bool, Never> in
|
||||
switch timelineMediaVisibility {
|
||||
@@ -538,10 +538,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
return roomProxy.infoPublisher
|
||||
.map { !$0.isPrivate }
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.hideTimelineMedia, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -18,6 +18,28 @@ final class NSEUserSession {
|
||||
imageCache: .onlyOnDisk,
|
||||
networkMonitor: nil)
|
||||
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,
|
||||
roomID: String,
|
||||
|
||||
@@ -15,7 +15,7 @@ import Version
|
||||
|
||||
struct NotificationContentBuilder {
|
||||
let messageEventStringBuilder: RoomMessageEventStringBuilder
|
||||
let settings: CommonSettingsProtocol
|
||||
let userSession: NSEUserSession
|
||||
|
||||
/// Process the given notification item proxy
|
||||
/// - Parameters:
|
||||
@@ -104,7 +104,7 @@ struct NotificationContentBuilder {
|
||||
senderID: notificationItem.senderID,
|
||||
senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName,
|
||||
icon: icon(for: notificationItem),
|
||||
forcePlaceholder: settings.hideInviteAvatars,
|
||||
forcePlaceholder: userSession.inviteAvatarsVisibility == .off,
|
||||
mediaProvider: mediaProvider)
|
||||
}
|
||||
|
||||
@@ -146,8 +146,9 @@ struct NotificationContentBuilder {
|
||||
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
||||
notificationContent.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters)
|
||||
|
||||
guard settings.timelineMediaVisibility == .always ||
|
||||
(settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate)
|
||||
let timelineMediaVisibility = await userSession.mediaPreviewVisibility
|
||||
guard timelineMediaVisibility == .on ||
|
||||
(timelineMediaVisibility == .private && notificationItem.isRoomPrivate)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class NotificationHandler {
|
||||
destination: .notification)
|
||||
|
||||
notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: eventStringBuilder,
|
||||
settings: settings)
|
||||
userSession: userSession)
|
||||
}
|
||||
|
||||
func processEvent(_ eventID: String, roomID: String) async {
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ packages:
|
||||
# path: ../matrix-rust-sdk
|
||||
Compound:
|
||||
url: https://github.com/element-hq/compound-ios
|
||||
revision: 81ba8bd8b3971beac252129c5466d7eac2f2ec31
|
||||
revision: 139d4f3d1ed19cc513686e5e39750ecef54c8403
|
||||
# path: ../compound-ios
|
||||
AnalyticsEvents:
|
||||
url: https://github.com/matrix-org/matrix-analytics-events
|
||||
|
||||
Reference in New Issue
Block a user