implemented the labs screen

This commit is contained in:
Mauro Romito
2025-10-03 18:19:03 +02:00
committed by Mauro
parent b110a7ba51
commit b58d4269f4
20 changed files with 257 additions and 12 deletions
@@ -102,6 +102,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
presentNotificationSettings()
case .advancedSettings:
presentAdvancedSettings()
case .labs:
presentLabs()
case .developerOptions:
presentDeveloperOptions()
case .deactivateAccount:
@@ -113,6 +115,11 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated)
}
private func presentLabs() {
let coordinator = LabsScreenCoordinator(parameters: .init(appSettings: flowParameters.appSettings))
navigationStackCoordinator.push(coordinator)
}
private func startEncryptionSettingsFlow(animated: Bool) {
let coordinator = EncryptionSettingsFlowCoordinator(parameters: .init(userSession: flowParameters.userSession,
appSettings: flowParameters.appSettings,
@@ -69,6 +69,7 @@ enum TestablePreviewsDictionary {
"KnockRequestsBannerView_Previews" : KnockRequestsBannerView_Previews.self,
"KnockRequestsListEmptyStateView_Previews" : KnockRequestsListEmptyStateView_Previews.self,
"KnockRequestsListScreen_Previews" : KnockRequestsListScreen_Previews.self,
"LabsScreen_Previews" : LabsScreen_Previews.self,
"LeaveSpaceView_Previews" : LeaveSpaceView_Previews.self,
"LegalInformationScreen_Previews" : LegalInformationScreen_Previews.self,
"LoadableImage_Previews" : LoadableImage_Previews.self,
@@ -0,0 +1,29 @@
//
// 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.
//
// periphery:ignore:all - this is just a labs remove this comment once generating the final file
import Combine
import SwiftUI
struct LabsScreenCoordinatorParameters {
let appSettings: AppSettings
}
final class LabsScreenCoordinator: CoordinatorProtocol {
private let viewModel: LabsScreenViewModelProtocol
init(parameters: LabsScreenCoordinatorParameters) {
viewModel = LabsScreenViewModel(labsOptions: parameters.appSettings)
}
func start() { }
func toPresentable() -> AnyView {
AnyView(LabsScreen(context: viewModel.context))
}
}
@@ -0,0 +1,37 @@
//
// 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 Foundation
enum LabsScreenViewAction { }
enum LabsScreenViewModelAction { }
struct LabsScreenViewState: BindableState {
var bindings: LabsScreenViewStateBindings
}
// periphery: ignore - subscripts are seen as false positive
@dynamicMemberLookup
struct LabsScreenViewStateBindings {
private let labsOptions: LabsOptionsProtocol
init(labsOptions: LabsOptionsProtocol) {
self.labsOptions = labsOptions
}
subscript<Setting>(dynamicMember keyPath: ReferenceWritableKeyPath<LabsOptionsProtocol, Setting>) -> Setting {
get { labsOptions[keyPath: keyPath] }
set { labsOptions[keyPath: keyPath] = newValue }
}
}
protocol LabsOptionsProtocol: AnyObject {
var threadsEnabled: Bool { get set }
}
extension AppSettings: LabsOptionsProtocol { }
@@ -0,0 +1,25 @@
//
// 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 Combine
import SwiftUI
typealias LabsScreenViewModelType = StateStoreViewModelV2<LabsScreenViewState, LabsScreenViewAction>
class LabsScreenViewModel: LabsScreenViewModelType, LabsScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<LabsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<LabsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(labsOptions: LabsOptionsProtocol) {
let bindings = LabsScreenViewStateBindings(labsOptions: labsOptions)
let state = LabsScreenViewState(bindings: bindings)
super.init(initialViewState: state)
}
}
@@ -0,0 +1,14 @@
//
// 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 Combine
@MainActor
protocol LabsScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<LabsScreenViewModelAction, Never> { get }
var context: LabsScreenViewModelType.Context { get }
}
@@ -0,0 +1,66 @@
//
// 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 Compound
import SwiftUI
struct LabsScreen: View {
@Bindable var context: LabsScreenViewModel.Context
var body: some View {
Form {
header
settingsSection
}
.compoundList()
.navigationTitle(L10n.screenLabsTitle)
.navigationBarTitleDisplayMode(.inline)
}
private var header: some View {
Section {
EmptyView()
} header: {
VStack(spacing: 16) {
BigIcon(icon: \.labs, style: .default)
VStack(spacing: 8) {
Text(L10n.screenLabsHeaderTitle)
.foregroundColor(.compound.textPrimary)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
Text(L10n.screenLabsHeaderDescription)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
}
.frame(maxWidth: .infinity)
}
}
private var settingsSection: some View {
Section {
ListRow(label: .default(title: L10n.screenLabsEnableThreads,
icon: \.threads),
kind: .toggle($context.threadsEnabled))
}
}
}
// MARK: - Previews
struct LabsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = LabsScreenViewModel(labsOptions: ServiceLocator.shared.settings)
static var previews: some View {
NavigationStack {
LabsScreen(context: viewModel.context)
}
}
}
@@ -41,7 +41,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
var enableOnlySignedDeviceIsolationMode: Bool { get set }
var enableKeyShareOnInvite: Bool { get set }
var threadsEnabled: Bool { get set }
var spacesEnabled: Bool { get set }
var hideQuietNotificationAlerts: Bool { get set }
@@ -33,10 +33,6 @@ struct DeveloperOptionsScreen: View {
}
Section("General") {
Toggle(isOn: $context.threadsEnabled) {
Text("Threads")
}
Toggle(isOn: $context.spacesEnabled) {
Text("Spaces")
}
@@ -27,6 +27,7 @@ enum SettingsScreenCoordinatorAction {
case manageAccount(url: URL)
case notifications
case advancedSettings
case labs
case developerOptions
case deactivateAccount
}
@@ -75,6 +76,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.notifications)
case .advancedSettings:
actionsSubject.send(.advancedSettings)
case .labs:
actionsSubject.send(.labs)
case .developerOptions:
actionsSubject.send(.developerOptions)
case .logout:
@@ -20,6 +20,7 @@ enum SettingsScreenViewModelAction: Equatable {
case secureBackup
case notifications
case advancedSettings
case labs
case developerOptions
case logout
case deactivateAccount
@@ -69,6 +70,7 @@ enum SettingsScreenViewAction {
case enableDeveloperOptions
case developerOptions
case advancedSettings
case labs
case logout
case deactivateAccount
}
@@ -112,6 +112,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
actionsSubject.send(.notifications)
case .advancedSettings:
actionsSubject.send(.advancedSettings)
case .labs:
actionsSubject.send(.labs)
case .enableDeveloperOptions:
appSettings.developerOptionsEnabled.toggle()
case .developerOptions:
@@ -137,6 +137,19 @@ struct SettingsScreen: View {
private var generalSection: some View {
Section {
ListRow(label: .default(title: L10n.commonAdvancedSettings,
icon: \.settings),
kind: .navigationLink {
context.send(viewAction: .advancedSettings)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.advancedSettings)
ListRow(label: .default(title: L10n.screenAdvancedSettingsLabs,
icon: \.labs),
kind: .navigationLink {
context.send(viewAction: .labs)
})
ListRow(label: .default(title: L10n.commonAbout,
icon: \.info),
kind: .navigationLink {
@@ -161,13 +174,6 @@ struct SettingsScreen: View {
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
}
ListRow(label: .default(title: L10n.commonAdvancedSettings,
icon: \.settings),
kind: .navigationLink {
context.send(viewAction: .advancedSettings)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.advancedSettings)
}
}