mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+35
@@ -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))
|
||||
}
|
||||
}
|
||||
+47
@@ -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 { }
|
||||
+32
@@ -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) { }
|
||||
}
|
||||
+23
@@ -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 }
|
||||
}
|
||||
+65
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -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 { }
|
||||
|
||||
+1
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user