mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Add space management to the flows. (#4978)
* Add the menu entries to add/remove rooms to/from a space. * Add a user indicator to SpaceAddRoomsScreen. * Reset the SpaceRoomListProxy after adding/removing any children. * Calm the animations down a bit when entering EditMode on the SpaceScreen.
This commit is contained in:
@@ -9791,7 +9791,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 26.1.13;
|
version = "26.01.20-2";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -158,8 +158,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "134161b4a42d019befe6a9a293d5248b8c41015a",
|
"revision" : "3c8b43e1203022ca1bc187fe41f48992958b02d0",
|
||||||
"version" : "26.1.13"
|
"version" : "26.1.20-2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case joinSpace
|
case joinSpace
|
||||||
/// The root screen for this flow.
|
/// The root screen for this flow.
|
||||||
case space
|
case space
|
||||||
|
/// The user is adding rooms to the space.
|
||||||
|
case addingRooms
|
||||||
/// A child (space) flow is in progress.
|
/// A child (space) flow is in progress.
|
||||||
case presentingChild(childSpaceID: String, previousState: State)
|
case presentingChild(childSpaceID: String, previousState: State)
|
||||||
/// A room flow is in progress
|
/// A room flow is in progress
|
||||||
@@ -77,6 +79,11 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// The space screen left the space.
|
/// The space screen left the space.
|
||||||
case leftSpace
|
case leftSpace
|
||||||
|
|
||||||
|
/// Allow the user to add existing rooms to this space.
|
||||||
|
case addRooms
|
||||||
|
/// The user finished adding rooms to this space.
|
||||||
|
case dismissedAddRooms
|
||||||
|
|
||||||
/// Request the presentation of a child space flow.
|
/// Request the presentation of a child space flow.
|
||||||
///
|
///
|
||||||
/// The space's `SpaceRoomListProxyProtocol` must be provided in the `userInfo`.
|
/// The space's `SpaceRoomListProxyProtocol` must be provided in the `userInfo`.
|
||||||
@@ -145,6 +152,9 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
} else {
|
} else {
|
||||||
navigationStackCoordinator.setRootCoordinator(nil, animated: animated)
|
navigationStackCoordinator.setRootCoordinator(nil, animated: animated)
|
||||||
}
|
}
|
||||||
|
case .addingRooms:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
clearRoute(animated: animated) // Re-run with the state machine back in the .space state.
|
||||||
case .presentingChild:
|
case .presentingChild:
|
||||||
childSpaceFlowCoordinator?.clearRoute(animated: animated)
|
childSpaceFlowCoordinator?.clearRoute(animated: animated)
|
||||||
clearRoute(animated: animated) // Re-run with the state machine back in the .space state.
|
clearRoute(animated: animated) // Re-run with the state machine back in the .space state.
|
||||||
@@ -182,6 +192,11 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self?.clearRoute(animated: true)
|
self?.clearRoute(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stateMachine.addRoutes(event: .addRooms, transitions: [.space => .addingRooms]) { [weak self] _ in
|
||||||
|
self?.presentSpaceAddRoomsScreen()
|
||||||
|
}
|
||||||
|
stateMachine.addRoutes(event: .dismissedAddRooms, transitions: [.addingRooms => .space])
|
||||||
|
|
||||||
stateMachine.addRouteMapping { event, fromState, userInfo in
|
stateMachine.addRouteMapping { event, fromState, userInfo in
|
||||||
guard event == .startChildFlow else { return nil }
|
guard event == .startChildFlow else { return nil }
|
||||||
guard let childEntryPoint = userInfo as? SpaceFlowCoordinatorEntryPoint else { fatalError("An entry point must be provided.") }
|
guard let childEntryPoint = userInfo as? SpaceFlowCoordinatorEntryPoint else { fatalError("An entry point must be provided.") }
|
||||||
@@ -306,6 +321,8 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.startSettingsFlow, userInfo: roomProxy)
|
stateMachine.tryEvent(.startSettingsFlow, userInfo: roomProxy)
|
||||||
case .displayRolesAndPermissions(let roomProxy):
|
case .displayRolesAndPermissions(let roomProxy):
|
||||||
stateMachine.tryEvent(.startRolesAndPermissionsFlow, userInfo: roomProxy)
|
stateMachine.tryEvent(.startRolesAndPermissionsFlow, userInfo: roomProxy)
|
||||||
|
case .addExistingChildren:
|
||||||
|
stateMachine.tryEvent(.addRooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -371,6 +388,31 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentSpace()
|
presentSpace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentSpaceAddRoomsScreen() {
|
||||||
|
guard case let .space(spaceRoomListProxy) = entryPoint else { fatalError("Attempting to show a space with the wrong entry point.") }
|
||||||
|
|
||||||
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
let parameters = SpaceAddRoomsScreenCoordinatorParameters(spaceRoomListProxy: spaceRoomListProxy,
|
||||||
|
userSession: flowParameters.userSession,
|
||||||
|
roomSummaryProvider: flowParameters.userSession.clientProxy.alternateRoomSummaryProvider,
|
||||||
|
userIndicatorController: flowParameters.userIndicatorController)
|
||||||
|
let coordinator = SpaceAddRoomsScreenCoordinator(parameters: parameters)
|
||||||
|
coordinator.actions
|
||||||
|
.sink { [weak self] action in
|
||||||
|
guard let self else { return }
|
||||||
|
switch action {
|
||||||
|
case .dismiss:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
stackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||||
|
self?.stateMachine.tryEvent(.dismissedAddRooms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Other flows
|
// MARK: - Other flows
|
||||||
|
|
||||||
private func startChildFlow(with entryPoint: SpaceFlowCoordinatorEntryPoint) {
|
private func startChildFlow(with entryPoint: SpaceFlowCoordinatorEntryPoint) {
|
||||||
|
|||||||
@@ -16772,6 +16772,41 @@ class SpaceRoomListProxyMock: SpaceRoomListProxyProtocol, @unchecked Sendable {
|
|||||||
paginateCallsCount += 1
|
paginateCallsCount += 1
|
||||||
await paginateClosure?()
|
await paginateClosure?()
|
||||||
}
|
}
|
||||||
|
//MARK: - reset
|
||||||
|
|
||||||
|
var resetUnderlyingCallsCount = 0
|
||||||
|
var resetCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return resetUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = resetUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
resetUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
resetUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var resetCalled: Bool {
|
||||||
|
return resetCallsCount > 0
|
||||||
|
}
|
||||||
|
var resetClosure: (() async -> Void)?
|
||||||
|
|
||||||
|
func reset() async {
|
||||||
|
resetCallsCount += 1
|
||||||
|
await resetClosure?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable {
|
class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable {
|
||||||
var topLevelSpacesPublisher: CurrentValuePublisher<[SpaceServiceRoomProtocol], Never> {
|
var topLevelSpacesPublisher: CurrentValuePublisher<[SpaceServiceRoomProtocol], Never> {
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ struct SpaceRoomCell: View {
|
|||||||
action(.select(spaceServiceRoom))
|
action(.select(spaceServiceRoom))
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
if isEditModeActive {
|
if isEditModeActive,
|
||||||
|
!spaceServiceRoom.isSpace { // We only support selection of rooms (so don't show this while removing the cell).
|
||||||
ZStack {
|
ZStack {
|
||||||
ListRowAccessory.multiSelection(isSelected)
|
ListRowAccessory.multiSelection(isSelected)
|
||||||
}
|
}
|
||||||
@@ -87,6 +88,9 @@ struct SpaceRoomCell: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, horizontalInsets)
|
.padding(.horizontal, horizontalInsets)
|
||||||
|
// Ensure the EditMode transition stays inside this cell if there are other insertions/removals in the list.
|
||||||
|
// Seems to slow down the animations a bit in Xcode previews but its fine in the simulator and on a device.
|
||||||
|
.drawingGroup()
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
}
|
}
|
||||||
.buttonStyle(SpaceRoomCellButtonStyle(isHighlighted: isHighlighted))
|
.buttonStyle(SpaceRoomCellButtonStyle(isHighlighted: isHighlighted))
|
||||||
|
|||||||
+29
-1
@@ -128,15 +128,43 @@ class SpaceAddRoomsScreenViewModel: SpaceAddRoomsScreenViewModelType, SpaceAddRo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func save() async {
|
private func save() async {
|
||||||
|
showSavingIndicator()
|
||||||
|
defer { hideSavingIndicator() }
|
||||||
|
|
||||||
for room in state.selectedRooms {
|
for room in state.selectedRooms {
|
||||||
if case .failure(let error) = await spaceServiceProxy.addChild(room.id, to: spaceRoomListProxy.id) {
|
if case .failure(let error) = await spaceServiceProxy.addChild(room.id, to: spaceRoomListProxy.id) {
|
||||||
MXLog.error("Failed adding room to space: \(error)")
|
MXLog.error("Failed adding room to space: \(error)")
|
||||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
showErrorIndicator()
|
||||||
updateRooms() // Hide any rooms that were already added.
|
updateRooms() // Hide any rooms that were already added.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await spaceRoomListProxy.resetAndWaitForFullReload(timeout: .seconds(10))
|
||||||
|
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: User Indicators
|
||||||
|
|
||||||
|
private var savingIndicatorID: String { "\(Self.self)-Saving" }
|
||||||
|
private var failureIndicatorID: String { "\(Self.self)-Failure" }
|
||||||
|
|
||||||
|
private func showSavingIndicator() {
|
||||||
|
userIndicatorController.submitIndicator(UserIndicator(id: savingIndicatorID,
|
||||||
|
type: .modal(progress: .indeterminate, interactiveDismissDisabled: true, allowsInteraction: false),
|
||||||
|
title: L10n.commonSaving,
|
||||||
|
persistent: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hideSavingIndicator() {
|
||||||
|
userIndicatorController.retractIndicatorWithId(savingIndicatorID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showErrorIndicator() {
|
||||||
|
userIndicatorController.submitIndicator(UserIndicator(id: failureIndicatorID,
|
||||||
|
type: .toast,
|
||||||
|
title: L10n.errorUnknown,
|
||||||
|
iconName: "xmark"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ enum SpaceScreenCoordinatorAction {
|
|||||||
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
||||||
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
||||||
case displayRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
|
case displayRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
|
||||||
|
case addExistingChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SpaceScreenCoordinator: CoordinatorProtocol {
|
final class SpaceScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -72,6 +73,8 @@ final class SpaceScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
|
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
|
||||||
case .presentRolesAndPermissions(let roomProxy):
|
case .presentRolesAndPermissions(let roomProxy):
|
||||||
actionsSubject.send(.displayRolesAndPermissions(roomProxy: roomProxy))
|
actionsSubject.send(.displayRolesAndPermissions(roomProxy: roomProxy))
|
||||||
|
case .addExistingChildren:
|
||||||
|
actionsSubject.send(.addExistingChildren)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ enum SpaceScreenViewModelAction {
|
|||||||
case presentRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
|
case presentRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
|
||||||
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
||||||
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
||||||
|
case addExistingChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpaceScreenViewState: BindableState {
|
struct SpaceScreenViewState: BindableState {
|
||||||
@@ -32,6 +33,7 @@ struct SpaceScreenViewState: BindableState {
|
|||||||
var canEditBaseInfo = false
|
var canEditBaseInfo = false
|
||||||
var canEditRolesAndPermissions = false
|
var canEditRolesAndPermissions = false
|
||||||
var canEditSecurityAndPrivacy = false
|
var canEditSecurityAndPrivacy = false
|
||||||
|
var canEditChildren = false
|
||||||
|
|
||||||
var editMode: EditMode = .inactive
|
var editMode: EditMode = .inactive
|
||||||
var editModeSelectedIDs: Set<String> = []
|
var editModeSelectedIDs: Set<String> = []
|
||||||
@@ -65,6 +67,7 @@ enum SpaceScreenViewAction {
|
|||||||
case leaveSpace
|
case leaveSpace
|
||||||
case spaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
case spaceSettings(roomProxy: JoinedRoomProxyProtocol)
|
||||||
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
|
||||||
|
case addExistingRooms
|
||||||
case manageChildren
|
case manageChildren
|
||||||
case removeSelectedChildren
|
case removeSelectedChildren
|
||||||
case confirmRemoveSelectedChildren
|
case confirmRemoveSelectedChildren
|
||||||
|
|||||||
@@ -89,12 +89,14 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
|
|||||||
state.canEditBaseInfo = false
|
state.canEditBaseInfo = false
|
||||||
state.canEditRolesAndPermissions = false
|
state.canEditRolesAndPermissions = false
|
||||||
state.canEditSecurityAndPrivacy = false
|
state.canEditSecurityAndPrivacy = false
|
||||||
|
state.canEditChildren = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
state.canEditBaseInfo = powerLevels.canOwnUserEditBaseInfo()
|
state.canEditBaseInfo = powerLevels.canOwnUserEditBaseInfo()
|
||||||
state.canEditRolesAndPermissions = powerLevels.canOwnUserEditRolesAndPermissions()
|
state.canEditRolesAndPermissions = powerLevels.canOwnUserEditRolesAndPermissions()
|
||||||
state.canEditSecurityAndPrivacy = powerLevels.canOwnUserEditSecurityAndPrivacy(isSpace: roomInfo.isSpace,
|
state.canEditSecurityAndPrivacy = powerLevels.canOwnUserEditSecurityAndPrivacy(isSpace: roomInfo.isSpace,
|
||||||
joinRule: roomInfo.joinRule)
|
joinRule: roomInfo.joinRule)
|
||||||
|
state.canEditChildren = powerLevels.canOwnUser(sendStateEvent: .spaceChild)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
@@ -134,6 +136,8 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
|
|||||||
actionsSubject.send(.displayMembers(roomProxy: roomProxy))
|
actionsSubject.send(.displayMembers(roomProxy: roomProxy))
|
||||||
case .spaceSettings(let roomProxy):
|
case .spaceSettings(let roomProxy):
|
||||||
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
|
actionsSubject.send(.displaySpaceSettings(roomProxy: roomProxy))
|
||||||
|
case .addExistingRooms:
|
||||||
|
actionsSubject.send(.addExistingChildren)
|
||||||
case .manageChildren:
|
case .manageChildren:
|
||||||
withAnimation(.easeOut(duration: 0.25).disabledDuringTests()) {
|
withAnimation(.easeOut(duration: 0.25).disabledDuringTests()) {
|
||||||
state.editMode = .transient
|
state.editMode = .transient
|
||||||
@@ -196,6 +200,8 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await spaceRoomListProxy.resetAndWaitForFullReload(timeout: .seconds(10))
|
||||||
|
|
||||||
process(viewAction: .finishManagingChildren)
|
process(viewAction: .finishManagingChildren)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,17 +27,11 @@ struct SpaceScreen: View {
|
|||||||
}
|
}
|
||||||
.environment(\.editMode, .constant(context.viewState.editMode))
|
.environment(\.editMode, .constant(context.viewState.editMode))
|
||||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||||
.toolbarRole(isEditModeActive ? .automatic : RoomHeaderView.toolbarRole)
|
.toolbarRole(RoomHeaderView.toolbarRole)
|
||||||
.navigationTitle(context.viewState.space.name)
|
.navigationTitle(context.viewState.space.name)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationBarBackButtonHidden(isEditModeActive)
|
.navigationBarBackButtonHidden(isEditModeActive)
|
||||||
.toolbar {
|
.toolbar { toolbar }
|
||||||
if isEditModeActive {
|
|
||||||
editModeToolbar
|
|
||||||
} else {
|
|
||||||
toolbar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $context.isPresentingRemoveChildrenConfirmation) {
|
.sheet(isPresented: $context.isPresentingRemoveChildrenConfirmation) {
|
||||||
SpaceRemoveChildrenConfirmationView(spaceName: context.viewState.space.name) {
|
SpaceRemoveChildrenConfirmationView(spaceName: context.viewState.space.name) {
|
||||||
context.send(viewAction: .confirmRemoveSelectedChildren)
|
context.send(viewAction: .confirmRemoveSelectedChildren)
|
||||||
@@ -67,6 +61,14 @@ struct SpaceScreen: View {
|
|||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
var toolbar: some ToolbarContent {
|
var toolbar: some ToolbarContent {
|
||||||
|
if isEditModeActive {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button(L10n.actionCancel, role: .cancel) {
|
||||||
|
context.send(viewAction: .finishManagingChildren)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use the same trick as the RoomScreen for a leading title view that
|
// Use the same trick as the RoomScreen for a leading title view that
|
||||||
// also hides the navigation title.
|
// also hides the navigation title.
|
||||||
ToolbarItem(placement: .principal) {
|
ToolbarItem(placement: .principal) {
|
||||||
@@ -80,62 +82,62 @@ struct SpaceScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should really use a ToolbarItemGroup(placement: .secondaryAction), however it
|
if isEditModeActive {
|
||||||
// was crashing on iOS 26.0 when tapping the ShareLink as the popover presentation
|
ToolbarItem(placement: .primaryAction) {
|
||||||
// controller attempts to anchor itself to the button that is no longer visible.
|
ToolbarButton(role: .destructive(title: L10n.actionRemove)) {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
context.send(viewAction: .removeSelectedChildren)
|
||||||
Menu {
|
}
|
||||||
Section {
|
.disabled(context.viewState.editModeSelectedIDs.isEmpty)
|
||||||
if let roomProxy = context.viewState.roomProxy {
|
}
|
||||||
Button { context.send(viewAction: .displayMembers(roomProxy: roomProxy)) } label: {
|
} else {
|
||||||
Label(L10n.screenSpaceMenuActionMembers, icon: \.user)
|
// This should really use a ToolbarItemGroup(placement: .secondaryAction), however it
|
||||||
}
|
// was crashing on iOS 26.0 when tapping the ShareLink as the popover presentation
|
||||||
}
|
// controller attempts to anchor itself to the button that is no longer visible.
|
||||||
if let permalink = context.viewState.permalink {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
ShareLink(item: permalink) {
|
Menu {
|
||||||
Label(L10n.actionShare, icon: \.shareIos)
|
if true {
|
||||||
|
Section {
|
||||||
|
Button { context.send(viewAction: .addExistingRooms) } label: {
|
||||||
|
Label(L10n.actionAddExistingRooms, icon: \.room)
|
||||||
|
}
|
||||||
|
Button { context.send(viewAction: .manageChildren) } label: {
|
||||||
|
Label(L10n.actionManageRooms, icon: \.edit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.isSpaceManagementEnabled,
|
Section {
|
||||||
let roomProxy = context.viewState.roomProxy {
|
if let roomProxy = context.viewState.roomProxy {
|
||||||
Button { context.send(viewAction: .spaceSettings(roomProxy: roomProxy)) } label: {
|
Button { context.send(viewAction: .displayMembers(roomProxy: roomProxy)) } label: {
|
||||||
Label(L10n.commonSettings, icon: \.settings)
|
Label(L10n.screenSpaceMenuActionMembers, icon: \.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let permalink = context.viewState.permalink {
|
||||||
|
ShareLink(item: permalink) {
|
||||||
|
Label(L10n.actionShare, icon: \.shareIos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.viewState.isSpaceManagementEnabled,
|
||||||
|
let roomProxy = context.viewState.roomProxy {
|
||||||
|
Button { context.send(viewAction: .spaceSettings(roomProxy: roomProxy)) } label: {
|
||||||
|
Label(L10n.commonSettings, icon: \.settings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Section {
|
||||||
Section {
|
Button(role: .destructive) { context.send(viewAction: .leaveSpace) } label: {
|
||||||
Button(role: .destructive) { context.send(viewAction: .leaveSpace) } label: {
|
Label(L10n.actionLeaveSpace, icon: \.leave)
|
||||||
Label(L10n.actionLeaveSpace, icon: \.leave)
|
}
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
// Use an SF Symbol to match what ToolbarItemGroup(placement: .secondaryAction) would give us.
|
||||||
|
Image(systemSymbol: .ellipsis)
|
||||||
}
|
}
|
||||||
} label: {
|
|
||||||
// Use an SF Symbol to match what ToolbarItemGroup(placement: .secondaryAction) would give us.
|
|
||||||
Image(systemSymbol: .ellipsis)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
|
||||||
var editModeToolbar: some ToolbarContent {
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
|
||||||
Button(L10n.actionCancel, role: .cancel) {
|
|
||||||
context.send(viewAction: .finishManagingChildren)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarItem(placement: .principal) {
|
|
||||||
Text(L10n.commonSelectedCount(context.viewState.editModeSelectedIDs.count))
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarItem(placement: .primaryAction) {
|
|
||||||
ToolbarButton(role: .destructive(title: L10n.actionRemove)) {
|
|
||||||
context.send(viewAction: .removeSelectedChildren)
|
|
||||||
}
|
|
||||||
.disabled(context.viewState.editModeSelectedIDs.isEmpty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ class SpaceRoomListProxy: SpaceRoomListProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reset() async {
|
||||||
|
await spaceRoomList.reset()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func handleUpdates(_ updates: [SpaceListUpdate]) {
|
private func handleUpdates(_ updates: [SpaceListUpdate]) {
|
||||||
|
|||||||
@@ -22,4 +22,20 @@ protocol SpaceRoomListProxyProtocol {
|
|||||||
var paginationStatePublisher: CurrentValuePublisher<SpaceRoomListPaginationState, Never> { get }
|
var paginationStatePublisher: CurrentValuePublisher<SpaceRoomListPaginationState, Never> { get }
|
||||||
|
|
||||||
func paginate() async
|
func paginate() async
|
||||||
|
func reset() async
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SpaceRoomListProxyProtocol {
|
||||||
|
/// Resets the list and then waits everything to be paginated back in again before returning.
|
||||||
|
///
|
||||||
|
/// **Note:** It's the caller's responsibility to handle the calls to ``paginate``. This method
|
||||||
|
/// purely acts as a helper to wait until the list has reloaded.
|
||||||
|
func resetAndWaitForFullReload(timeout: Duration) async {
|
||||||
|
await reset()
|
||||||
|
|
||||||
|
let runner = ExpiringTaskRunner { [paginationStatePublisher] in
|
||||||
|
await _ = paginationStatePublisher.values.first { $0 == .idle(endReached: true) }
|
||||||
|
}
|
||||||
|
try? await runner.run(timeout: timeout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5511435ffbb116306de56c4fe13874f90cc12eabf83b2589ba9a1adad4080238
|
oid sha256:21c3bf8bb04de6c1896b1955f0785d8ffc9b15898205b1c6ea68638d729db47a
|
||||||
size 253938
|
size 254978
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:0dc15c6e2798e677ab80ec5ee6f2e22d32faddccdc47e2f52b433bad0ca4ec94
|
oid sha256:88ae666e2969417e9b03925bdd23b863d8b4c1cd93b86a00cfb76a78b93e6ad1
|
||||||
size 296429
|
size 297539
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:34754363cd2c7886e42caba103759f6fdb63af2e28a23de910573d266d7f4b82
|
oid sha256:d0f77bba1b2b3cbfc2c61639c2b2e585a8dba2e3955e4d3b329fc5e8113fdc16
|
||||||
size 193936
|
size 194831
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:dee1473c832480b2adf30bf5a1900ff3d94a55400d651baafabdef1ed0e445d2
|
oid sha256:781bd4e44a9912bbe293b5111f3579cf74235fb17de33114c2849077ea69e932
|
||||||
size 226188
|
size 227203
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:4b68d646d3cc7e109b242dab25985c9311129f4625c61b0fceabfde8befbe2a2
|
oid sha256:a981d66c5c9f6edccbe959b4cfbe431b8e67e0bd736b16f39638be62a5297b7c
|
||||||
size 139161
|
size 141054
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:180b69501525210102f696b7d7e7c6030070b2fce796149040a8310758cd826a
|
oid sha256:9704b5b3af36640a459086aeac5732b7308a293555f2a8227c4f8bf6033ec9ea
|
||||||
size 153988
|
size 153135
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:525db300c8ddf0b4277f082d972338bbcddef87ccc12c72b615ed8dfce8dc367
|
oid sha256:9780f6394e4a459137e7357ecf520fd193714d3e091258f63ccbf73522787034
|
||||||
size 89218
|
size 90779
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:df0e19c0abbd1b39e399f3b8f7d033fdf5bf8c5ce688688027fc7ed856e78d04
|
oid sha256:830f150bed0d92a729e47ad6ac263c68178b179de8955a25a4aac3c05126e1b2
|
||||||
size 97285
|
size 96788
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9396e9b4d4be5286a09b62fda56aae69c1ea862cad78f196151f3c47a2b2429e
|
oid sha256:82d4bd888ba03059dd94297a561b4b53d6584a8e10f5f81a6197370898c85d6e
|
||||||
size 233895
|
size 234307
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fd3560a4acae0e9f1e635343f0f4a5b46ebc55b9853e93c59233457948cdfe33
|
oid sha256:76b3679937eb3ff7e92c4cce0d654fa9895dba93dc228d9f1c9165e040d51d14
|
||||||
size 263863
|
size 264529
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:df56484cef6939240050513ca42a0d29b843b5a3362b292fc3d5d8ed14076ddb
|
oid sha256:8f8c4c4e33ce89bd6a25a40db74f149b18b3994aae8513b1fd10fe24859dfda6
|
||||||
size 175828
|
size 176522
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:08b11c147ca9ac8b855af225fdea4f606261b9ad8d0af4043a231666a006c96a
|
oid sha256:37f0f4765abc7592e0e890fdd214bfac8be5003067f773687be992b9a96b37da
|
||||||
size 197580
|
size 198295
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:da8634792cdf250fc915387d74a513e71375e4fde05fde2260618eee8bd705c3
|
oid sha256:15f22091f345610f1dfd613684f2ee4ada908d4ca2c862327f4b96ff2f9f3013
|
||||||
size 127480
|
size 127224
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:af7a804c1ab88187c5df2a1b3bca2161ec4afe05c7f013763cbb1dd70b1a2b3b
|
oid sha256:f043cc81549b07df4b489d52b35aa1ac8b3136ebec006adb6ce69e6bc8d53b06
|
||||||
size 150444
|
size 150115
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import XCTest
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class SpaceAddRoomsScreenViewModelTests: XCTestCase {
|
class SpaceAddRoomsScreenViewModelTests: XCTestCase {
|
||||||
|
var spaceRoomListProxy: SpaceRoomListProxyMock!
|
||||||
|
|
||||||
var viewModel: SpaceAddRoomsScreenViewModelProtocol!
|
var viewModel: SpaceAddRoomsScreenViewModelProtocol!
|
||||||
var context: SpaceAddRoomsScreenViewModelType.Context { viewModel.context }
|
var context: SpaceAddRoomsScreenViewModelType.Context { viewModel.context }
|
||||||
|
|
||||||
@@ -41,11 +43,13 @@ class SpaceAddRoomsScreenViewModelTests: XCTestCase {
|
|||||||
context.send(viewAction: .save)
|
context.send(viewAction: .save)
|
||||||
|
|
||||||
try await deferredAction.fulfill()
|
try await deferredAction.fulfill()
|
||||||
|
|
||||||
|
XCTAssertTrue(spaceRoomListProxy.resetCalled, "The room list should be reset to pick up the changes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupViewModel() {
|
func setupViewModel() {
|
||||||
let summaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
|
let summaryProvider = RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))
|
||||||
let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceServiceRoom: SpaceServiceRoomMock(.init(isSpace: true))))
|
spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceServiceRoom: SpaceServiceRoomMock(.init(isSpace: true))))
|
||||||
|
|
||||||
let clientProxy = ClientProxyMock(.init())
|
let clientProxy = ClientProxyMock(.init())
|
||||||
clientProxy.recentlyVisitedRoomsFilterReturnValue = .init(repeating: JoinedRoomProxyMock(.init()), count: 5)
|
clientProxy.recentlyVisitedRoomsFilterReturnValue = .init(repeating: JoinedRoomProxyMock(.init()), count: 5)
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ class SpaceScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertTrue(context.viewState.visibleRooms.contains { $0.isSpace }, "Confirming should restore the hidden spaces when done.")
|
XCTAssertTrue(context.viewState.visibleRooms.contains { $0.isSpace }, "Confirming should restore the hidden spaces when done.")
|
||||||
|
|
||||||
XCTAssertEqual(spaceServiceProxy.removeChildFromCallsCount, 2, "Each selected room should have been removed.")
|
XCTAssertEqual(spaceServiceProxy.removeChildFromCallsCount, 2, "Each selected room should have been removed.")
|
||||||
|
XCTAssertTrue(spaceRoomListProxy.resetCalled, "The room list should be reset to pick up the changes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLeavingSpace() async throws {
|
func testLeavingSpace() async throws {
|
||||||
|
|||||||
+1
-1
@@ -73,7 +73,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 26.1.13
|
exactVersion: 26.01.20-2
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
path: compound-ios
|
path: compound-ios
|
||||||
|
|||||||
Reference in New Issue
Block a user