mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
implemented the navigation to the manage auth spaces screen
# Conflicts: # ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift # ElementX/Sources/FlowCoordinators/SpaceSettingsFlowCoordinator.swift # ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenCoordinator.swift # ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift
This commit is contained in:
@@ -472,6 +472,12 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.roomDetails, .presentSecurityAndPrivacyScreen, .securityAndPrivacy):
|
||||
presentSecurityAndPrivacyScreen()
|
||||
|
||||
case (.securityAndPrivacy, .presentManageAuthorizedSpacesScreen, .manageAuthorizedSpacesScreen):
|
||||
guard let selection = (context.userInfo as? EventUserInfo)?.authorizedSpacesSelection else {
|
||||
fatalError("Missing required AuthorizedSpacesSelection")
|
||||
}
|
||||
presentManageAuthorizedSpacesScreen(selection: selection)
|
||||
|
||||
case (.roomDetails, .presentReportRoomScreen, .reportRoom):
|
||||
presentReportRoom()
|
||||
|
||||
@@ -1357,6 +1363,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentEditAddressScreen()
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.pop()
|
||||
case .displayManageAuthorizedSpacesScreen(let selection):
|
||||
stateMachine.tryEvent(.presentManageAuthorizedSpacesScreen, userInfo: EventUserInfo(animated: true, authorizedSpacesSelection: selection))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -1366,6 +1374,25 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentManageAuthorizedSpacesScreen(selection: AuthorizedSpacesSelection) {
|
||||
let navigationStack = NavigationStackCoordinator()
|
||||
let coordinator = ManageAuthorizedSpacesScreenCoordinator(parameters: .init(authorizedSpacesSelection: selection,
|
||||
mediaProvider: flowParameters.userSession.mediaProvider))
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStack.setRootCoordinator(coordinator)
|
||||
navigationStackCoordinator.setSheetCoordinator(navigationStack) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedManageAuthorizedSpacesScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentEditAddressScreen() {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = EditRoomAddressScreenCoordinator(parameters: .init(roomProxy: roomProxy,
|
||||
|
||||
@@ -80,6 +80,7 @@ extension RoomFlowCoordinator {
|
||||
case knockRequestsList(previousState: State)
|
||||
case mediaEventsTimeline(previousState: State)
|
||||
case securityAndPrivacy(previousState: State)
|
||||
case manageAuthorizedSpacesScreen(previousState: State)
|
||||
case reportRoom(previousState: State)
|
||||
case declineAndBlockScreen
|
||||
|
||||
@@ -98,6 +99,7 @@ extension RoomFlowCoordinator {
|
||||
let animated: Bool
|
||||
var timelineController: TimelineControllerProtocol?
|
||||
var spaceRoomListProxy: SpaceRoomListProxyProtocol?
|
||||
var authorizedSpacesSelection: AuthorizedSpacesSelection?
|
||||
}
|
||||
|
||||
enum Event: EventType {
|
||||
@@ -174,6 +176,9 @@ extension RoomFlowCoordinator {
|
||||
case presentSecurityAndPrivacyScreen
|
||||
case dismissSecurityAndPrivacyScreen
|
||||
|
||||
case presentManageAuthorizedSpacesScreen
|
||||
case dismissedManageAuthorizedSpacesScreen
|
||||
|
||||
case presentReportRoomScreen
|
||||
case dismissReportRoomScreen
|
||||
|
||||
@@ -321,6 +326,11 @@ extension RoomFlowCoordinator {
|
||||
case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen):
|
||||
return previousState
|
||||
|
||||
case (.securityAndPrivacy, .presentManageAuthorizedSpacesScreen):
|
||||
return .manageAuthorizedSpacesScreen(previousState: fromState)
|
||||
case (.manageAuthorizedSpacesScreen(let previousState), .dismissedManageAuthorizedSpacesScreen):
|
||||
return previousState
|
||||
|
||||
case (.roomDetails, .presentReportRoomScreen):
|
||||
return .reportRoom(previousState: fromState)
|
||||
case (.reportRoom(let previousState), .dismissReportRoomScreen):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
@@ -33,6 +32,8 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case rolesAndPermissionsFlow
|
||||
/// The members flow screen
|
||||
case membersFlow
|
||||
|
||||
case manageAuthorizedSpacesScreen
|
||||
}
|
||||
|
||||
enum Event: EventType {
|
||||
@@ -55,6 +56,9 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
case startRolesAndPermissionsFlow
|
||||
case stopRolesAndPermissionsFlow
|
||||
|
||||
case presentManageAuthorizedSpacesScreen
|
||||
case dismissedManageAuthorizedSpacesScreen
|
||||
}
|
||||
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
@@ -126,6 +130,11 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.editAddress, .dismissedEditAddress):
|
||||
return .securityAndPrivacy
|
||||
|
||||
case (.securityAndPrivacy, .presentManageAuthorizedSpacesScreen):
|
||||
return .manageAuthorizedSpacesScreen
|
||||
case (.manageAuthorizedSpacesScreen, .dismissedManageAuthorizedSpacesScreen):
|
||||
return .securityAndPrivacy
|
||||
|
||||
case (.spaceSettings, .startMembersListFlow):
|
||||
return .membersFlow
|
||||
case (.membersFlow, .stopMembersListFlow):
|
||||
@@ -164,6 +173,14 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.editAddress, .dismissedEditAddress, .securityAndPrivacy):
|
||||
break
|
||||
|
||||
case (.securityAndPrivacy, .presentManageAuthorizedSpacesScreen, .manageAuthorizedSpacesScreen):
|
||||
guard let selection = context.userInfo as? AuthorizedSpacesSelection else {
|
||||
fatalError("AuthorizedSpacesSelection expected as userInfo")
|
||||
}
|
||||
presentManageAuthorizedSpacesScreen(selection: selection)
|
||||
case (.manageAuthorizedSpacesScreen, .dismissedManageAuthorizedSpacesScreen, .securityAndPrivacy):
|
||||
break
|
||||
|
||||
case (.spaceSettings, .startMembersListFlow, .membersFlow):
|
||||
startMembersListFlow()
|
||||
case (.membersFlow, .stopMembersListFlow, .spaceSettings):
|
||||
@@ -258,6 +275,8 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.stateMachine.tryEvent(.presentEditAddress)
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.pop()
|
||||
case .displayManageAuthorizedSpacesScreen(let selection):
|
||||
self.stateMachine.tryEvent(.presentManageAuthorizedSpacesScreen, userInfo: selection)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -287,6 +306,25 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentManageAuthorizedSpacesScreen(selection: AuthorizedSpacesSelection) {
|
||||
let navigationStack = NavigationStackCoordinator()
|
||||
let coordinator = ManageAuthorizedSpacesScreenCoordinator(parameters: .init(authorizedSpacesSelection: selection,
|
||||
mediaProvider: flowParameters.userSession.mediaProvider))
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
navigationStack.setRootCoordinator(coordinator)
|
||||
navigationStackCoordinator.setSheetCoordinator(navigationStack) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissedManageAuthorizedSpacesScreen)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Other flows
|
||||
|
||||
private func startRolesAndPermissionsFlow() {
|
||||
|
||||
@@ -82,6 +82,7 @@ enum TestablePreviewsDictionary {
|
||||
"LocationRoomTimelineView_Previews" : LocationRoomTimelineView_Previews.self,
|
||||
"LoginScreen_Previews" : LoginScreen_Previews.self,
|
||||
"LongPressWithFeedback_Previews" : LongPressWithFeedback_Previews.self,
|
||||
"ManageAuthorizedSpacesScreen_Previews" : ManageAuthorizedSpacesScreen_Previews.self,
|
||||
"ManageRoomMemberSheetView_Previews" : ManageRoomMemberSheetView_Previews.self,
|
||||
"MapLibreStaticMapView_Previews" : MapLibreStaticMapView_Previews.self,
|
||||
"MatrixUserPermalink_Previews" : MatrixUserPermalink_Previews.self,
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations 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 manageAuthorizedSpaces remove this comment once generating the final file
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct ManageAuthorizedSpacesScreenCoordinatorParameters {
|
||||
let authorizedSpacesSelection: AuthorizedSpacesSelection
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
}
|
||||
|
||||
enum ManageAuthorizedSpacesScreenCoordinatorAction {
|
||||
case dismiss
|
||||
}
|
||||
|
||||
final class ManageAuthorizedSpacesScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: ManageAuthorizedSpacesScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ManageAuthorizedSpacesScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ManageAuthorizedSpacesScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: ManageAuthorizedSpacesScreenCoordinatorParameters) {
|
||||
viewModel = ManageAuthorizedSpacesScreenViewModel(authorizedSpacesSelection: parameters.authorizedSpacesSelection,
|
||||
mediaProvider: parameters.mediaProvider)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received view model action: \(action)")
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(ManageAuthorizedSpacesScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations 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 ManageAuthorizedSpacesScreenViewModelAction {
|
||||
case dismiss
|
||||
}
|
||||
|
||||
struct ManageAuthorizedSpacesScreenViewState: BindableState {
|
||||
let authorizedSpacesSelection: AuthorizedSpacesSelection
|
||||
var desiredSelectedIDs: Set<String>
|
||||
|
||||
var hasChanges: Bool {
|
||||
authorizedSpacesSelection.selectedIDs != desiredSelectedIDs
|
||||
}
|
||||
|
||||
init(authorizedSpacesSelection: AuthorizedSpacesSelection) {
|
||||
self.authorizedSpacesSelection = authorizedSpacesSelection
|
||||
desiredSelectedIDs = authorizedSpacesSelection.selectedIDs
|
||||
}
|
||||
}
|
||||
|
||||
enum ManageAuthorizedSpacesScreenViewAction { }
|
||||
|
||||
struct AuthorizedSpacesSelection {
|
||||
let joinedParentSpaces: [SpaceRoomProxyProtocol]
|
||||
let unknownSpacesIDs: [String]
|
||||
let selectedIDs: Set<String>
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations 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 ManageAuthorizedSpacesScreenViewModelType = StateStoreViewModelV2<ManageAuthorizedSpacesScreenViewState, ManageAuthorizedSpacesScreenViewAction>
|
||||
|
||||
class ManageAuthorizedSpacesScreenViewModel: ManageAuthorizedSpacesScreenViewModelType, ManageAuthorizedSpacesScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<ManageAuthorizedSpacesScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ManageAuthorizedSpacesScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(authorizedSpacesSelection: AuthorizedSpacesSelection,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
super.init(initialViewState: ManageAuthorizedSpacesScreenViewState(authorizedSpacesSelection: authorizedSpacesSelection),
|
||||
mediaProvider: mediaProvider)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ManageAuthorizedSpacesScreenViewAction) {
|
||||
MXLog.info("View model: received view action: \(viewAction)")
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations 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 ManageAuthorizedSpacesScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ManageAuthorizedSpacesScreenViewModelAction, Never> { get }
|
||||
var context: ManageAuthorizedSpacesScreenViewModelType.Context { get }
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2025 Element Creations 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 ManageAuthorizedSpacesScreen: View {
|
||||
@Bindable var context: ManageAuthorizedSpacesScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form { }
|
||||
.compoundList()
|
||||
.navigationTitle("Manage spaces")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct ManageAuthorizedSpacesScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = ManageAuthorizedSpacesScreenViewModel(authorizedSpacesSelection: .init(joinedParentSpaces: .mockJoinedSpaces,
|
||||
unknownSpacesIDs: ["!unknown-space-id-1",
|
||||
"!unknown-space-id-2",
|
||||
"!unknown-space-id-3"],
|
||||
selectedIDs: ["space1",
|
||||
"space3",
|
||||
"!unknown-space-id-2"]),
|
||||
mediaProvider: MediaProviderMock(configuration: .init()))
|
||||
|
||||
static var previews: some View {
|
||||
ManageAuthorizedSpacesScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
+3
@@ -19,6 +19,7 @@ struct SecurityAndPrivacyScreenCoordinatorParameters {
|
||||
enum SecurityAndPrivacyScreenCoordinatorAction {
|
||||
case displayEditAddressScreen
|
||||
case dismiss
|
||||
case displayManageAuthorizedSpacesScreen(AuthorizedSpacesSelection)
|
||||
}
|
||||
|
||||
final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -44,6 +45,8 @@ final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .displayManageAuthorizedSpacesScreen(let selection):
|
||||
actionsSubject.send(.displayManageAuthorizedSpacesScreen(selection))
|
||||
case .displayEditAddressScreen:
|
||||
actionsSubject.send(.displayEditAddressScreen)
|
||||
case .dismiss:
|
||||
|
||||
@@ -11,6 +11,7 @@ import Foundation
|
||||
enum SecurityAndPrivacyScreenViewModelAction {
|
||||
case displayEditAddressScreen
|
||||
case dismiss
|
||||
case displayManageAuthorizedSpacesScreen(AuthorizedSpacesSelection)
|
||||
}
|
||||
|
||||
struct SecurityAndPrivacyScreenViewState: BindableState {
|
||||
|
||||
+23
-10
@@ -81,8 +81,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
||||
case .selectedSpaceMembersAccess:
|
||||
handleSelectedSpaceMembersAccess()
|
||||
case .manageSpaces:
|
||||
// TODO: Implement multiple space selection
|
||||
break
|
||||
displayManageAuthorizedSpacesScreen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,15 +235,29 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
||||
}
|
||||
|
||||
private func handleSelectedSpaceMembersAccess() {
|
||||
switch context.viewState.spaceSelection {
|
||||
case .singleJoined(let joinedParent):
|
||||
context.desiredSettings.accessType = .spaceUsers(spaceIDs: [joinedParent.id])
|
||||
case .singleUnknown(let id):
|
||||
context.desiredSettings.accessType = .spaceUsers(spaceIDs: [id])
|
||||
case .multiple:
|
||||
// TODO: Implement multiple space selection
|
||||
break
|
||||
guard !state.bindings.desiredSettings.accessType.isSpaceUsers else {
|
||||
return
|
||||
}
|
||||
|
||||
switch state.spaceSelection {
|
||||
case .singleJoined(let joinedParent):
|
||||
state.bindings.desiredSettings.accessType = .spaceUsers(spaceIDs: [joinedParent.id])
|
||||
case .singleUnknown(let id):
|
||||
state.bindings.desiredSettings.accessType = .spaceUsers(spaceIDs: [id])
|
||||
case .multiple:
|
||||
displayManageAuthorizedSpacesScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private func displayManageAuthorizedSpacesScreen() {
|
||||
let joinedParentSpaces = state.joinedParentSpaces
|
||||
let unknownSpaceIDs = state.currentSettings.accessType.spaceIDs.filter { id in
|
||||
!joinedParentSpaces.contains(where: { $0.id == id })
|
||||
}
|
||||
let selectedIDs = Set(state.bindings.desiredSettings.accessType.spaceIDs)
|
||||
actionsSubject.send(.displayManageAuthorizedSpacesScreen(.init(joinedParentSpaces: joinedParentSpaces,
|
||||
unknownSpacesIDs: unknownSpaceIDs,
|
||||
selectedIDs: selectedIDs)))
|
||||
}
|
||||
|
||||
private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading"
|
||||
|
||||
Reference in New Issue
Block a user