Skip to content

Commit

Permalink
SP3.2: Long press on rooms in space room lists #5232
Browse files Browse the repository at this point in the history
- Added context menu for spaces and rooms in Explore rooms screen
  • Loading branch information
gileluard committed Jan 17, 2022
1 parent be4cd45 commit 2c5ed41
Show file tree
Hide file tree
Showing 12 changed files with 579 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"existing" = "Existing";
"add" = "Add";
"ok" = "OK";
"suggest" = "Suggest";

// Call Bar
"callbar_only_single_active" = "Tap to return to the call (%@)";
Expand Down Expand Up @@ -1803,6 +1804,8 @@ Tap the + to start adding people.";
"leave_space_and_all_rooms_action" = "Leave all rooms and spaces";
"spaces_explore_rooms" = "Explore rooms";
"spaces_suggested_room" = "Suggested";
"spaces_explore_rooms_room_number" = "%@ rooms";
"spaces_explore_rooms_one_room" = "1 room";
"space_tag" = "space";
"spaces_empty_space_title" = "This space has no rooms (yet)";
"spaces_empty_space_detail" = "Some rooms may be hidden because they’re private and you need an invite.";
Expand Down
5 changes: 5 additions & 0 deletions Riot/Generated/Storyboards.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@ internal enum StoryboardScene {

internal static let initialScene = InitialSceneType<Riot.SpaceMenuViewController>(storyboard: SpaceMenuViewController.self)
}
internal enum SpaceRoomPreviewViewController: StoryboardType {
internal static let storyboardName = "SpaceRoomPreviewViewController"

internal static let initialScene = InitialSceneType<Riot.SpaceRoomPreviewViewController>(storyboard: SpaceRoomPreviewViewController.self)
}
internal enum TemplateScreenViewController: StoryboardType {
internal static let storyboardName = "TemplateScreenViewController"

Expand Down
12 changes: 12 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5563,6 +5563,14 @@ public class VectorL10n: NSObject {
public static var spacesExploreRooms: String {
return VectorL10n.tr("Vector", "spaces_explore_rooms")
}
/// 1 room
public static var spacesExploreRoomsOneRoom: String {
return VectorL10n.tr("Vector", "spaces_explore_rooms_one_room")
}
/// %@ rooms
public static func spacesExploreRoomsRoomNumber(_ p1: String) -> String {
return VectorL10n.tr("Vector", "spaces_explore_rooms_room_number", p1)
}
/// Home
public static var spacesHomeSpaceTitle: String {
return VectorL10n.tr("Vector", "spaces_home_space_title")
Expand Down Expand Up @@ -5615,6 +5623,10 @@ public class VectorL10n: NSObject {
public static var storeShortDescription: String {
return VectorL10n.tr("Vector", "store_short_description")
}
/// Suggest
public static var suggest: String {
return VectorL10n.tr("Vector", "suggest")
}
/// Switch
public static var `switch`: String {
return VectorL10n.tr("Vector", "switch")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ final class SpaceExploreRoomCoordinator: SpaceExploreRoomCoordinatorType {

// MARK: - SpaceExploreRoomViewModelCoordinatorDelegate
extension SpaceExploreRoomCoordinator: SpaceExploreRoomViewModelCoordinatorDelegate {
func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, openSettingsOf item: SpaceExploreRoomListItemViewData) {
self.delegate?.spaceExploreRoomCoordinatorDidAddRoom(self, openSettingsOf: item)
}

func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, inviteTo item: SpaceExploreRoomListItemViewData) {
self.delegate?.spaceExploreRoomCoordinatorDidAddRoom(self, inviteTo: item)
}

func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) {
self.delegate?.spaceExploreRoomCoordinator(self, didSelect: item, from: sourceView)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ protocol SpaceExploreRoomCoordinatorDelegate: AnyObject {
func spaceExploreRoomCoordinator(_ coordinator: SpaceExploreRoomCoordinatorType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?)
func spaceExploreRoomCoordinatorDidCancel(_ coordinator: SpaceExploreRoomCoordinatorType)
func spaceExploreRoomCoordinatorDidAddRoom(_ coordinator: SpaceExploreRoomCoordinatorType)
func spaceExploreRoomCoordinatorDidAddRoom(_ viewModel: SpaceExploreRoomCoordinatorType, openSettingsOf item: SpaceExploreRoomListItemViewData)
func spaceExploreRoomCoordinatorDidAddRoom(_ viewModel: SpaceExploreRoomCoordinatorType, inviteTo item: SpaceExploreRoomListItemViewData)
}

/// `SpaceExploreRoomCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ enum SpaceExploreRoomViewAction {
case searchChanged(_ text: String?)
case cancel
case addRoom
case inviteTo(_ item: SpaceExploreRoomListItemViewData)
case revertSuggestion(_ item: SpaceExploreRoomListItemViewData)
case settings(_ item: SpaceExploreRoomListItemViewData)
case removeChild(_ item: SpaceExploreRoomListItemViewData)
case join(_ item: SpaceExploreRoomListItemViewData)
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ extension SpaceExploreRoomViewController: UITableViewDelegate {
self.viewModel.process(viewAction: .loadData)
}
}

@available(iOS 13.0, *)
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let viewData = self.itemDataList[indexPath.row]
return UIContextMenuConfiguration(identifier: nil) {
return SpaceRoomPreviewViewController.instantiate(with: viewData.childInfo, avatarViewData: viewData.avatarViewData)
} actionProvider: { suggestedActions in
return self.viewModel.contextMenu(for: self.itemDataList[indexPath.row])
}
}
}

// MARK: - SpaceExploreRoomViewModelViewDelegate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType {
private var nextBatch: String?
private var rootSpaceChildInfo: MXSpaceChildInfo?

private var spaceRoom: MXRoom?
private var powerLevels: MXRoomPowerLevels?
private var oneSelfPowerLevel: Int?

private var itemDataList: [SpaceExploreRoomListItemViewData] = [] {
didSet {
self.updateFilteredItemList()
Expand Down Expand Up @@ -91,9 +95,39 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType {
self.searchKeyword = newText
case .addRoom:
self.coordinatorDelegate?.spaceExploreRoomViewModelDidAddRoom(self)
case .inviteTo(let item):
self.coordinatorDelegate?.spaceExploreRoomViewModel(self, inviteTo: item)
case .revertSuggestion(let item):
setChild(withRoomId: item.childInfo.childRoomId, suggested: !item.childInfo.suggested)
case .settings(let item):
self.coordinatorDelegate?.spaceExploreRoomViewModel(self, openSettingsOf: item)
case .removeChild(let item):
removeChild(withRoomId: item.childInfo.childRoomId)
case .join(let item):
joinRoom(withRoomId: item.childInfo.childRoomId)
}
}

@available(iOS 13.0, *)
func contextMenu(for itemData: SpaceExploreRoomListItemViewData) -> UIMenu {
let canSendSpaceStateEvents: Bool
if let powerLevels = self.powerLevels, let oneSelfPowerLevel = self.oneSelfPowerLevel {
let minimumPowerLevel = powerLevels.minimumPowerLevel(forNotifications: kMXEventTypeStringRoomJoinRules, defaultPower: powerLevels.stateDefault)
canSendSpaceStateEvents = oneSelfPowerLevel >= minimumPowerLevel
} else {
canSendSpaceStateEvents = false
}

let roomSummary = session.roomSummary(withRoomId: itemData.childInfo.childRoomId)
let isJoined = roomSummary?.isJoined ?? false

if itemData.childInfo.roomType == .space {
return contextMenu(forSpace: itemData, isJoined: isJoined, canSendSpaceStateEvents: canSendSpaceStateEvents)
} else {
return contextMenu(forRoom: itemData, isJoined: isJoined, canSendSpaceStateEvents: canSendSpaceStateEvents)
}
}

// MARK: - Private

private func loadData() {
Expand All @@ -109,6 +143,15 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType {
self.update(viewState: .loading)
}

self.spaceRoom = self.session.spaceService.getSpace(withId: self.spaceId)?.room
if let spaceRoom = self.spaceRoom {
spaceRoom.state { roomState in
self.powerLevels = roomState?.powerLevels
self.oneSelfPowerLevel = self.powerLevels?.powerLevelOfUser(withUserID: self.session.myUserId)

}
}

self.currentOperation = self.session.spaceService.getSpaceChildrenForSpace(withId: self.spaceId, suggestedOnly: false, limit: nil, maxDepth: 1, paginationToken: self.nextBatch, completion: { [weak self] response in
guard let self = self else {
return
Expand Down Expand Up @@ -170,4 +213,154 @@ final class SpaceExploreRoomViewModel: SpaceExploreRoomViewModelType {
return (itemData.childInfo.name?.lowercased().contains(searchKeyword) ?? false) || (itemData.childInfo.topic?.lowercased().contains(searchKeyword) ?? false)
})
}

private func setChild(withRoomId roomId: String, suggested: Bool) {
guard let space = session.spaceService.getSpace(withId: spaceId) else {
return
}

self.update(viewState: .loading)
space.setChild(withRoomId: roomId, suggested: suggested) { [weak self] response in
guard let self = self else { return }

switch response {
case .success:
self.itemDataList = self.itemDataList.compactMap({ item in
if item.childInfo.childRoomId != roomId {
return item
}

let childInfo = MXSpaceChildInfo(
childRoomId: item.childInfo.childRoomId,
isKnown: item.childInfo.isKnown,
roomTypeString: item.childInfo.roomTypeString,
roomType: item.childInfo.roomType,
name: item.childInfo.name,
topic: item.childInfo.topic,
canonicalAlias: item.childInfo.canonicalAlias,
avatarUrl: item.childInfo.avatarUrl,
activeMemberCount: item.childInfo.activeMemberCount,
autoJoin: item.childInfo.autoJoin,
suggested: suggested,
childrenIds: item.childInfo.childrenIds)
return SpaceExploreRoomListItemViewData(childInfo: childInfo, avatarViewData: item.avatarViewData)
})
self.update(viewState: .loaded(self.filteredItemDataList, self.nextBatch != nil && (self.searchKeyword ?? "").isEmpty))
case .failure(let error):
self.update(viewState: .error(error))
}
}
}

private func removeChild(withRoomId roomId: String) {
guard let space = session.spaceService.getSpace(withId: spaceId) else {
return
}

self.update(viewState: .loading)
space.removeChild(roomId: roomId) { [weak self] response in
guard let self = self else { return }

switch response {
case .success:
self.itemDataList = self.itemDataList.filter { $0.childInfo.childRoomId != roomId }
self.update(viewState: .loaded(self.filteredItemDataList, self.nextBatch != nil && (self.searchKeyword ?? "").isEmpty))
case .failure(let error):
self.update(viewState: .error(error))
}
}
}

private func joinRoom(withRoomId roomId: String) {
self.update(viewState: .loading)
self.session.joinRoom(roomId) { [weak self] response in
guard let self = self else { return }
switch response {
case .success:
self.update(viewState: .loaded(self.filteredItemDataList, self.nextBatch != nil && (self.searchKeyword ?? "").isEmpty))
case .failure(let error):
self.update(viewState: .error(error))
}
}
}


// MARK: - ContextMenu

@available(iOS 13.0, *)
private func contextMenu(forRoom itemData: SpaceExploreRoomListItemViewData, isJoined: Bool, canSendSpaceStateEvents: Bool) -> UIMenu {
return UIMenu(children: [
inviteAction(for: itemData, isJoined: isJoined),
suggestAction(for: itemData, canSendSpaceStateEvents: canSendSpaceStateEvents),
isJoined ? settingsAction(for: itemData, isJoined: isJoined) :joinAction(for: itemData, isJoined: isJoined),
removeAction(for: itemData, canSendSpaceStateEvents: canSendSpaceStateEvents)
])
}

@available(iOS 13.0, *)
private func contextMenu(forSpace itemData: SpaceExploreRoomListItemViewData, isJoined: Bool, canSendSpaceStateEvents: Bool) -> UIMenu {
return UIMenu(children: [
inviteAction(for: itemData, isJoined: isJoined),
suggestAction(for: itemData, canSendSpaceStateEvents: canSendSpaceStateEvents),
isJoined ? settingsAction(for: itemData, isJoined: isJoined) :joinAction(for: itemData, isJoined: isJoined),
removeAction(for: itemData, canSendSpaceStateEvents: canSendSpaceStateEvents)
])
}

@available(iOS 13.0, *)
private func inviteAction(for itemData: SpaceExploreRoomListItemViewData, isJoined: Bool) -> UIAction {
let action = UIAction(title: VectorL10n.invite, image: Asset.Images.spaceInviteUser.image) { action in
self.process(viewAction: .inviteTo(itemData))
}
if !isJoined {
action.attributes = .disabled
}
return action
}

@available(iOS 13.0, *)
private func suggestAction(for itemData: SpaceExploreRoomListItemViewData, canSendSpaceStateEvents: Bool) -> UIAction {
let action = UIAction(title: itemData.childInfo.suggested ? VectorL10n.spacesSuggestedRoom : VectorL10n.suggest) { action in
self.process(viewAction: .revertSuggestion(itemData))
}
action.state = itemData.childInfo.suggested ? .on : .off
if !canSendSpaceStateEvents {
action.attributes = .disabled
}
return action
}

@available(iOS 13.0, *)
private func settingsAction(for itemData: SpaceExploreRoomListItemViewData, isJoined: Bool) -> UIAction {
let action = UIAction(title: VectorL10n.roomDetailsSettings, image: Asset.Images.settingsIcon.image) { action in
self.process(viewAction: .settings(itemData))
}
if !isJoined {
action.attributes = .disabled
}
return action
}

@available(iOS 13.0, *)
private func joinAction(for itemData: SpaceExploreRoomListItemViewData, isJoined: Bool) -> UIAction {
let action = UIAction(title: VectorL10n.join) { action in
self.process(viewAction: .join(itemData))
}
if !isJoined {
action.attributes = .disabled
}
return action
}

@available(iOS 13.0, *)
private func removeAction(for itemData: SpaceExploreRoomListItemViewData, canSendSpaceStateEvents: Bool) -> UIAction {
let action = UIAction(title: VectorL10n.remove, image: Asset.Images.roomContextMenuDelete.image) { action in
self.process(viewAction: .removeChild(itemData))
}
action.attributes = .destructive
if !canSendSpaceStateEvents {
action.attributes = .disabled
}
return action
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ protocol SpaceExploreRoomViewModelCoordinatorDelegate: AnyObject {
func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?)
func spaceExploreRoomViewModelDidCancel(_ viewModel: SpaceExploreRoomViewModelType)
func spaceExploreRoomViewModelDidAddRoom(_ viewModel: SpaceExploreRoomViewModelType)
func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, openSettingsOf item: SpaceExploreRoomListItemViewData)
func spaceExploreRoomViewModel(_ viewModel: SpaceExploreRoomViewModelType, inviteTo item: SpaceExploreRoomListItemViewData)
}

/// Protocol describing the view model used by `SpaceExploreRoomViewController`
Expand All @@ -35,4 +37,6 @@ protocol SpaceExploreRoomViewModelType {
var coordinatorDelegate: SpaceExploreRoomViewModelCoordinatorDelegate? { get set }

func process(viewAction: SpaceExploreRoomViewAction)
@available(iOS 13.0, *)
func contextMenu(for itemData: SpaceExploreRoomListItemViewData) -> UIMenu
}
Loading

0 comments on commit 2c5ed41

Please sign in to comment.