diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 01cd3ebeb..6db84922c 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1750,6 +1750,8 @@ Tap the + to start adding people."; "room_invite_to_space_option_detail" = "They can explore %@, but won’t be a member of %@."; "room_invite_to_room_option_title" = "To just this room"; "room_invite_to_room_option_detail" = "They won’t be a part of %@."; +"room_invite_not_enough_permission" = "You do not have permission to invite people to this room"; +"space_invite_not_enough_permission" = "You do not have permission to invite people to this space"; // Mark: - Spaces diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 94566dbe1..33053a5d1 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -3291,6 +3291,10 @@ public class VectorL10n: NSObject { public static var roomIntroCellInformationRoomWithoutTopicSentence2Part2: String { return VectorL10n.tr("Vector", "room_intro_cell_information_room_without_topic_sentence2_part2") } + /// You do not have permission to invite people to this room + public static var roomInviteNotEnoughPermission: String { + return VectorL10n.tr("Vector", "room_invite_not_enough_permission") + } /// They won’t be a part of %@. public static func roomInviteToRoomOptionDetail(_ p1: String) -> String { return VectorL10n.tr("Vector", "room_invite_to_room_option_detail", p1) @@ -5235,6 +5239,10 @@ public class VectorL10n: NSObject { public static var spaceHomeShowAllRooms: String { return VectorL10n.tr("Vector", "space_home_show_all_rooms") } + /// You do not have permission to invite people to this space + public static var spaceInviteNotEnoughPermission: String { + return VectorL10n.tr("Vector", "space_invite_not_enough_permission") + } /// Ban from this space public static var spaceParticipantsActionBan: String { return VectorL10n.tr("Vector", "space_participants_action_ban") diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListItemViewData.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListItemViewData.swift index abf1c9510..8ad0c0225 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListItemViewData.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListItemViewData.swift @@ -21,14 +21,17 @@ class OptionListItemViewData { let detail: String? let image: UIImage? let accessoryImage: UIImage? + let enabled: Bool init(title: String? = nil, detail: String? = nil, image: UIImage? = nil, - accessoryImage: UIImage? = Asset.Images.chevron.image) { + accessoryImage: UIImage? = Asset.Images.chevron.image, + enabled: Bool = true) { self.title = title self.detail = detail self.image = image self.accessoryImage = accessoryImage + self.enabled = enabled } } diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewCell.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewCell.swift index 4b05a3755..8e5a8d93f 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewCell.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewCell.swift @@ -26,6 +26,12 @@ class OptionListViewCell: UITableViewCell, NibReusable { @IBOutlet private weak var detailLabel: UILabel! @IBOutlet private weak var selectionView: UIView! @IBOutlet private weak var chevronView: UIImageView! + + var isEnabled: Bool = true { + didSet { + self.contentView.alpha = isEnabled ? 1 : 0.3 + } + } // MARK: - Private @@ -42,10 +48,12 @@ class OptionListViewCell: UITableViewCell, NibReusable { } override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) + if isEnabled { + super.setSelected(selected, animated: animated) - UIView.animate(withDuration: animated ? 0.2 : 0.0) { - self.selectionView.transform = selected ? .init(scaleX: 0.95, y: 0.95) : .identity + UIView.animate(withDuration: animated ? 0.2 : 0.0) { + self.selectionView.transform = selected ? .init(scaleX: 0.95, y: 0.95) : .identity + } } } @@ -56,6 +64,7 @@ class OptionListViewCell: UITableViewCell, NibReusable { self.titleLabel.text = viewData.title self.detailLabel.text = viewData.detail self.chevronView.image = viewData.accessoryImage?.withRenderingMode(.alwaysTemplate) + self.isEnabled = viewData.enabled } func update(theme: Theme) { diff --git a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift index 640fea8b1..77ce45a0a 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/OptionList/OptionListViewController.swift @@ -207,6 +207,8 @@ extension OptionListViewController: UITableViewDataSource { extension OptionListViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - viewModel.process(viewAction: .selected(indexPath.row)) + if options[indexPath.row].enabled { + viewModel.process(viewAction: .selected(indexPath.row)) + } } } diff --git a/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift index 5ca84922d..8d86ebeca 100644 --- a/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/ParticipantsInviteModal/RoomParticipantsInviteModalCoordinatorBridgePresenter.swift @@ -82,17 +82,17 @@ final class RoomParticipantsInviteCoordinatorBridgePresenter: NSObject { } if let navigationController = viewController.navigationController { - navigationRouter = NavigationRouterStore.shared.findNavigationRouter(for: navigationController) ?? NavigationRouter(navigationController: navigationController) + self.navigationRouter = NavigationRouterStore.shared.findNavigationRouter(for: navigationController) ?? NavigationRouter(navigationController: navigationController) } else { - navigationRouter = nil + self.navigationRouter = nil } - if let spaceId = self.parentSpaceId, let spaceRoom = session?.spaceService.getSpace(withId: spaceId)?.room { - presentRoomSelector(between: room, and: spaceRoom) + if let spaceId = self.parentSpaceId, let spaceRoom = self.session?.spaceService.getSpace(withId: spaceId)?.room { + self.presentRoomSelector(between: room, and: spaceRoom) return } - pushContactsPicker(for: room) + self.pushContactsPicker(for: room) } // MARK: - Private @@ -104,26 +104,43 @@ final class RoomParticipantsInviteCoordinatorBridgePresenter: NSObject { detail: String? = nil, image: UIImage? = nil, room: MXRoom, - accessoryImage: UIImage? = Asset.Images.chevron.image) { + accessoryImage: UIImage? = Asset.Images.chevron.image, + enabled: Bool) { self.room = room - super.init(title: title, detail: detail, image: image, accessoryImage: accessoryImage) + super.init(title: title, detail: detail, image: image, accessoryImage: accessoryImage, enabled: enabled) } } private func presentRoomSelector(between room: MXRoom, and spaceRoom: MXRoom) { - let roomName = room.displayName ?? "" - let spaceName = spaceRoom.displayName ?? "" - roomOptions = [ - RoomOptionListItemViewData(title: VectorL10n.roomInviteToSpaceOptionTitle(spaceName), - detail: VectorL10n.roomInviteToSpaceOptionDetail(spaceName, roomName), - image: Asset.Images.addParticipants.image, room: spaceRoom), - RoomOptionListItemViewData(title: VectorL10n.roomInviteToRoomOptionTitle, - detail: VectorL10n.roomInviteToRoomOptionDetail(spaceName), - image: Asset.Images.addParticipants.image, room: room) - ] - optionListCoordinator = OptionListCoordinator(parameters: OptionListCoordinatorParameters(title: VectorL10n.roomIntroCellAddParticipantsAction, options: roomOptions, navigationRouter: navigationRouter)) - optionListCoordinator?.delegate = self - optionListCoordinator?.start() + canInvite(to: room) { [weak self] canInviteToRoom in + guard let self = self else { return } + + self.canInvite(to: spaceRoom) { [weak self] canInviteToSpace in + guard let self = self else { return } + + let roomName = room.displayName ?? "" + let spaceName = spaceRoom.displayName ?? "" + + self.roomOptions = [ + RoomOptionListItemViewData(title: VectorL10n.roomInviteToSpaceOptionTitle(spaceName), + detail: canInviteToSpace ? VectorL10n.roomInviteToSpaceOptionDetail(spaceName, roomName) : VectorL10n.spaceInviteNotEnoughPermission, + image: Asset.Images.addParticipants.image, room: spaceRoom, + accessoryImage: canInviteToSpace ? Asset.Images.chevron.image : nil, + enabled: canInviteToSpace), + RoomOptionListItemViewData(title: VectorL10n.roomInviteToRoomOptionTitle, + detail: canInviteToRoom ? VectorL10n.roomInviteToRoomOptionDetail(spaceName) : VectorL10n.roomInviteNotEnoughPermission, + image: Asset.Images.addParticipants.image, room: room, + accessoryImage: canInviteToRoom ? Asset.Images.chevron.image : nil, + enabled: canInviteToRoom) + ] + + let coordinator = OptionListCoordinator(parameters: OptionListCoordinatorParameters(title: VectorL10n.roomIntroCellAddParticipantsAction, options: self.roomOptions, navigationRouter: self.navigationRouter)) + coordinator.delegate = self + coordinator.start() + + self.optionListCoordinator = coordinator + } + } } private func pushContactsPicker(for room: MXRoom) { @@ -132,17 +149,48 @@ final class RoomParticipantsInviteCoordinatorBridgePresenter: NSObject { return } - let coordinator = ContactsPickerCoordinator(session: session, - room: room, - currentSearchText: currentSearchText, - actualParticipants: actualParticipants, - invitedParticipants: invitedParticipants, - userParticipant: userParticipant, - navigationRouter: navigationRouter) - coordinator.delegate = self - coordinator.start() - - self.contactPickerCoordinator = coordinator + canInvite(to: room) { [weak self] canInvite in + guard let self = self else { return } + + guard canInvite else { + let message = room.summary?.roomType == .space ? VectorL10n.spaceInviteNotEnoughPermission : VectorL10n.roomInviteNotEnoughPermission + let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) + self.navigationRouter?.present(alert, animated: true) + return + } + + let coordinator = ContactsPickerCoordinator(session: session, + room: room, + currentSearchText: self.currentSearchText, + actualParticipants: self.actualParticipants, + invitedParticipants: self.invitedParticipants, + userParticipant: self.userParticipant, + navigationRouter: self.navigationRouter) + coordinator.delegate = self + coordinator.start() + + self.contactPickerCoordinator = coordinator + } + } + + private func canInvite(to room: MXRoom, completion: @escaping (Bool) -> Void) { + guard let userId = self.session?.myUserId else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] canInvite: userId not found") + completion(false) + return + } + + room.state { roomState in + guard let powerLevels = roomState?.powerLevels else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] canInvite: powerLevels room found") + completion(false) + return + } + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + + completion(userPowerLevel >= powerLevels.invite) + } } } diff --git a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift index d74109024..2ca20ed8f 100644 --- a/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceMembers/SpaceMembersCoordinator.swift @@ -138,10 +138,27 @@ extension SpaceMembersCoordinator: SpaceMemberListCoordinatorDelegate { return } - let coordinator = ContactsPickerCoordinator(session: parameters.session, room: spaceRoom, currentSearchText: nil, actualParticipants: nil, invitedParticipants: nil, userParticipant: nil, navigationRouter: navigationRouter) - coordinator.delegate = self - coordinator.start() - childCoordinators.append(coordinator) + spaceRoom.state { [weak self] roomState in + guard let self = self else { return } + + guard let powerLevels = roomState?.powerLevels, let userId = self.parameters.session.myUserId else { + MXLog.error("[RoomParticipantsInviteCoordinatorBridgePresenter] present: powerLevels room found") + return + } + let userPowerLevel = powerLevels.powerLevelOfUser(withUserID: userId) + + guard userPowerLevel >= powerLevels.invite else { + let alert = UIAlertController(title: VectorL10n.spacesInvitePeople, message: VectorL10n.spaceInviteNotEnoughPermission, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default, handler: nil)) + self.navigationRouter.present(alert, animated: true) + return + } + + let coordinator = ContactsPickerCoordinator(session: self.parameters.session, room: spaceRoom, currentSearchText: nil, actualParticipants: nil, invitedParticipants: nil, userParticipant: nil, navigationRouter: self.navigationRouter) + coordinator.delegate = self + coordinator.start() + self.childCoordinators.append(coordinator) + } } }