mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
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:
parent
be4cd455f1
commit
2c5ed41f02
12 changed files with 579 additions and 0 deletions
|
@ -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 they’re private and you need an invite.";
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue