SP3.2: Long press on rooms in space room lists #5232

- Added context menu for spaces and rooms in Explore rooms screen
This commit is contained in:
Gil Eluard 2022-01-17 16:44:40 +01:00
parent be4cd455f1
commit 2c5ed41f02
12 changed files with 579 additions and 0 deletions

View file

@ -73,6 +73,7 @@
"existing" = "Existing";
"add" = "Add";
"ok" = "OK";
"suggest" = "Suggest";
// Call Bar
"callbar_only_single_active" = "Tap to return to the call (%@)";
@ -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 theyre private and you need an invite.";

View file

@ -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"

View file

@ -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")
@ -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")

View file

@ -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)
}

View file

@ -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.

View file

@ -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)
}

View file

@ -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

View file

@ -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()
@ -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() {
@ -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
@ -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
}
}

View file

@ -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`
@ -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
}

View file

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Space Room Preview View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="SpaceRoomPreviewViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aYH-QK-OjX" customClass="SpaceAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="16" y="16" width="40" height="40"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="GFn-cD-R7B" customClass="RoomAvatarView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="16" y="16" width="40" height="40"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="gZ5-JI-BYT"/>
<constraint firstAttribute="height" constant="40" id="pSQ-i9-uTk"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="A message" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bxI-mu-qng">
<rect key="frame" x="72" y="27" width="326" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="space_user_icon" translatesAutoresizingMaskIntoConstraints="NO" id="csm-jO-Pai">
<rect key="frame" x="16" y="72" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="fyF-zL-p9K"/>
<constraint firstAttribute="height" constant="16" id="q4Q-vf-9NW"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="44" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AL2-9M-fyr">
<rect key="frame" x="37" y="72" width="17" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="space_room_icon" translatesAutoresizingMaskIntoConstraints="NO" id="GIX-Uh-g6e">
<rect key="frame" x="62" y="72" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="Sze-Z0-VGR"/>
<constraint firstAttribute="height" constant="16" id="aeM-ff-fuq"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="44" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eCr-BQ-Bl6">
<rect key="frame" x="82" y="72" width="17" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="B2R-Tu-sci">
<rect key="frame" x="107" y="70" width="77.5" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Description" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bKu-1p-huC">
<rect key="frame" x="4" y="2" width="69.5" height="16"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="bKu-1p-huC" firstAttribute="top" secondItem="B2R-Tu-sci" secondAttribute="top" constant="2" id="5Al-wx-tba"/>
<constraint firstAttribute="trailing" secondItem="bKu-1p-huC" secondAttribute="trailing" constant="4" id="Eu3-Ll-8kv"/>
<constraint firstItem="bKu-1p-huC" firstAttribute="leading" secondItem="B2R-Tu-sci" secondAttribute="leading" constant="4" id="Yjc-CW-w7C"/>
<constraint firstAttribute="bottom" secondItem="bKu-1p-huC" secondAttribute="bottom" constant="2" id="pZQ-47-Sfk"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMm-p7-j1q">
<rect key="frame" x="16" y="104" width="382" height="688"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="pMm-p7-j1q" secondAttribute="trailing" constant="16" id="2Af-eE-Tu4"/>
<constraint firstItem="csm-jO-Pai" firstAttribute="leading" secondItem="GFn-cD-R7B" secondAttribute="leading" id="3Xh-nX-t4c"/>
<constraint firstItem="AL2-9M-fyr" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="4wx-ap-x5O"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="trailing" secondItem="GFn-cD-R7B" secondAttribute="trailing" id="Ate-jA-69O"/>
<constraint firstItem="GFn-cD-R7B" firstAttribute="top" secondItem="bFg-jh-JZB" secondAttribute="top" constant="16" id="CMG-tR-oJg"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="leading" secondItem="GFn-cD-R7B" secondAttribute="leading" id="M3T-o5-tVS"/>
<constraint firstItem="AL2-9M-fyr" firstAttribute="leading" secondItem="csm-jO-Pai" secondAttribute="trailing" constant="5" id="MA1-5b-Dca"/>
<constraint firstItem="B2R-Tu-sci" firstAttribute="leading" secondItem="eCr-BQ-Bl6" secondAttribute="trailing" constant="8" id="Mvt-Yk-aAq"/>
<constraint firstItem="pMm-p7-j1q" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" constant="16" id="NX5-d9-kTf"/>
<constraint firstItem="GFn-cD-R7B" firstAttribute="leading" secondItem="bFg-jh-JZB" secondAttribute="leading" constant="16" id="PBB-Hd-49v"/>
<constraint firstItem="csm-jO-Pai" firstAttribute="top" secondItem="GFn-cD-R7B" secondAttribute="bottom" constant="16" id="RB7-V4-oJQ"/>
<constraint firstItem="eCr-BQ-Bl6" firstAttribute="firstBaseline" secondItem="AL2-9M-fyr" secondAttribute="firstBaseline" id="UvJ-AQ-bUZ"/>
<constraint firstItem="pMm-p7-j1q" firstAttribute="top" secondItem="csm-jO-Pai" secondAttribute="bottom" constant="16" id="WJv-U4-Db1"/>
<constraint firstItem="eCr-BQ-Bl6" firstAttribute="leading" secondItem="GIX-Uh-g6e" secondAttribute="trailing" constant="4" id="cZd-uR-2gY"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="top" secondItem="GFn-cD-R7B" secondAttribute="top" id="dTM-n9-XR4"/>
<constraint firstItem="GIX-Uh-g6e" firstAttribute="leading" secondItem="AL2-9M-fyr" secondAttribute="trailing" constant="8" id="gWI-u8-ywf"/>
<constraint firstItem="GIX-Uh-g6e" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="hBs-Ql-nyR"/>
<constraint firstItem="B2R-Tu-sci" firstAttribute="centerY" secondItem="csm-jO-Pai" secondAttribute="centerY" id="hmo-FG-eEO"/>
<constraint firstItem="aYH-QK-OjX" firstAttribute="bottom" secondItem="GFn-cD-R7B" secondAttribute="bottom" id="iOu-fu-sn4"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="trailing" secondItem="bxI-mu-qng" secondAttribute="trailing" constant="16" id="jPI-Ov-vRG"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="centerY" secondItem="GFn-cD-R7B" secondAttribute="centerY" id="mr6-ub-Bca"/>
<constraint firstItem="bxI-mu-qng" firstAttribute="leading" secondItem="GFn-cD-R7B" secondAttribute="trailing" constant="16" id="pLL-gp-Gkq"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="bottom" secondItem="pMm-p7-j1q" secondAttribute="bottom" constant="16" id="rRC-IJ-NME"/>
</constraints>
</view>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<modalPageSheetSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="avatarView" destination="GFn-cD-R7B" id="VCB-vM-Kyk"/>
<outlet property="membersLabel" destination="AL2-9M-fyr" id="Nij-ha-A7f"/>
<outlet property="roomsIconView" destination="GIX-Uh-g6e" id="lW2-zB-8wl"/>
<outlet property="roomsLabel" destination="eCr-BQ-Bl6" id="oA0-Ah-CAJ"/>
<outlet property="spaceAvatarView" destination="aYH-QK-OjX" id="M5D-r3-6HA"/>
<outlet property="spaceTagLabel" destination="bKu-1p-huC" id="0xv-Ic-puM"/>
<outlet property="spaceTagView" destination="B2R-Tu-sci" id="qTf-Vw-Ydz"/>
<outlet property="titleLabel" destination="bxI-mu-qng" id="pbX-aZ-inC"/>
<outlet property="topicLabel" destination="pMm-p7-j1q" id="EMb-CK-1bp"/>
<outlet property="topicLabelBottomMargin" destination="rRC-IJ-NME" id="NfZ-qp-NoN"/>
<outlet property="userIconView" destination="csm-jO-Pai" id="QAD-Zt-foS"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3198.5507246376815" y="-647.54464285714278"/>
</scene>
</scenes>
<resources>
<image name="space_room_icon" width="16" height="16"/>
<image name="space_user_icon" width="14" height="14"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,156 @@
// File created from ScreenTemplate
// $ createScreen.sh Spaces/SpaceRoomList/SpaceChildRoomDetail ShowSpaceChildRoomDetail
/*
Copyright 2021 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
final class SpaceRoomPreviewViewController: UIViewController {
// MARK: - Constants
private enum Constants {
static let popoverWidth: CGFloat = 300
}
// MARK: Outlets
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var avatarView: RoomAvatarView!
@IBOutlet private weak var spaceAvatarView: SpaceAvatarView!
@IBOutlet private weak var userIconView: UIImageView!
@IBOutlet private weak var membersLabel: UILabel!
@IBOutlet private weak var roomsIconView: UIImageView!
@IBOutlet private weak var roomsLabel: UILabel!
@IBOutlet private weak var topicLabel: UILabel!
@IBOutlet private weak var topicLabelBottomMargin: NSLayoutConstraint!
@IBOutlet private weak var spaceTagView: UIView!
@IBOutlet private weak var spaceTagLabel: UILabel!
// MARK: Private
private var theme: Theme!
private var roomInfo: MXSpaceChildInfo!
private var avatarViewData: AvatarViewDataProtocol!
// MARK: - Setup
class func instantiate(with roomInfo: MXSpaceChildInfo, avatarViewData: AvatarViewDataProtocol!) -> SpaceRoomPreviewViewController {
let viewController = StoryboardScene.SpaceRoomPreviewViewController.initialScene.instantiate()
viewController.roomInfo = roomInfo
viewController.avatarViewData = avatarViewData
viewController.theme = ThemeService.shared().theme
return viewController
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupView()
self.registerThemeServiceDidChangeThemeNotification()
self.update(theme: self.theme)
}
override var preferredContentSize: CGSize {
get {
return CGSize(width: Constants.popoverWidth, height: self.intrisicHeight(with: Constants.popoverWidth))
}
set {
super.preferredContentSize = newValue
}
}
// MARK: - Private
private func update(theme: Theme) {
self.theme = theme
self.view.backgroundColor = theme.headerBackgroundColor
if let navigationBar = self.navigationController?.navigationBar {
theme.applyStyle(onNavigationBar: navigationBar)
}
self.titleLabel.textColor = theme.textPrimaryColor
self.titleLabel.font = theme.fonts.title3SB
self.membersLabel.font = theme.fonts.caption1
self.membersLabel.textColor = theme.colors.tertiaryContent
self.topicLabel.font = theme.fonts.caption1
self.topicLabel.textColor = theme.colors.tertiaryContent
self.userIconView.tintColor = theme.colors.tertiaryContent
self.roomsIconView.tintColor = theme.colors.tertiaryContent
self.roomsLabel.font = theme.fonts.caption1
self.roomsLabel.textColor = theme.colors.tertiaryContent
self.spaceTagView.backgroundColor = theme.colors.quinaryContent
self.spaceTagLabel.font = theme.fonts.caption1
self.spaceTagLabel.textColor = theme.colors.tertiaryContent
}
private func registerThemeServiceDidChangeThemeNotification() {
NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}
@objc private func themeDidChange() {
self.update(theme: ThemeService.shared().theme)
}
private func setupView() {
self.titleLabel.text = roomInfo.displayName
self.spaceTagView.layer.masksToBounds = true
self.spaceTagView.layer.cornerRadius = 2
self.spaceTagView.isHidden = roomInfo.roomType != .space
self.spaceTagLabel.text = VectorL10n.spaceTag
self.avatarView.isHidden = roomInfo.roomType == .space
self.spaceAvatarView.isHidden = roomInfo.roomType != .space
if !self.avatarView.isHidden {
self.avatarView.fill(with: avatarViewData)
}
if !self.spaceAvatarView.isHidden {
self.spaceAvatarView.fill(with: avatarViewData)
}
self.membersLabel.text = roomInfo.activeMemberCount == 1 ? VectorL10n.roomTitleOneMember : VectorL10n.roomTitleMembers("\(roomInfo.activeMemberCount)")
if roomInfo.childrenIds.count == 1 {
self.roomsLabel.text = VectorL10n.spacesExploreRoomsOneRoom
} else {
self.roomsLabel.text = VectorL10n.spacesExploreRoomsRoomNumber("\(roomInfo.childrenIds.count)")
}
self.topicLabel.text = roomInfo.topic
topicLabelBottomMargin.constant = self.topicLabel.text.isEmptyOrNil ? 0 : 16
self.roomsIconView.isHidden = roomInfo.roomType != .space
self.roomsLabel.isHidden = roomInfo.roomType != .space
}
private func intrisicHeight(with width: CGFloat) -> CGFloat {
if self.topicLabel.text.isEmptyOrNil {
return self.topicLabel.frame.minY
}
let topicHeight = self.topicLabel.sizeThatFits(CGSize(width: width - self.topicLabel.frame.minX * 2, height: 0)).height
return self.topicLabel.frame.minY + topicHeight + 16
}
}

View file

@ -173,6 +173,18 @@ final class ExploreRoomCoordinator: NSObject, ExploreRoomCoordinatorType {
self.navigationRouter.popToRootModule(animated: animated)
}
}
private func pushInviteScreen(forRoomWithId roomId: String) {
guard let room = session.room(withRoomId: roomId) else {
MXLog.error("[ExploreRoomCoordinator] pushInviteScreen: room not found.")
return
}
let coordinator = ContactsPickerCoordinator(session: session, room: room, currentSearchText: nil, actualParticipants: nil, invitedParticipants: nil, userParticipant: nil, navigationRouter: navigationRouter)
coordinator.delegate = self
coordinator.start()
childCoordinators.append(coordinator)
}
}
// MARK: - ShowSpaceExploreRoomCoordinatorDelegate
@ -192,6 +204,14 @@ extension ExploreRoomCoordinator: SpaceExploreRoomCoordinatorDelegate {
func spaceExploreRoomCoordinatorDidAddRoom(_ coordinator: SpaceExploreRoomCoordinatorType) {
self.presentRoomCreation()
}
func spaceExploreRoomCoordinatorDidAddRoom(_ viewModel: SpaceExploreRoomCoordinatorType, openSettingsOf item: SpaceExploreRoomListItemViewData) {
self.navigateTo(roomWith: item.childInfo.childRoomId, showSettingsInitially: true, animated: true)
}
func spaceExploreRoomCoordinatorDidAddRoom(_ viewModel: SpaceExploreRoomCoordinatorType, inviteTo item: SpaceExploreRoomListItemViewData) {
self.pushInviteScreen(forRoomWithId: item.childInfo.childRoomId)
}
}
// MARK: - ShowSpaceChildRoomDetailCoordinator
@ -260,6 +280,7 @@ extension ExploreRoomCoordinator: UIAdaptivePresentationControllerDelegate {
}
// MARK: - RoomViewControllerDelegate
extension ExploreRoomCoordinator: RoomViewControllerDelegate {
func roomViewControllerShowRoomDetails(_ roomViewController: RoomViewController) {
// TODO:
@ -348,3 +369,17 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate {
}
}
// MARK: - ContactsPickerCoordinatorDelegate
extension ExploreRoomCoordinator: ContactsPickerCoordinatorDelegate {
func contactsPickerCoordinatorDidStartLoading(_ coordinator: ContactsPickerCoordinatorType) {
}
func contactsPickerCoordinatorDidEndLoading(_ coordinator: ContactsPickerCoordinatorType) {
}
func contactsPickerCoordinatorDidClose(_ coordinator: ContactsPickerCoordinatorType) {
childCoordinators.removeLast()
}
}