mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Adopt StateStoreViewModelV2 in the remaining settings screens. (#4158)
* Move the AuthenticationStartScreen into the Authentication directory.
* Commit the updated Sentry license.
No idea why they dropped the 2024 🤷♂️
* Use StateStoreViewModelV2 in BugReportScreen.
* Use StateStoreViewModelV2 in UserDetailsEditScreen.
* Use StateStoreViewModelV2 in NotificationSettingsScreen.
* Use StateStoreViewModelV2 in NotificationSettingsEditScreen.
* Use StateStoreViewModelV2 in LegalInformationScreen.
* Use StateStoreViewModelV2 in LogViewerScreen.
* Use StateStoreViewModelV2 in AnalyticsSettingsScreen.
* Rename AdvancedSettingsScreen directory.
* Use StateStoreViewModelV2 in EncryptionResetScreen.
* Use StateStoreViewModelV2 in EncryptionResetPasswordScreen.
* Use StateStoreViewModelV2 in SecureBackup…Screens.
* Use StateStoreViewModelV2 in LoginScreen.
Seems this one was ignored waiting on the fulfillment transitionValues implementation.
* Use StateStoreViewModelV2 in DeactivateAccountScreen.
* Move DeactivateAccountScreen into the Settings directory.
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
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,
|
||||
clientProxy: parameters.clientProxy,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(AdvancedSettingsScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AdvancedSettingsScreenViewState: BindableState {
|
||||
var timelineMediaVisibility: TimelineMediaVisibility
|
||||
var hideInviteAvatars: Bool
|
||||
var isWaitingTimelineMediaVisibility = false
|
||||
var isWaitingHideInviteAvatars = false
|
||||
var bindings: AdvancedSettingsScreenViewStateBindings
|
||||
}
|
||||
|
||||
// periphery:ignore - subscript are seen as false positives
|
||||
@dynamicMemberLookup
|
||||
struct AdvancedSettingsScreenViewStateBindings {
|
||||
private let advancedSettings: AdvancedSettingsProtocol
|
||||
|
||||
init(advancedSettings: AdvancedSettingsProtocol) {
|
||||
self.advancedSettings = advancedSettings
|
||||
}
|
||||
|
||||
subscript<Setting>(dynamicMember keyPath: ReferenceWritableKeyPath<AdvancedSettingsProtocol, Setting>) -> Setting {
|
||||
get { advancedSettings[keyPath: keyPath] }
|
||||
set { advancedSettings[keyPath: keyPath] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
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 optimizeMediaUploads: Bool { get set }
|
||||
}
|
||||
|
||||
extension AppSettings: AdvancedSettingsProtocol { }
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2<AdvancedSettingsScreenViewState, AdvancedSettingsScreenViewAction>
|
||||
|
||||
class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol {
|
||||
private let analytics: AnalyticsService
|
||||
private let clientProxy: ClientProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
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(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) {
|
||||
switch viewAction {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol AdvancedSettingsScreenViewModelProtocol {
|
||||
var context: AdvancedSettingsScreenViewModelType.Context { get }
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct AdvancedSettingsScreen: View {
|
||||
@Bindable var context: AdvancedSettingsScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.commonAppearance),
|
||||
kind: .picker(selection: $context.appAppearance,
|
||||
items: AppAppearance.allCases.map { (title: $0.name, tag: $0) }))
|
||||
|
||||
ListRow(label: .plain(title: L10n.actionViewSource,
|
||||
description: L10n.screenAdvancedSettingsViewSourceDescription),
|
||||
kind: .toggle($context.viewSourceEnabled))
|
||||
|
||||
ListRow(label: .plain(title: L10n.screenAdvancedSettingsSharePresence,
|
||||
description: L10n.screenAdvancedSettingsSharePresenceDescription),
|
||||
kind: .toggle($context.sharePresence))
|
||||
|
||||
ListRow(label: .plain(title: L10n.screenAdvancedSettingsMediaCompressionTitle,
|
||||
description: L10n.screenAdvancedSettingsMediaCompressionDescription),
|
||||
kind: .toggle($context.optimizeMediaUploads))
|
||||
.onChange(of: context.optimizeMediaUploads) {
|
||||
context.send(viewAction: .optimizeMediaUploadsChanged)
|
||||
}
|
||||
}
|
||||
|
||||
moderationAndSafetySection
|
||||
timelineMediaSection
|
||||
}
|
||||
.compoundList()
|
||||
.navigationTitle(L10n.commonAdvancedSettings)
|
||||
.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),
|
||||
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),
|
||||
details: .isWaiting(context.viewState.isWaitingTimelineMediaVisibility),
|
||||
kind: .inlinePicker(selection: binding,
|
||||
items: TimelineMediaVisibility.items))
|
||||
.disabled(context.viewState.isWaitingTimelineMediaVisibility)
|
||||
} header: {
|
||||
Text(L10n.screenAdvancedSettingsShowMediaTimelineTitle)
|
||||
.compoundListSectionHeader()
|
||||
} footer: {
|
||||
Text(L10n.screenAdvancedSettingsShowMediaTimelineSubtitle)
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AppAppearance {
|
||||
var name: String {
|
||||
switch self {
|
||||
case .system:
|
||||
return L10n.commonSystem
|
||||
case .light:
|
||||
return L10n.commonLight
|
||||
case .dark:
|
||||
return L10n.commonDark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
clientProxy: ClientProxyMock(.init()),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
AdvancedSettingsScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineMediaVisibility {
|
||||
static var items: [(title: String, tag: TimelineMediaVisibility)] {
|
||||
[(title: L10n.screenAdvancedSettingsShowMediaTimelineAlwaysShow, tag: .always),
|
||||
(title: L10n.screenAdvancedSettingsShowMediaTimelinePrivateRooms, tag: .privateOnly),
|
||||
(title: L10n.screenAdvancedSettingsShowMediaTimelineAlwaysHide, tag: .never)]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user