diff --git a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift index 391e864326..39cb9b4404 100644 --- a/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift +++ b/AccessibilityTests/Sources/GeneratedAccessibilityTests.swift @@ -247,6 +247,10 @@ extension AccessibilityTests { try await performAccessibilityAudit(named: "KnockRequestsListScreen_Previews") } + func testLabsScreen() async throws { + try await performAccessibilityAudit(named: "LabsScreen_Previews") + } + func testLeaveSpaceView() async throws { try await performAccessibilityAudit(named: "LeaveSpaceView_Previews") } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 373aa24f7b..13a0920b0d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -288,6 +288,7 @@ 32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; }; 32F47002A331817F0E6BD7EB /* RoomMembershipDetailsProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1434D5169F0EE319E226DA7F /* RoomMembershipDetailsProxyProtocol.swift */; }; 339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; }; + 33BA0964A308D2286B39976D /* LabsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C5AA3EF7EC67C01C75CEDD /* LabsScreen.swift */; }; 33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; 33F1FB19F222BA9930AB1A00 /* RoomListFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6372DD10DED30E7AD7BCE21 /* RoomListFiltersView.swift */; }; 340D39DB87F3800D53A6A621 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */; }; @@ -1117,6 +1118,7 @@ C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; }; C8E11A335456FCF94A744E6E /* SpaceFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */; }; C8E1E4E06B7C7A3A8246FC9B /* MediaEventsTimelineScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8512B82404B1751D0BCC82D2 /* MediaEventsTimelineScreenCoordinator.swift */; }; + C900127318820AD04D6C90B8 /* LabsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E43D8784B0054C048060FEB /* LabsScreenModels.swift */; }; C915347779B3C7FDD073A87A /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E1FF0DFBB3768F79FDBF6D /* AVMetadataMachineReadableCodeObjectExtensionsTest.swift */; }; C969A62F3D9F14318481A33B /* KnockedRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858DA81F2ACF484B7CAD6AE4 /* KnockedRoomProxy.swift */; }; C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; }; @@ -1160,10 +1162,12 @@ D104B27C5DA0626B41CE78D3 /* CurrentValuePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 127C8472672A5BA09EF1ACF8 /* CurrentValuePublisher.swift */; }; D10BA4F041DC58580A440A32 /* RoomRolesAndPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B1DC3B3FB40A7F4AE9B7BF /* RoomRolesAndPermissionsScreen.swift */; }; D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; }; + D150D6E96CA6CA09FA50E13C /* LabsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */; }; D18B70975644C24F60656C0D /* KnockRequestProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07851F4EA81AA3339806A7B /* KnockRequestProxyProtocol.swift */; }; D19A748E95E2FAB2940570F0 /* CallScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4103AB4340F2974D690A12A /* CallScreen.swift */; }; D2048FD56760BDABA3DB5FC2 /* AppLockServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAAB54C6CE91D64B69A9F8 /* AppLockServiceProtocol.swift */; }; D22345698F6548C1EE960940 /* IdentityConfirmedScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBE70FFB7936F35811772C1 /* IdentityConfirmedScreenModels.swift */; }; + D23BA23864EA7BA3F353C0D1 /* LabsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */; }; D2466C6BC8CAD8FADD7BF89B /* RoomPreviewProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6695C64F066628411EAD21E9 /* RoomPreviewProxyMock.swift */; }; D26093BB80B69092B0E9AC7C /* PinnedItemsIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66763BD54A3A1D9C6E6F2F1 /* PinnedItemsIndicatorView.swift */; }; D2825E013A8ECFB66D9A1DE6 /* RoomChangeRolesScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F841F219ACDFC1D3F42FEFB /* RoomChangeRolesScreenViewModelTests.swift */; }; @@ -1288,6 +1292,7 @@ EB9F4688006B52E69DF5358F /* BlankFormCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7F63EB1525E697CAEB002B /* BlankFormCoordinator.swift */; }; EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; EC09E502A21E4EAA8B367AB8 /* ReportContentScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EA681527A8FE65E4C8E9A9 /* ReportContentScreenViewModelTests.swift */; }; + EC1A0E85CEE50BF0C64EEFA5 /* LabsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */; }; EC280623A42904341363EAAF /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = A20EA00CCB9DBE0FFB17DD09 /* Collections */; }; EC3320639828BED8B3E5F2C6 /* EncryptionResetScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5875F7C0A2398E9F134B1284 /* EncryptionResetScreenViewModel.swift */; }; ED3E91E6166E4923791ACA84 /* ResolveVerifiedUserSendFailureScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56852036214ABA9D7D305768 /* ResolveVerifiedUserSendFailureScreenViewModelProtocol.swift */; }; @@ -1912,6 +1917,7 @@ 4C8D988E82A8DFA13BE46F7C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = pl; path = pl.lproj/Localizable.stringsdict; sourceTree = ""; }; 4CD6AC7546E8D7E5C73CEA48 /* ElementX.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4CDDDDD9FE1A699D23A5E096 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; + 4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenCoordinator.swift; sourceTree = ""; }; 4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarModels.swift; sourceTree = ""; }; 4D635709C1D6D37C225AD40E /* RoomPowerLevelProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelProxyProtocol.swift; sourceTree = ""; }; 4E2245243369B99216C7D84E /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; @@ -1991,6 +1997,7 @@ 5D53754227CEBD06358956D7 /* PinnedEventsTimelineScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineScreenCoordinator.swift; sourceTree = ""; }; 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProperties.swift; sourceTree = ""; }; 5E33FD32BBC44D703C7AE4F9 /* TextBasedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineItem.swift; sourceTree = ""; }; + 5E43D8784B0054C048060FEB /* LabsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenModels.swift; sourceTree = ""; }; 5E6DE144D887A254F4CAF203 /* UserPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreference.swift; sourceTree = ""; }; 5E75948AA1FE1D1A7809931F /* AuthenticationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProtocol.swift; sourceTree = ""; }; 5E9CBF577B9711CFBB4FA40D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = ""; }; @@ -2346,6 +2353,7 @@ A433BE28B40D418237BE37B5 /* ReportContentScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreen.swift; sourceTree = ""; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = ""; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; + A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModel.swift; sourceTree = ""; }; A54AAF72E821B4084B7E4298 /* PinnedEventsTimelineFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedEventsTimelineFlowCoordinator.swift; sourceTree = ""; }; A6B19D10B102956066AF117B /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = ""; }; A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; @@ -2576,6 +2584,7 @@ CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CEE20623EB4A9B88FB29F2BA /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/SAS.strings; sourceTree = ""; }; CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = ""; }; + CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreenViewModelProtocol.swift; sourceTree = ""; }; CFFA5E881D281810AB428EA3 /* RoomPowerLevelsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomPowerLevelsProxy.swift; sourceTree = ""; }; D01FD1171FF40E34D707FD00 /* BigIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigIcon.swift; sourceTree = ""; }; D03D7ECAC68C2FFB8CF01BCB /* DeactivateAccountScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeactivateAccountScreen.swift; sourceTree = ""; }; @@ -2631,6 +2640,7 @@ D93C94C30E3135BC9290DE13 /* VoiceMessageRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecorderTests.swift; sourceTree = ""; }; D95E8C0EFEC0C6F96EDAA71A /* PreviewTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = PreviewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D97A4E73EA97CA08D2BB9806 /* RoomScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenTests.swift; sourceTree = ""; }; + D9C5AA3EF7EC67C01C75CEDD /* LabsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabsScreen.swift; sourceTree = ""; }; DA14564EE143F73F7E4D1F79 /* RoomNotificationSettingsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenModels.swift; sourceTree = ""; }; DA3D82522494E78746B2214E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/SAS.strings; sourceTree = ""; }; DAB8D7926A5684E18196B538 /* VoiceMessageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageCache.swift; sourceTree = ""; }; @@ -3180,6 +3190,14 @@ path = Supplementary; sourceTree = ""; }; + 155D9F609E878C1875A366D4 /* View */ = { + isa = PBXGroup; + children = ( + D9C5AA3EF7EC67C01C75CEDD /* LabsScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 15D44FCA9475E660B7F56DB9 /* Timeline */ = { isa = PBXGroup; children = ( @@ -4093,6 +4111,18 @@ path = UnitTests; sourceTree = ""; }; + 4D963F50D7AA8FE302CA8ACF /* LabsScreen */ = { + isa = PBXGroup; + children = ( + 4CF17EFB2833B4CE5C06E7F8 /* LabsScreenCoordinator.swift */, + 5E43D8784B0054C048060FEB /* LabsScreenModels.swift */, + A4D9DF4F2DF3507F99B5B97B /* LabsScreenViewModel.swift */, + CF847A34FC4C8C937CD39E08 /* LabsScreenViewModelProtocol.swift */, + 155D9F609E878C1875A366D4 /* View */, + ); + path = LabsScreen; + sourceTree = ""; + }; 4DC0344D2EBD0AE5D71754A9 /* RoomMembershipDetails */ = { isa = PBXGroup; children = ( @@ -6184,6 +6214,7 @@ F12966DF3DA87FEF21348D60 /* InviteUsersScreen */, FFD7C58CA6A7D6BBC2F584B5 /* JoinRoomScreen */, BF0415BE807CA2BCFC210008 /* KnockRequestsListScreen */, + 4D963F50D7AA8FE302CA8ACF /* LabsScreen */, 948DD12A5533BE1BC260E437 /* LocationSharing */, 73E032ADD008D63812791D97 /* LogViewerScreen */, 3D76DA5827DF9396AC90E7B4 /* ManageRoomMemberSheet */, @@ -7840,6 +7871,11 @@ 454F8DDC4442C0DE54094902 /* LABiometryType.swift in Sources */, E468CC731C3F4D678499E52F /* LAContextMock.swift in Sources */, D5681C80D8281560AACE0035 /* Label.swift in Sources */, + 33BA0964A308D2286B39976D /* LabsScreen.swift in Sources */, + D150D6E96CA6CA09FA50E13C /* LabsScreenCoordinator.swift in Sources */, + C900127318820AD04D6C90B8 /* LabsScreenModels.swift in Sources */, + EC1A0E85CEE50BF0C64EEFA5 /* LabsScreenViewModel.swift in Sources */, + D23BA23864EA7BA3F353C0D1 /* LabsScreenViewModelProtocol.swift in Sources */, EEAE954289DE813A61656AE0 /* LayoutDirection.swift in Sources */, EFF735EC040BEB669AFBAB50 /* LeaveSpaceHandleProxy.swift in Sources */, DD21CE51DF9BD04FC8155972 /* LeaveSpaceHandleSDKMock.swift in Sources */, diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index e3ee9a343b..ab67fbf8bc 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -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, diff --git a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift index 65f4f20555..11f37b09a7 100644 --- a/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift +++ b/ElementX/Sources/Other/TestablePreview/TestablePreviewsDictionary.swift @@ -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, diff --git a/ElementX/Sources/Screens/LabsScreen/LabsScreenCoordinator.swift b/ElementX/Sources/Screens/LabsScreen/LabsScreenCoordinator.swift new file mode 100644 index 0000000000..b897d859bb --- /dev/null +++ b/ElementX/Sources/Screens/LabsScreen/LabsScreenCoordinator.swift @@ -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)) + } +} diff --git a/ElementX/Sources/Screens/LabsScreen/LabsScreenModels.swift b/ElementX/Sources/Screens/LabsScreen/LabsScreenModels.swift new file mode 100644 index 0000000000..037c6d6541 --- /dev/null +++ b/ElementX/Sources/Screens/LabsScreen/LabsScreenModels.swift @@ -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(dynamicMember keyPath: ReferenceWritableKeyPath) -> Setting { + get { labsOptions[keyPath: keyPath] } + set { labsOptions[keyPath: keyPath] = newValue } + } +} + +protocol LabsOptionsProtocol: AnyObject { + var threadsEnabled: Bool { get set } +} + +extension AppSettings: LabsOptionsProtocol { } diff --git a/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModel.swift b/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModel.swift new file mode 100644 index 0000000000..224d55108c --- /dev/null +++ b/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModel.swift @@ -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 + +class LabsScreenViewModel: LabsScreenViewModelType, LabsScreenViewModelProtocol { + private let actionsSubject: PassthroughSubject = .init() + var actionsPublisher: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(labsOptions: LabsOptionsProtocol) { + let bindings = LabsScreenViewStateBindings(labsOptions: labsOptions) + let state = LabsScreenViewState(bindings: bindings) + + super.init(initialViewState: state) + } +} diff --git a/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModelProtocol.swift new file mode 100644 index 0000000000..465c99ae7e --- /dev/null +++ b/ElementX/Sources/Screens/LabsScreen/LabsScreenViewModelProtocol.swift @@ -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 { get } + var context: LabsScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/LabsScreen/View/LabsScreen.swift b/ElementX/Sources/Screens/LabsScreen/View/LabsScreen.swift new file mode 100644 index 0000000000..0187ab888c --- /dev/null +++ b/ElementX/Sources/Screens/LabsScreen/View/LabsScreen.swift @@ -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) + } + } +} diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index bdf91b26e7..68562e8583 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -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 } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 3e1134da1a..9e419ff875 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -33,10 +33,6 @@ struct DeveloperOptionsScreen: View { } Section("General") { - Toggle(isOn: $context.threadsEnabled) { - Text("Threads") - } - Toggle(isOn: $context.spacesEnabled) { Text("Spaces") } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift index ee3bf47da7..20e9764921 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenCoordinator.swift @@ -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: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift index 28a28350fd..8a2193fee1 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenModels.swift @@ -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 } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index e1f3cd01d7..082cb66ab6 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -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: diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift index 24263be45f..181e77deeb 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/View/SettingsScreen.swift @@ -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) } } diff --git a/PreviewTests/Sources/GeneratedPreviewTests.swift b/PreviewTests/Sources/GeneratedPreviewTests.swift index 8a21cdbdfb..ce8474f485 100644 --- a/PreviewTests/Sources/GeneratedPreviewTests.swift +++ b/PreviewTests/Sources/GeneratedPreviewTests.swift @@ -371,6 +371,12 @@ extension PreviewTests { } } + func testLabsScreen() async throws { + for (index, preview) in LabsScreen_Previews._allPreviews.enumerated() { + try await assertSnapshots(matching: preview, step: index) + } + } + func testLeaveSpaceView() async throws { for (index, preview) in LeaveSpaceView_Previews._allPreviews.enumerated() { try await assertSnapshots(matching: preview, step: index) diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-en-GB-0.png new file mode 100644 index 0000000000..44d8ba3fab --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2170efb3086308662f9dca90475254c16123f856ff295c4fe9cadb308310dc6 +size 109823 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png new file mode 100644 index 0000000000..1f55073768 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPad-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe61566d43980722e18dde794154e6682dc52aae39c594c870c3da479914c412 +size 122330 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-en-GB-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-en-GB-0.png new file mode 100644 index 0000000000..08bf82da1f --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-en-GB-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9ff0be2476921e4c18a3ff885a00c8470b1d1b0005b48ac88832dc49a3f1a01 +size 65214 diff --git a/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-pseudo-0.png b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-pseudo-0.png new file mode 100644 index 0000000000..3e954013a3 --- /dev/null +++ b/PreviewTests/Sources/__Snapshots__/PreviewTests/labsScreen.iPhone-16-pseudo-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:210fdd8adf9279c35daa8992275ad436f64a96a3138687d1d6fdee38e13642e9 +size 90346