Fixes #322 - Developer options menu (#581)

This commit is contained in:
Stefan Ceriu
2023-02-15 13:07:06 +02:00
committed by GitHub
parent 6211f121a0
commit 4fd24a6c16
21 changed files with 390 additions and 7 deletions
@@ -0,0 +1,29 @@
//
// 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
final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol {
private let viewModel: DeveloperOptionsScreenViewModelProtocol
init() {
viewModel = DeveloperOptionsScreenViewModel()
}
func toPresentable() -> AnyView {
AnyView(DeveloperOptionsScreenScreen(context: viewModel.context))
}
}
@@ -0,0 +1,27 @@
//
// 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 DeveloperOptionsScreenViewModelAction { }
struct DeveloperOptionsScreenViewState: BindableState {
var bindings: DeveloperOptionsScreenViewStateBindings
}
struct DeveloperOptionsScreenViewStateBindings { }
enum DeveloperOptionsScreenViewAction { }
@@ -0,0 +1,27 @@
//
// 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 DeveloperOptionsScreenViewModelType = StateStoreViewModel<DeveloperOptionsScreenViewState, DeveloperOptionsScreenViewAction>
class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, DeveloperOptionsScreenViewModelProtocol {
var callback: ((DeveloperOptionsScreenViewModelAction) -> Void)?
init() {
super.init(initialViewState: DeveloperOptionsScreenViewState(bindings: DeveloperOptionsScreenViewStateBindings()))
}
}
@@ -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 DeveloperOptionsScreenViewModelProtocol {
var callback: ((DeveloperOptionsScreenViewModelAction) -> Void)? { get set }
var context: DeveloperOptionsScreenViewModelType.Context { get }
}
@@ -0,0 +1,64 @@
//
// 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
struct DeveloperOptionsScreenScreen: View {
@ObservedObject var context: DeveloperOptionsScreenViewModel.Context
@State private var showConfetti = false
var body: some View {
Form {
Section {
Button {
showConfetti = true
} label: {
Text("🥳")
.frame(maxWidth: .infinity)
}
}
.listRowBackground(Color.element.formRowBackground)
}
.overlay(effectsView)
.scrollContentBackground(.hidden)
.background(Color.element.formBackground.ignoresSafeArea())
.navigationTitle(ElementL10n.settingsDeveloperOptions)
.navigationBarTitleDisplayMode(.inline)
}
@ViewBuilder
private var effectsView: some View {
if showConfetti {
EffectsView(effect: .confetti)
.task { await removeConfettiAfterDelay() }
}
}
private func removeConfettiAfterDelay() async {
try? await Task.sleep(for: .seconds(4))
showConfetti = false
}
}
// MARK: - Previews
struct DeveloperOptionsScreen_Previews: PreviewProvider {
static var previews: some View {
let viewModel = DeveloperOptionsScreenViewModel()
DeveloperOptionsScreenScreen(context: viewModel.context)
.tint(.element.accent)
}
}
@@ -88,7 +88,7 @@ struct RoomScreenViewStateBindings {
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<RoomScreenErrorType>?
var debugInfo: DebugScreen.DebugInfo?
var debugInfo: TimelineItemDebugView.DebugInfo?
}
enum RoomScreenErrorType: Hashable {
@@ -229,7 +229,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
actions.append(.redact)
}
var debugActions: [TimelineItemContextMenuAction] = [.viewSource]
var debugActions: [TimelineItemContextMenuAction] = ServiceLocator.shared.settings.canShowDeveloperOptions ? [.viewSource] : []
if let item = timelineItem as? EncryptedRoomTimelineItem,
case let .megolmV1AesSha2(sessionID) = item.encryptionType {
@@ -30,7 +30,7 @@ struct RoomScreen: View {
.toolbarBackground(.visible, for: .navigationBar) // Fix the toolbar's background.
.overlay { loadingIndicator }
.alert(item: $context.alertInfo) { $0.alert }
.sheet(item: $context.debugInfo) { DebugScreen(info: $0) }
.sheet(item: $context.debugInfo) { TimelineItemDebugView(info: $0) }
.task(id: context.viewState.roomId) {
// Give a couple of seconds for items to load and to see them.
try? await Task.sleep(for: .seconds(2))
@@ -16,7 +16,7 @@
import SwiftUI
struct DebugScreen: View {
struct TimelineItemDebugView: View {
struct DebugInfo: Identifiable {
let id = UUID()
let title: String
@@ -52,6 +52,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
self.presentBugReportScreen()
case .sessionVerification:
self.verifySession()
case .developerOptions:
self.presentDeveloperOptions()
case .logout:
self.callback?(.logout)
}
@@ -110,6 +112,11 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
self?.parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
}
}
private func presentDeveloperOptions() {
let coordinator = DeveloperOptionsScreenCoordinator()
parameters.navigationStackCoordinator?.push(coordinator)
}
private func showSuccess(label: String) {
parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label))
@@ -22,6 +22,7 @@ enum SettingsScreenViewModelAction {
case toggleAnalytics
case reportBug
case sessionVerification
case developerOptions
case logout
}
@@ -32,6 +33,7 @@ struct SettingsScreenViewState: BindableState {
var userAvatarURL: URL?
var userDisplayName: String?
var showSessionVerificationSection: Bool
var showDeveloperOptions: Bool
}
struct SettingsScreenViewStateBindings {
@@ -45,4 +47,5 @@ enum SettingsScreenViewAction {
case sessionVerification
case logout
case changedTimelineStyle
case developerOptions
}
@@ -23,14 +23,15 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
private let userSession: UserSessionProtocol
var callback: ((SettingsScreenViewModelAction) -> Void)?
init(withUserSession userSession: UserSessionProtocol) {
self.userSession = userSession
let bindings = SettingsScreenViewStateBindings(timelineStyle: ServiceLocator.shared.settings.timelineStyle)
super.init(initialViewState: .init(bindings: bindings,
deviceID: userSession.deviceId,
userID: userSession.userID,
showSessionVerificationSection: !(userSession.sessionVerificationController?.isVerified ?? false)),
showSessionVerificationSection: !(userSession.sessionVerificationController?.isVerified ?? false),
showDeveloperOptions: ServiceLocator.shared.settings.canShowDeveloperOptions),
imageProvider: userSession.mediaProvider)
ServiceLocator.shared.settings.$timelineStyle
@@ -78,6 +79,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
callback?(.sessionVerification)
case .changedTimelineStyle:
ServiceLocator.shared.settings.timelineStyle = state.bindings.timelineStyle
case .developerOptions:
callback?(.developerOptions)
}
}
}
@@ -18,7 +18,6 @@ import SwiftUI
struct SettingsScreen: View {
@State private var showingLogoutConfirmation = false
@Environment(\.colorScheme) private var colorScheme
@ScaledMetric private var avatarSize = AvatarSize.user(on: .settings).value
@ScaledMetric private var menuIconSize = 30.0
@@ -40,6 +39,11 @@ struct SettingsScreen: View {
simplifiedSection
.listRowBackground(Color.element.formRowBackground)
if context.viewState.showDeveloperOptions {
developerOptionsSection
.listRowBackground(Color.element.formRowBackground)
}
signOutSection
.listRowBackground(Color.element.formRowBackground)
}
@@ -89,6 +93,16 @@ struct SettingsScreen: View {
}
}
private var developerOptionsSection: some View {
Section {
SettingsDefaultRow(title: ElementL10n.settingsDeveloperOptions,
image: Image(systemName: "hammer.circle")) {
context.send(viewAction: .developerOptions)
}
.accessibilityIdentifier("sessionVerificationButton")
}
}
private var simplifiedSection: some View {
Section {
SettingsPickerRow(title: ElementL10n.settingsTimelineStyle,