Files
letro-ios/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift
Doug 682f91a2ff Add the new bloom style under a feature flag. (#4033)
* Refactor the bloom into a modifier.

It currently depends on the home screen context but that will be removed in the future.

* Add an initial implementation of the new bloom using an image on the navigation item's appearance.

* Add a feature flag to control the new bloom.
2025-04-16 16:36:19 +01:00

198 lines
6.6 KiB
Swift

//
// 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("General") {
Toggle(isOn: $context.threadsEnabled) {
Text("Threads")
}
}
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")
}
Toggle(isOn: $context.isNewBloomEnabled) {
Text("New bloom appearance")
Text("Requires app reboot")
}
}
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<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: ServiceLocator.shared.settings,
elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL)
static var previews: some View {
NavigationStack {
DeveloperOptionsScreen(context: viewModel.context)
}
}
}