// // 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 SwiftUI struct DeveloperOptionsScreen: View { @ObservedObject 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 { 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("Room List") { Toggle(isOn: $context.publicSearchEnabled) { Text("Public search") } Toggle(isOn: $context.hideUnreadMessagesBadge) { Text("Hide grey dots") } Toggle(isOn: $context.fuzzyRoomListSearchEnabled) { Text("Fuzzy searching") } } Section("Join rules") { Toggle(isOn: $context.knockingEnabled) { Text("Knocking") Text("Ask to join rooms") } } 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("Reporting") { Toggle(isOn: $context.reportRoomEnabled) { Text("Report rooms") Text("Report API might not work properly") } Toggle(isOn: $context.reportInviteEnabled) { Text("Report invites") Text("Report API might not work properly") } } 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 } } } header: { Text("Element Call remote URL override") } Section { Button { showConfetti = true } label: { Text("🥳") .frame(maxWidth: .infinity) .alignmentGuide(.listRowSeparatorLeading) { _ in 0 } // Fix separator alignment } Button { fatalError("This crash is a test.") } label: { Text("💥") .frame(maxWidth: .infinity) } } Section { Button(role: .destructive) { context.send(viewAction: .clearCache) } label: { Text("Clear cache") .frame(maxWidth: .infinity) } } } .overlay(effectsView) .compoundList() .navigationTitle(L10n.commonDeveloperOptions) .navigationBarTitleDisplayMode(.inline) } @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 } } 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 { /// 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: ServiceLocator.shared.settings, elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL) static var previews: some View { NavigationStack { DeveloperOptionsScreen(context: viewModel.context) } } }