implemented the logic to transfer ownership from a space

This commit is contained in:
Mauro Romito
2026-01-27 17:52:28 +01:00
committed by Mauro
parent b903658975
commit 9de49bad9e
11 changed files with 72 additions and 22 deletions
@@ -349,6 +349,9 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.addRooms)
case .displayCreateChildRoomFlow(let space):
stateMachine.tryEvent(.startCreateChildRoomFlow, userInfo: space)
case .displayTransferOwnership(roomProxy: let roomProxy):
// TODO: Implement
break
}
}
.store(in: &cancellables)
@@ -23,11 +23,11 @@ extension LeaveSpaceHandleSDKMock {
}
extension [LeaveSpaceRoom] {
static func mockRoomsWithSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool) -> [LeaveSpaceRoom] {
mockRooms + mockSingleSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: isLastOwner)
static func mockRoomsWithSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool, areCreatorsPrivileged: Bool) -> [LeaveSpaceRoom] {
mockRooms + mockSingleSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: isLastOwner, areCreatorsPrivileged: areCreatorsPrivileged)
}
static func mockSingleSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool) -> [LeaveSpaceRoom] {
static func mockSingleSpace(spaceServiceRoom: SpaceServiceRoom, isLastOwner: Bool, areCreatorsPrivileged: Bool) -> [LeaveSpaceRoom] {
[LeaveSpaceRoom(spaceRoom: SpaceRoom(id: spaceServiceRoom.id,
name: spaceServiceRoom.name,
avatarURL: spaceServiceRoom.avatarURL,
@@ -35,7 +35,7 @@ extension [LeaveSpaceRoom] {
memberCount: UInt64(spaceServiceRoom.joinedMembersCount),
joinRule: spaceServiceRoom.joinRule),
isLastOwner: isLastOwner,
areCreatorsPrivileged: false)]
areCreatorsPrivileged: areCreatorsPrivileged)]
}
static var mockNeedNewOwnerRooms: [LeaveSpaceRoom] {
@@ -93,15 +93,15 @@ struct RoomChangeRolesScreen: View {
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionSave) {
ToolbarButton(role: .save) {
context.send(viewAction: .save)
}
.disabled(!context.viewState.hasChanges)
}
if context.viewState.hasChanges {
if context.viewState.mode == .owner {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
ToolbarButton(role: .cancel) {
context.send(viewAction: .cancel)
}
}
@@ -226,6 +226,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
case .didLeaveSpace:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.leftRoom)
case .presentTransferOwnership:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.transferOwnership)
}
}
.store(in: &cancellables)
@@ -11,6 +11,7 @@ enum LeaveSpaceViewAction {
case selectAll
case toggleRoom(roomID: String)
case rolesAndPermissions
case transferOwnership
case cancel
}
@@ -44,5 +45,6 @@ struct LeaveSpaceViewState: BindableState {
enum LeaveSpaceViewModelAction {
case didLeaveSpace
case presentRolesAndPermissions
case presentTransferOwnership
case didCancel
}
@@ -41,6 +41,8 @@ class LeaveSpaceViewModel: LeaveSpaceViewModelType {
withTransaction(\.disablesAnimations, true) { // The button is adding an unwanted animation.
state.leaveHandle.toggleRoom(roomID: roomID)
}
case .transferOwnership:
actionsSubject.send(.presentTransferOwnership)
}
}
@@ -66,7 +66,7 @@ struct LeaveSpaceView: View {
Section {
ForEach(context.viewState.leaveHandle.rooms, id: \.spaceServiceRoom.id) { room in
LeaveSpaceRoomDetailsCell(room: room,
hideSelection: context.viewState.leaveHandle.mode == .roomsNeedNewOwner,
hideSelection: !context.viewState.leaveHandle.canLeave,
mediaProvider: context.mediaProvider) {
context.send(viewAction: .toggleRoom(roomID: room.spaceServiceRoom.id))
}
@@ -90,20 +90,32 @@ struct LeaveSpaceView: View {
var buttons: some View {
VStack(spacing: 16) {
if context.viewState.leaveHandle.canLeave {
switch context.viewState.leaveHandle.mode {
case .spaceNeedsNewOwner(let useTransferOwnershipFlow):
if context.viewState.canEditRolesAndPermissions {
if useTransferOwnershipFlow {
Button(role: .destructive) {
context.send(viewAction: .transferOwnership)
} label: {
Label(L10n.actionGoToRolesAndPermissions, icon: \.settings)
}
.buttonStyle(.compound(.primary))
} else {
Button {
context.send(viewAction: .rolesAndPermissions)
} label: {
Label(L10n.actionGoToRolesAndPermissions, icon: \.settings)
}
.buttonStyle(.compound(.primary))
}
}
default:
Button(role: .destructive) {
context.send(viewAction: .confirmLeaveSpace)
} label: {
Label(context.viewState.confirmationTitle, icon: \.leave)
}
.buttonStyle(.compound(.primary))
} else if context.viewState.canEditRolesAndPermissions {
Button {
context.send(viewAction: .rolesAndPermissions)
} label: {
Label(L10n.actionGoToRolesAndPermissions, icon: \.settings)
}
.buttonStyle(.compound(.primary))
}
Button(L10n.actionCancel, action: dismiss.callAsFunction)
@@ -123,7 +135,8 @@ struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
static let manyViewModel = makeViewModel(mode: .manyRooms)
static let onlyAdminViewModel = makeViewModel(mode: .roomsNeedNewOwner)
static let noRoomsViewModel = makeViewModel(mode: .noRooms)
static let lastAdminViewModel = makeViewModel(mode: .spaceNeedsNewOwner)
static let lastAdminViewModel = makeViewModel(mode: .spaceNeedsNewOwner(useTransferOwnershipFlow: false))
static let lastOwnerViewModel = makeViewModel(mode: .spaceNeedsNewOwner(useTransferOwnershipFlow: true))
static var previews: some View {
LeaveSpaceView(context: manyViewModel.context)
@@ -134,6 +147,8 @@ struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("No Rooms")
LeaveSpaceView(context: lastAdminViewModel.context)
.previewDisplayName("Last Space Admin")
LeaveSpaceView(context: lastOwnerViewModel.context)
.previewDisplayName("Last Space Owner")
}
static let spaceServiceRoom = SpaceServiceRoom.mock(id: "!eng-space:matrix.org",
@@ -149,8 +164,12 @@ struct LeaveSpaceView_Previews: PreviewProvider, TestablePreview {
let rooms: [LeaveSpaceRoom] = switch mode {
case .manyRooms: .mockRooms
case .roomsNeedNewOwner: .mockNeedNewOwnerRooms
case .noRooms: .mockSingleSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: false)
case .spaceNeedsNewOwner: .mockRoomsWithSpace(spaceServiceRoom: spaceServiceRoom, isLastOwner: true)
case .noRooms: .mockSingleSpace(spaceServiceRoom: spaceServiceRoom,
isLastOwner: false,
areCreatorsPrivileged: false)
case .spaceNeedsNewOwner(let useTransferOwnershipFlow): .mockRoomsWithSpace(spaceServiceRoom: spaceServiceRoom,
isLastOwner: true,
areCreatorsPrivileged: useTransferOwnershipFlow)
}
let leaveHandle = LeaveSpaceHandleProxy(spaceID: spaceServiceRoom.id,
@@ -28,6 +28,7 @@ enum SpaceScreenCoordinatorAction {
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
case displayRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
case displayTransferOwnership(roomProxy: JoinedRoomProxyProtocol)
case addExistingChildren
case displayCreateChildRoomFlow(space: SpaceServiceRoom)
}
@@ -78,6 +79,8 @@ final class SpaceScreenCoordinator: CoordinatorProtocol {
actionsSubject.send(.addExistingChildren)
case .displayCreateChildRoomFlow(let space):
actionsSubject.send(.displayCreateChildRoomFlow(space: space))
case .presentTransferOwnership(roomProxy: let roomProxy):
actionsSubject.send(.displayTransferOwnership(roomProxy: roomProxy))
}
}
.store(in: &cancellables)
@@ -14,6 +14,7 @@ enum SpaceScreenViewModelAction {
case selectRoom(roomID: String)
case leftSpace
case presentRolesAndPermissions(roomProxy: JoinedRoomProxyProtocol)
case presentTransferOwnership(roomProxy: JoinedRoomProxyProtocol)
case displayMembers(roomProxy: JoinedRoomProxyProtocol)
case displaySpaceSettings(roomProxy: JoinedRoomProxyProtocol)
case addExistingChildren
@@ -261,6 +261,12 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
case .didLeaveSpace:
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.leftSpace)
case .presentTransferOwnership:
guard let roomProxy = state.roomProxy else {
fatalError("The space screen should always have a room proxy")
}
state.bindings.leaveSpaceViewModel = nil
actionsSubject.send(.presentTransferOwnership(roomProxy: roomProxy))
}
}
.store(in: &cancellables)
@@ -13,13 +13,24 @@ final class LeaveSpaceHandleProxy {
let id: String
var rooms: [LeaveSpaceRoomDetails]
enum Mode { case manyRooms, roomsNeedNewOwner, noRooms, spaceNeedsNewOwner }
enum Mode: Equatable {
case manyRooms
case roomsNeedNewOwner
case noRooms
case spaceNeedsNewOwner(useTransferOwnershipFlow: Bool)
}
let mode: Mode
private let leaveHandle: LeaveSpaceHandleProtocol
var canLeave: Bool {
mode != .spaceNeedsNewOwner
switch mode {
case .spaceNeedsNewOwner:
false
default:
true
}
}
var selectedCount: Int {
@@ -47,7 +58,7 @@ final class LeaveSpaceHandleProxy {
}
mode = if let space, space.isLastOwner, space.spaceRoom.numJoinedMembers > 1 {
.spaceNeedsNewOwner
.spaceNeedsNewOwner(useTransferOwnershipFlow: space.areCreatorsPrivileged)
} else if self.rooms.isEmpty {
.noRooms
} else if self.rooms.count(where: { $0.canLeave }) == 0 {