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
@@ -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 {
@@ -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 }
}
@@ -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))
}
}
}
@@ -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)