Fixes #1699 - Introduce a new advanced settings screen (#1700)

This commit is contained in:
Stefan Ceriu
2023-09-14 08:37:42 +03:00
committed by GitHub
parent 21856e994a
commit 761d508ae5
18 changed files with 309 additions and 75 deletions
@@ -31,6 +31,7 @@ final class AppSettings {
case pusherProfileTag
case logLevel
case otlpTracingEnabled
case viewSourceEnabled
// Feature flags
case shouldCollapseRoomStateEvents
@@ -188,6 +189,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.shouldCollapseRoomStateEvents, defaultValue: true, storageType: .volatile)
var shouldCollapseRoomStateEvents
@UserPreference(key: UserDefaultsKeys.viewSourceEnabled, defaultValue: false, storageType: .userDefaults(store))
var viewSourceEnabled
// MARK: - Notifications
@UserPreference(key: UserDefaultsKeys.enableNotifications, defaultValue: true, storageType: .userDefaults(store))
+4
View File
@@ -150,6 +150,8 @@ public enum L10n {
public static var commonAbout: String { return L10n.tr("Localizable", "common_about") }
/// Acceptable use policy
public static var commonAcceptableUsePolicy: String { return L10n.tr("Localizable", "common_acceptable_use_policy") }
/// Advanced settings
public static var commonAdvancedSettings: String { return L10n.tr("Localizable", "common_advanced_settings") }
/// Analytics
public static var commonAnalytics: String { return L10n.tr("Localizable", "common_analytics") }
/// Audio
@@ -248,6 +250,8 @@ public enum L10n {
public static var commonReportABug: String { return L10n.tr("Localizable", "common_report_a_bug") }
/// Report submitted
public static var commonReportSubmitted: String { return L10n.tr("Localizable", "common_report_submitted") }
/// Rich text editor
public static var commonRichTextEditor: String { return L10n.tr("Localizable", "common_rich_text_editor") }
/// Room name
public static var commonRoomName: String { return L10n.tr("Localizable", "common_room_name") }
/// e.g. your project name
@@ -174,6 +174,7 @@ struct A11yIdentifiers {
let analytics = "settings-analytics"
let reportBug = "settings-report_bug"
let about = "settings_about"
let advancedSettings = "settings_advanced-settings"
let developerOptions = "settings_developer-options"
let logout = "settings-logout"
}
@@ -501,7 +501,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
return nil
}
var debugActions: [TimelineItemMenuAction] = appSettings.canShowDeveloperOptions ? [.viewSource] : []
var debugActions: [TimelineItemMenuAction] = []
if appSettings.canShowDeveloperOptions || appSettings.viewSourceEnabled {
debugActions.append(.viewSource)
}
if let encryptedItem = timelineItem as? EncryptedRoomTimelineItem {
switch encryptedItem.encryptionType {
@@ -0,0 +1,35 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
enum AdvancedSettingsScreenCoordinatorAction {
case clearCache
}
final class AdvancedSettingsScreenCoordinator: CoordinatorProtocol {
private var viewModel: AdvancedSettingsScreenViewModelProtocol
var callback: ((AdvancedSettingsScreenCoordinatorAction) -> Void)?
init() {
viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings)
}
func toPresentable() -> AnyView {
AnyView(AdvancedSettingsScreen(context: viewModel.context))
}
}
@@ -0,0 +1,47 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
enum AdvancedSettingsScreenViewModelAction { }
struct AdvancedSettingsScreenViewState: BindableState {
var bindings: AdvancedSettingsScreenViewStateBindings
}
@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 { }
protocol AdvancedSettingsProtocol: AnyObject {
var timelineStyle: TimelineStyle { get set }
var viewSourceEnabled: Bool { get set }
var richTextEditorEnabled: Bool { get set }
}
extension AppSettings: AdvancedSettingsProtocol { }
@@ -0,0 +1,32 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
typealias AdvancedSettingsScreenViewModelType = StateStoreViewModel<AdvancedSettingsScreenViewState, AdvancedSettingsScreenViewAction>
class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, AdvancedSettingsScreenViewModelProtocol {
var callback: ((AdvancedSettingsScreenViewModelAction) -> Void)?
init(advancedSettings: AdvancedSettingsProtocol) {
let bindings = AdvancedSettingsScreenViewStateBindings(advancedSettings: advancedSettings)
let state = AdvancedSettingsScreenViewState(bindings: bindings)
super.init(initialViewState: state)
}
override func process(viewAction: AdvancedSettingsScreenViewAction) { }
}
@@ -0,0 +1,23 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@MainActor
protocol AdvancedSettingsScreenViewModelProtocol {
var callback: ((AdvancedSettingsScreenViewModelAction) -> Void)? { get set }
var context: AdvancedSettingsScreenViewModelType.Context { get }
}
@@ -0,0 +1,65 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Compound
import SwiftUI
struct AdvancedSettingsScreen: View {
@ObservedObject var context: AdvancedSettingsScreenViewModel.Context
@State private var showConfetti = false
var body: some View {
Form {
Section {
ListRow(label: .default(title: L10n.commonMessageLayout,
systemIcon: .rectangleGrid1x2),
kind: .picker(selection: $context.timelineStyle,
items: TimelineStyle.allCases.map { (title: $0.name, tag: $0) }))
ListRow(label: .default(title: L10n.actionViewSource, systemIcon: .mailAndTextMagnifyingglass),
kind: .toggle($context.viewSourceEnabled))
ListRow(label: .default(title: L10n.commonRichTextEditor, icon: Image(asset: Asset.Images.textFormat)),
kind: .toggle($context.richTextEditorEnabled))
}
}
.compoundList()
.navigationTitle(L10n.commonAdvancedSettings)
.navigationBarTitleDisplayMode(.inline)
}
}
private extension TimelineStyle {
var name: String {
switch self {
case .plain:
return L10n.commonModern
case .bubbles:
return L10n.commonBubbles
}
}
}
// MARK: - Previews
struct AdvancedSettingsScreen_Previews: PreviewProvider {
static let viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings)
static var previews: some View {
NavigationStack {
AdvancedSettingsScreen(context: viewModel.context)
}
}
}
@@ -49,7 +49,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
var userSuggestionsEnabled: Bool { get set }
var readReceiptsEnabled: Bool { get set }
var swiftUITimelineEnabled: Bool { get set }
var richTextEditorEnabled: Bool { get set }
}
extension AppSettings: DeveloperOptionsProtocol { }
@@ -53,12 +53,6 @@ struct DeveloperOptionsScreen: View {
}
}
Section("Rich Text Editor") {
Toggle(isOn: $context.richTextEditorEnabled) {
Text("Use the Rich Text Editor")
}
}
Section {
Button {
showConfetti = true
@@ -86,7 +80,7 @@ struct DeveloperOptionsScreen: View {
}
}
.overlay(effectsView)
.compoundForm()
.compoundList()
.navigationTitle(L10n.commonDeveloperOptions)
.navigationBarTitleDisplayMode(.inline)
}
@@ -61,12 +61,14 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
verifySession()
case .accountSessionsList:
presentAccountSessionsListURL()
case .notifications:
presentNotificationSettings()
case .advancedSettings:
self.presentAdvancedSettings()
case .developerOptions:
presentDeveloperOptions()
case .logout:
callback?(.logout)
case .notifications:
presentNotificationSettings()
}
}
}
@@ -159,6 +161,21 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
}
}
private func presentNotificationSettings() {
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: parameters.navigationStackCoordinator,
userSession: parameters.userSession,
userNotificationCenter: UNUserNotificationCenter.current(),
notificationSettings: parameters.notificationSettings,
isModallyPresented: false)
let coordinator = NotificationSettingsScreenCoordinator(parameters: notificationParameters)
parameters.navigationStackCoordinator?.push(coordinator)
}
private func presentAdvancedSettings() {
let coordinator = AdvancedSettingsScreenCoordinator()
parameters.navigationStackCoordinator?.push(coordinator)
}
private func presentDeveloperOptions() {
let coordinator = DeveloperOptionsScreenCoordinator()
@@ -175,14 +192,4 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
private func showSuccess(label: String) {
parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label))
}
private func presentNotificationSettings() {
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(navigationStackCoordinator: parameters.navigationStackCoordinator,
userSession: parameters.userSession,
userNotificationCenter: UNUserNotificationCenter.current(),
notificationSettings: parameters.notificationSettings,
isModallyPresented: false)
let coordinator = NotificationSettingsScreenCoordinator(parameters: notificationParameters)
parameters.navigationStackCoordinator?.push(coordinator)
}
}
@@ -25,13 +25,13 @@ enum SettingsScreenViewModelAction {
case about
case sessionVerification
case accountSessionsList
case developerOptions
case notifications
case advancedSettings
case developerOptions
case logout
}
struct SettingsScreenViewState: BindableState {
var bindings: SettingsScreenViewStateBindings
var deviceID: String?
var userID: String
var accountProfileURL: URL?
@@ -45,10 +45,6 @@ struct SettingsScreenViewState: BindableState {
var window: UIWindow?
}
struct SettingsScreenViewStateBindings {
var timelineStyle: TimelineStyle
}
enum SettingsScreenViewAction {
case close
case accountProfile
@@ -56,11 +52,11 @@ enum SettingsScreenViewAction {
case reportBug
case about
case sessionVerification
case logout
case changedTimelineStyle
case accountSessionsList
case developerOptions
case notifications
case developerOptions
case advancedSettings
case logout
/// Updates the window used for the OIDC account URL anchor.
case updateWindow(UIWindow)
@@ -29,15 +29,12 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
self.userSession = userSession
self.appSettings = appSettings
let bindings = SettingsScreenViewStateBindings(timelineStyle: appSettings.timelineStyle)
var showSessionVerificationSection = false
if let sessionVerificationController = userSession.sessionVerificationController {
showSessionVerificationSection = !sessionVerificationController.isVerified
}
super.init(initialViewState: .init(bindings: bindings,
deviceID: userSession.deviceID,
super.init(initialViewState: .init(deviceID: userSession.deviceID,
userID: userSession.userID,
accountProfileURL: userSession.clientProxy.accountURL(action: .profile),
accountSessionsListURL: userSession.clientProxy.accountURL(action: .sessionsList),
@@ -45,10 +42,6 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
showDeveloperOptions: appSettings.canShowDeveloperOptions),
imageProvider: userSession.mediaProvider)
appSettings.$timelineStyle
.weakAssign(to: \.state.bindings.timelineStyle, on: self)
.store(in: &cancellables)
userSession.clientProxy.avatarURLPublisher
.weakAssign(to: \.state.userAvatarURL, on: self)
.store(in: &cancellables)
@@ -94,12 +87,12 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
callback?(.logout)
case .sessionVerification:
callback?(.sessionVerification)
case .changedTimelineStyle:
appSettings.timelineStyle = state.bindings.timelineStyle
case .notifications:
callback?(.notifications)
case .accountSessionsList:
callback?(.accountSessionsList)
case .advancedSettings:
callback?(.advancedSettings)
case .developerOptions:
callback?(.developerOptions)
@@ -37,9 +37,7 @@ struct SettingsScreen: View {
manageSessionsSection
}
if context.viewState.showDeveloperOptions {
developerOptionsSection
}
advancedOptionsSection
signOutSection
}
@@ -89,7 +87,6 @@ struct SettingsScreen: View {
private var mainSection: some View {
Section {
// Account
if context.viewState.accountProfileURL != nil {
ListRow(label: .default(title: L10n.actionManageAccount, systemIcon: .person),
kind: .button {
@@ -98,17 +95,6 @@ struct SettingsScreen: View {
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.account)
}
// Message layout
ListRow(label: .default(title: L10n.commonMessageLayout,
systemIcon: .rectangleGrid1x2),
kind: .picker(selection: $context.timelineStyle,
items: TimelineStyle.allCases.map { (title: $0.name, tag: $0) }))
.accessibilityIdentifier("timelineStylePicker")
.onChange(of: context.timelineStyle) { _ in
context.send(viewAction: .changedTimelineStyle)
}
// Notifications
ListRow(label: .default(title: L10n.screenNotificationSettingsTitle,
systemIcon: .bell),
kind: .navigationLink {
@@ -116,7 +102,6 @@ struct SettingsScreen: View {
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.notifications)
// Analytics
ListRow(label: .default(title: L10n.commonAnalytics,
systemIcon: .chartBar),
kind: .navigationLink {
@@ -124,7 +109,6 @@ struct SettingsScreen: View {
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
// Report Bug
ListRow(label: .default(title: L10n.commonReportABug,
systemIcon: .ladybug),
kind: .navigationLink {
@@ -132,7 +116,6 @@ struct SettingsScreen: View {
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.reportBug)
// About
ListRow(label: .default(title: L10n.commonAbout,
systemIcon: .questionmarkCircle),
kind: .navigationLink {
@@ -161,14 +144,23 @@ struct SettingsScreen: View {
}
}
private var developerOptionsSection: some View {
private var advancedOptionsSection: some View {
Section {
ListRow(label: .default(title: L10n.commonDeveloperOptions,
systemIcon: .hammerCircle),
ListRow(label: .default(title: L10n.commonAdvancedSettings,
systemIcon: .gearshape2),
kind: .navigationLink {
context.send(viewAction: .developerOptions)
context.send(viewAction: .advancedSettings)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.advancedSettings)
if context.viewState.showDeveloperOptions {
ListRow(label: .default(title: L10n.commonDeveloperOptions,
systemIcon: .hammerCircle),
kind: .navigationLink {
context.send(viewAction: .developerOptions)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
}
}
}