mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
2e7d78ea01
Automatic back pagination is now always enabled when building the client, so the setting and its developer options toggle have been removed.
274 lines
10 KiB
Swift
274 lines
10 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2022-2025 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 SwiftUI
|
|
|
|
struct DeveloperOptionsScreen: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var showMarkAllRoomsAsReadAlert = false
|
|
|
|
@Bindable var context: DeveloperOptionsScreenViewModel.Context
|
|
|
|
@State private var showConfetti = false
|
|
@State private var elementCallURLOverrideString: String
|
|
|
|
init(context: DeveloperOptionsScreenViewModel.Context) {
|
|
self.context = context
|
|
elementCallURLOverrideString = context.elementCallBaseURLOverride?.absoluteString ?? ""
|
|
}
|
|
|
|
var body: some View {
|
|
Form {
|
|
if let storeSizes = context.viewState.storeSizes {
|
|
Section("Usage") {
|
|
ForEach(storeSizes) { storeSize in
|
|
LabeledContent(storeSize.name, value: storeSize.size)
|
|
}
|
|
}
|
|
}
|
|
|
|
Section("Logging") {
|
|
LogLevelConfigurationView(logLevel: $context.logLevel)
|
|
|
|
DisclosureGroup("SDK trace packs") {
|
|
ForEach(TraceLogPack.allCases, id: \.self) { pack in
|
|
Toggle(isOn: $context.traceLogPacks[pack]) {
|
|
Text(pack.title)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Section("General") {
|
|
Toggle(isOn: $context.linkNewDeviceEnabled) {
|
|
Text("Link new device with QR code")
|
|
}
|
|
|
|
Toggle(isOn: $context.globalSearchEnabled) {
|
|
Text("Global search")
|
|
Text("Moves search to a separate tab")
|
|
}
|
|
|
|
context.viewState.appHooks
|
|
.developerOptionsScreenHook
|
|
.generalSectionRows(isSignedIn: context.viewState.isSignedIn)
|
|
}
|
|
|
|
Section("Room List") {
|
|
Picker("Room list activity visibility", selection: $context.roomListActivityVisibility) {
|
|
ForEach(RoomListActivityVisibility.allCases, id: \.self) { visibility in
|
|
Text(visibility.rawValue.capitalized)
|
|
.tag(visibility)
|
|
}
|
|
}
|
|
|
|
Toggle(isOn: $context.roomListNotificationCountEnabled) {
|
|
Text("Show unread notification count")
|
|
Text("Also makes the app icon badge use the SDK's own unread notification count")
|
|
}
|
|
|
|
Toggle(isOn: $context.fuzzyRoomListSearchEnabled) {
|
|
Text("Fuzzy searching")
|
|
}
|
|
|
|
Toggle(isOn: $context.lowPriorityFilterEnabled) {
|
|
Text("Low priority filter")
|
|
}
|
|
|
|
Toggle(isOn: $context.mentionsFilterEnabled) {
|
|
Text("Mentions filter")
|
|
}
|
|
}
|
|
|
|
Section("Room") {
|
|
Toggle(isOn: $context.linkPreviewsEnabled) {
|
|
Text("Link previews")
|
|
Text("Follows the timeline media visibility settings.")
|
|
Text("Can leak the device IP address when loading link metadata.")
|
|
.foregroundStyle(.compound.textCriticalPrimary)
|
|
}
|
|
|
|
Toggle(isOn: $context.jumpToReadMarkerEnabled) {
|
|
Text("Jump to unread")
|
|
Text("Adds a button to jump to the read marker, plus a presence dot on the scroll-to-bottom button when new messages arrive while scrolled away.")
|
|
}
|
|
|
|
Toggle(isOn: $context.messageMultiSelectEnabled) {
|
|
Text("Multi-select messages")
|
|
Text("Adds a Select action to the message menu to pick several messages at once.")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
Toggle(isOn: $context.enableOnlySignedDeviceIsolationMode) {
|
|
Text("Exclude insecure devices when sending/receiving messages")
|
|
Text("Requires app reboot")
|
|
}
|
|
} header: {
|
|
Text("Trust and Decoration")
|
|
} footer: {
|
|
Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.")
|
|
}
|
|
|
|
Section {
|
|
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
|
|
.autocorrectionDisabled(true)
|
|
.autocapitalization(.none)
|
|
.foregroundColor(URL(string: elementCallURLOverrideString) == nil ? .red : .primary)
|
|
.submitLabel(.done)
|
|
.onSubmit {
|
|
if elementCallURLOverrideString.isEmpty {
|
|
context.elementCallBaseURLOverride = nil
|
|
} else if let url = URL(string: elementCallURLOverrideString) {
|
|
context.elementCallBaseURLOverride = url
|
|
}
|
|
}
|
|
Toggle(isOn: $context.nativeCallEnabled) {
|
|
Text("Experimental native calls")
|
|
}
|
|
} header: {
|
|
Text("Calls")
|
|
} footer: {
|
|
Text("The URL override only applies to calls run in the Element Call web view, which native calls replace.")
|
|
}
|
|
|
|
Section("Notifications") {
|
|
Toggle(isOn: $context.hideQuietNotificationAlerts) {
|
|
Text("Hide quiet alerts")
|
|
Text("The badge count will still be updated")
|
|
}
|
|
|
|
Toggle(isOn: $context.focusEventOnNotificationTap) {
|
|
Text("Focus event on notification tap")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
Button {
|
|
showMarkAllRoomsAsReadAlert = true
|
|
} label: {
|
|
Text("Mark all rooms as read")
|
|
}.alert("Are you sure you want to mark all the rooms as read?", isPresented: $showMarkAllRoomsAsReadAlert) {
|
|
Button("Cancel", role: .cancel) { }
|
|
|
|
Button("Yes") {
|
|
context.send(viewAction: .markAllRoomsAsRead)
|
|
}
|
|
}
|
|
} footer: {
|
|
Text("""
|
|
This will send a private read receipt and a read marker in every room you are part of. \
|
|
It's a long running operation that might get rate limited. \
|
|
It will run in the background but the app must be alive for it to finish.
|
|
""")
|
|
}
|
|
|
|
Section {
|
|
Button {
|
|
showConfetti = true
|
|
} label: {
|
|
Text("🥳")
|
|
.frame(maxWidth: .infinity)
|
|
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 } // Fix separator alignment
|
|
}
|
|
}
|
|
|
|
if context.viewState.shouldShowClearCache {
|
|
Section {
|
|
Button(role: .destructive) {
|
|
context.send(viewAction: .clearCache)
|
|
} label: {
|
|
Text("Clear cache")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.overlay(effectsView)
|
|
.navigationTitle(L10n.commonDeveloperOptions)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar { toolbar }
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var effectsView: some View {
|
|
if showConfetti {
|
|
EffectsView(effect: .confetti)
|
|
.ignoresSafeArea()
|
|
.allowsHitTesting(false)
|
|
.task { await removeConfettiAfterDelay() }
|
|
}
|
|
}
|
|
|
|
private func removeConfettiAfterDelay() async {
|
|
try? await Task.sleep(for: .seconds(4))
|
|
showConfetti = false
|
|
}
|
|
|
|
@ToolbarContentBuilder
|
|
private var toolbar: some ToolbarContent {
|
|
if !context.viewState.isSignedIn {
|
|
ToolbarItem(placement: .primaryAction) {
|
|
if #available(iOS 26.0, *) {
|
|
Button(role: .close, action: dismiss.callAsFunction)
|
|
} else {
|
|
Button(L10n.actionDone, action: dismiss.callAsFunction)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct LogLevelConfigurationView: View {
|
|
@Binding var logLevel: LogLevel
|
|
|
|
var body: some View {
|
|
Picker(selection: $logLevel) {
|
|
ForEach(logLevels, id: \.self) { logLevel in
|
|
Text(logLevel.title)
|
|
}
|
|
} label: {
|
|
Text("Log level")
|
|
Text("Requires app reboot")
|
|
}
|
|
}
|
|
|
|
/// Allows the picker to work with associated values
|
|
private var logLevels: [LogLevel] {
|
|
[.error, .warn, .info, .debug, .trace]
|
|
}
|
|
}
|
|
|
|
private extension Set<TraceLogPack> {
|
|
/// A custom subscript that allows binding a toggle to add/remove a pack from the array.
|
|
subscript(pack: TraceLogPack) -> Bool {
|
|
get { contains(pack) }
|
|
set {
|
|
if newValue {
|
|
insert(pack)
|
|
} else {
|
|
remove(pack)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
struct DeveloperOptionsScreen_Previews: PreviewProvider {
|
|
static let viewModel = DeveloperOptionsScreenViewModel(developerOptions: AppSettings.volatile(),
|
|
appHooks: AppHooks(),
|
|
clientProxy: ClientProxyMock(.init()))
|
|
|
|
static var previews: some View {
|
|
ElementNavigationStack {
|
|
DeveloperOptionsScreen(context: viewModel.context)
|
|
}
|
|
}
|
|
}
|