diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index d7a477951..ae51dc10f 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1673,6 +1673,7 @@ Tap the + to start adding people."; "spaces_home_space_title" = "Home"; "spaces_left_panel_title" = "Spaces"; "leave_space_message" = "Are you sure you want to leave %@?"; +"spaces_explore_rooms" = "Explore rooms"; // Mark: Avatar diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index c0d7fb716..2b7903b2d 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -234,6 +234,11 @@ internal enum StoryboardScene { internal static let searchableDirectoryViewController = SceneType(storyboard: ShowDirectoryViewController.self, identifier: "SearchableDirectoryViewController") } + internal enum ShowSpaceChildRoomDetailViewController: StoryboardType { + internal static let storyboardName = "ShowSpaceChildRoomDetailViewController" + + internal static let initialScene = InitialSceneType(storyboard: ShowSpaceChildRoomDetailViewController.self) + } internal enum ShowSpaceExploreRoomViewController: StoryboardType { internal static let storyboardName = "ShowSpaceExploreRoomViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 2e446bd3a..539907a6e 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -4714,6 +4714,10 @@ internal enum VectorL10n { internal static var spaceFeatureUnavailableTitle: String { return VectorL10n.tr("Vector", "space_feature_unavailable_title") } + /// Explore rooms + internal static var spacesExploreRooms: String { + return VectorL10n.tr("Vector", "spaces_explore_rooms") + } /// Home internal static var spacesHomeSpaceTitle: String { return VectorL10n.tr("Vector", "spaces_home_space_title") diff --git a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift index c32c367d9..47b72352d 100644 --- a/Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift +++ b/Riot/Modules/Spaces/SpaceMenu/SpaceMenuViewModel.swift @@ -35,7 +35,7 @@ class SpaceMenuViewModel: SpaceMenuViewModelType { var menuItems: [SpaceMenuListItemViewData] = [ // TODO: Add list item when implementing actions // SpaceMenuListItemViewData(actionId: ActionId.members.rawValue, style: .normal, title: VectorL10n.roomDetailsPeople, icon: UIImage(named: "space_menu_members")), - SpaceMenuListItemViewData(actionId: ActionId.rooms.rawValue, style: .normal, title: VectorL10n.groupDetailsRooms, icon: UIImage(named: "space_menu_rooms")), + SpaceMenuListItemViewData(actionId: ActionId.rooms.rawValue, style: .normal, title: VectorL10n.spacesExploreRooms, icon: UIImage(named: "space_menu_rooms")), SpaceMenuListItemViewData(actionId: ActionId.leave.rawValue, style: .destructive, title: VectorL10n.leave, icon: UIImage(named: "space_menu_leave")) ] diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinator.swift index 43ffca536..8009b5f45 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinator.swift @@ -62,8 +62,8 @@ final class ShowSpaceExploreRoomCoordinator: ShowSpaceExploreRoomCoordinatorType // MARK: - ShowSpaceExploreRoomViewModelCoordinatorDelegate extension ShowSpaceExploreRoomCoordinator: ShowSpaceExploreRoomViewModelCoordinatorDelegate { - func showSpaceExploreRoomViewModel(_ viewModel: ShowSpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData) { - self.delegate?.showSpaceExploreRoomCoordinator(self, didSelect: item) + func showSpaceExploreRoomViewModel(_ viewModel: ShowSpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) { + self.delegate?.showSpaceExploreRoomCoordinator(self, didSelect: item, from: sourceView) } func showSpaceExploreRoomViewModelDidCancel(_ viewModel: ShowSpaceExploreRoomViewModelType) { diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinatorType.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinatorType.swift index 5673eb008..bee854ed8 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinatorType.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomCoordinatorType.swift @@ -19,7 +19,7 @@ import Foundation protocol ShowSpaceExploreRoomCoordinatorDelegate: AnyObject { - func showSpaceExploreRoomCoordinator(_ coordinator: ShowSpaceExploreRoomCoordinatorType, didSelect item: SpaceExploreRoomListItemViewData) + func showSpaceExploreRoomCoordinator(_ coordinator: ShowSpaceExploreRoomCoordinatorType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) func showSpaceExploreRoomCoordinatorDidCancel(_ coordinator: ShowSpaceExploreRoomCoordinatorType) } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewAction.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewAction.swift index 1c48b93bc..68a3b74c6 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewAction.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewAction.swift @@ -21,7 +21,7 @@ import Foundation /// ShowSpaceExploreRoomViewController view actions exposed to view model enum ShowSpaceExploreRoomViewAction { case loadData - case complete(_ selectedItem: SpaceExploreRoomListItemViewData) + case complete(_ selectedItem: SpaceExploreRoomListItemViewData, _ sourceView: UIView?) case searchChanged(_ text: String?) case cancel } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.storyboard b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.storyboard index 718c05e41..3dcf75622 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.storyboard +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.storyboard @@ -25,7 +25,7 @@ - + @@ -38,7 +38,7 @@ - + diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.swift index 8926b049e..4c15c4d9b 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewController.swift @@ -211,7 +211,7 @@ extension ShowSpaceExploreRoomViewController: UITableViewDataSource { extension ShowSpaceExploreRoomViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - self.viewModel.process(viewAction: .complete(self.itemDataList[indexPath.row])) + self.viewModel.process(viewAction: .complete(self.itemDataList[indexPath.row], tableView.cellForRow(at: indexPath))) } } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModel.swift index d6c40d810..92a6a55d2 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModel.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModel.swift @@ -69,8 +69,8 @@ final class ShowSpaceExploreRoomViewModel: ShowSpaceExploreRoomViewModelType { switch viewAction { case .loadData: self.loadData() - case .complete(let selectedItem): - self.coordinatorDelegate?.showSpaceExploreRoomViewModel(self, didSelect: selectedItem) + case .complete(let selectedItem, let sourceView): + self.coordinatorDelegate?.showSpaceExploreRoomViewModel(self, didSelect: selectedItem, from: sourceView) case .cancel: self.cancelOperations() self.coordinatorDelegate?.showSpaceExploreRoomViewModelDidCancel(self) diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModelType.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModelType.swift index e23874660..090b45972 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModelType.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoom/ShowSpaceExploreRoomViewModelType.swift @@ -23,7 +23,7 @@ protocol ShowSpaceExploreRoomViewModelViewDelegate: AnyObject { } protocol ShowSpaceExploreRoomViewModelCoordinatorDelegate: AnyObject { - func showSpaceExploreRoomViewModel(_ viewModel: ShowSpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData) + func showSpaceExploreRoomViewModel(_ viewModel: ShowSpaceExploreRoomViewModelType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) func showSpaceExploreRoomViewModelDidCancel(_ viewModel: ShowSpaceExploreRoomViewModelType) } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift index 06c78dc1c..5c17f409d 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift @@ -28,6 +28,11 @@ final class ExploreRoomCoordinator: ExploreRoomCoordinatorType { private let navigationRouter: NavigationRouterType private let session: MXSession private let spaceId: String + private weak var roomDetailCoordinator: ShowSpaceChildRoomDetailCoordinator? + + private lazy var slidingModalPresenter: SlidingModalPresenter = { + return SlidingModalPresenter() + }() // MARK: Public @@ -70,6 +75,30 @@ final class ExploreRoomCoordinator: ExploreRoomCoordinatorType { } } + func presentRoom(with item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) { + if let currentCoordinator = self.roomDetailCoordinator { + self.remove(childCoordinator: currentCoordinator) + } + + let coordinator = self.createShowSpaceRoomDetailCoordinator(session: self.session, childInfo: item.childInfo) + coordinator.start() + self.add(childCoordinator: coordinator) + self.roomDetailCoordinator = coordinator + + if UIDevice.current.isPhone { + slidingModalPresenter.present(coordinator.toSlidingPresentable(), from: self.navigationRouter.toPresentable(), animated: true, completion: nil) + } else { + let viewController = coordinator.toPresentable() + viewController.modalPresentationStyle = .popover + if let sourceView = sourceView, let popoverPresentationController = viewController.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + popoverPresentationController.sourceRect = sourceView.bounds + } + + self.navigationRouter.present(viewController, animated: true) + } + } + // MARK: - Private methods private func createShowSpaceExploreRoomCoordinator(session: MXSession, spaceId: String, spaceName: String?) -> ShowSpaceExploreRoomCoordinator { @@ -77,15 +106,40 @@ final class ExploreRoomCoordinator: ExploreRoomCoordinatorType { coordinator.delegate = self return coordinator } + + private func createShowSpaceRoomDetailCoordinator(session: MXSession, childInfo: MXSpaceChildInfo) -> ShowSpaceChildRoomDetailCoordinator { + let coordinator = ShowSpaceChildRoomDetailCoordinator(session: session, childInfo: childInfo) + coordinator.delegate = self + return coordinator + } } // MARK: - ShowSpaceExploreRoomCoordinatorDelegate extension ExploreRoomCoordinator: ShowSpaceExploreRoomCoordinatorDelegate { - func showSpaceExploreRoomCoordinator(_ coordinator: ShowSpaceExploreRoomCoordinatorType, didSelect item: SpaceExploreRoomListItemViewData) { - self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: item) + func showSpaceExploreRoomCoordinator(_ coordinator: ShowSpaceExploreRoomCoordinatorType, didSelect item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) { + self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: item, from: sourceView) } func showSpaceExploreRoomCoordinatorDidCancel(_ coordinator: ShowSpaceExploreRoomCoordinatorType) { - self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: nil) + self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: nil, from: nil) + } +} + +// MARK: - ShowSpaceChildRoomDetailCoordinator +extension ExploreRoomCoordinator: ShowSpaceChildRoomDetailCoordinatorDelegate { + func showSpaceChildRoomDetailCoordinator(_ coordinator: ShowSpaceChildRoomDetailCoordinatorType, didCompleteWithUserDisplayName userDisplayName: String?) { + if UIDevice.current.isPhone { + self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: nil, from: nil) + } else { + self.navigationRouter.toPresentable().dismiss(animated: true) { + if let lastCoordinator = self.roomDetailCoordinator { + self.remove(childCoordinator: lastCoordinator) + } + } + } + } + + func showSpaceChildRoomDetailCoordinatorDidCancel(_ coordinator: ShowSpaceChildRoomDetailCoordinatorType) { + self.delegate?.exploreRoomCoordinatorDidComplete(self, withSelectedIem: nil, from: nil) } } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorBridgePresenter.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorBridgePresenter.swift index 14865d443..e06894b52 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorBridgePresenter.swift @@ -81,21 +81,23 @@ final class ExploreRoomCoordinatorBridgePresenter: NSObject { // MARK: - Private - func navigate(to item: SpaceExploreRoomListItemViewData) { + func navigate(to item: SpaceExploreRoomListItemViewData, from sourceView: UIView?) { if item.childInfo.roomType == .space { self.coordinator?.pushSpace(with: item) + } else if item.childInfo.roomType == .room { + self.coordinator?.presentRoom(with: item, from: sourceView) } } } // MARK: - ExploreRoomCoordinatorDelegate extension ExploreRoomCoordinatorBridgePresenter: ExploreRoomCoordinatorDelegate { - func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType, withSelectedIem item: SpaceExploreRoomListItemViewData?) { + func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType, withSelectedIem item: SpaceExploreRoomListItemViewData?, from sourceView: UIView?) { guard let item = item else { self.delegate?.exploreRoomCoordinatorBridgePresenterDelegateDidComplete(self) return } - self.navigate(to: item) + self.navigate(to: item, from: sourceView) } } diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorType.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorType.swift index 48c2f2e43..5d806d080 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorType.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinatorType.swift @@ -19,7 +19,7 @@ import Foundation protocol ExploreRoomCoordinatorDelegate: AnyObject { - func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType, withSelectedIem item: SpaceExploreRoomListItemViewData?) + func exploreRoomCoordinatorDidComplete(_ coordinator: ExploreRoomCoordinatorType, withSelectedIem item: SpaceExploreRoomListItemViewData?, from sourceView: UIView?) } /// `ExploreRoomCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinator.swift new file mode 100644 index 000000000..e05d4752f --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinator.swift @@ -0,0 +1,77 @@ +// 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 Foundation +import UIKit + +final class ShowSpaceChildRoomDetailCoordinator: ShowSpaceChildRoomDetailCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let childInfo: MXSpaceChildInfo + private var showSpaceChildRoomDetailViewModel: ShowSpaceChildRoomDetailViewModelType + private let showSpaceChildRoomDetailViewController: ShowSpaceChildRoomDetailViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: ShowSpaceChildRoomDetailCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, childInfo: MXSpaceChildInfo) { + self.session = session + self.childInfo = childInfo + + let showSpaceChildRoomDetailViewModel = ShowSpaceChildRoomDetailViewModel(session: self.session, childInfo: childInfo) + let showSpaceChildRoomDetailViewController = ShowSpaceChildRoomDetailViewController.instantiate(with: showSpaceChildRoomDetailViewModel) + self.showSpaceChildRoomDetailViewModel = showSpaceChildRoomDetailViewModel + self.showSpaceChildRoomDetailViewController = showSpaceChildRoomDetailViewController + } + + // MARK: - Public methods + + func start() { + self.showSpaceChildRoomDetailViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.showSpaceChildRoomDetailViewController + } + + func toSlidingPresentable() -> UIViewController & SlidingModalPresentable { + return self.showSpaceChildRoomDetailViewController + } +} + +// MARK: - ShowSpaceChildRoomDetailViewModelCoordinatorDelegate +extension ShowSpaceChildRoomDetailCoordinator: ShowSpaceChildRoomDetailViewModelCoordinatorDelegate { + + func showSpaceChildRoomDetailViewModel(_ viewModel: ShowSpaceChildRoomDetailViewModelType, didCompleteWithUserDisplayName userDisplayName: String?) { + self.delegate?.showSpaceChildRoomDetailCoordinator(self, didCompleteWithUserDisplayName: userDisplayName) + } + + func showSpaceChildRoomDetailViewModelDidCancel(_ viewModel: ShowSpaceChildRoomDetailViewModelType) { + self.delegate?.showSpaceChildRoomDetailCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinatorType.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinatorType.swift new file mode 100644 index 000000000..8b354747b --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailCoordinatorType.swift @@ -0,0 +1,29 @@ +// 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 Foundation + +protocol ShowSpaceChildRoomDetailCoordinatorDelegate: AnyObject { + func showSpaceChildRoomDetailCoordinator(_ coordinator: ShowSpaceChildRoomDetailCoordinatorType, didCompleteWithUserDisplayName userDisplayName: String?) + func showSpaceChildRoomDetailCoordinatorDidCancel(_ coordinator: ShowSpaceChildRoomDetailCoordinatorType) +} + +/// `ShowSpaceChildRoomDetailCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol ShowSpaceChildRoomDetailCoordinatorType: Coordinator, Presentable { + var delegate: ShowSpaceChildRoomDetailCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewAction.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewAction.swift new file mode 100644 index 000000000..01bf52084 --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewAction.swift @@ -0,0 +1,26 @@ +// 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 Foundation + +/// ShowSpaceChildRoomDetailViewController view actions exposed to view model +enum ShowSpaceChildRoomDetailViewAction { + case loadData + case complete + case cancel +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.storyboard b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.storyboard new file mode 100644 index 000000000..ffecd409b --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.storyboard @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.swift new file mode 100644 index 000000000..9344b68cd --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewController.swift @@ -0,0 +1,193 @@ +// 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 ShowSpaceChildRoomDetailViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let aConstant: Int = 666 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var doneButton: UIButton! + @IBOutlet private weak var avatarView: RoomAvatarView! + @IBOutlet private weak var userIconView: UIImageView! + @IBOutlet private weak var membersLabel: UILabel! + @IBOutlet private weak var topicLabel: UILabel! + + // MARK: Private + + private var viewModel: ShowSpaceChildRoomDetailViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: ShowSpaceChildRoomDetailViewModelType) -> ShowSpaceChildRoomDetailViewController { + let viewController = StoryboardScene.ShowSpaceChildRoomDetailViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override var preferredContentSize: CGSize { + get { + return CGSize(width: 320, height: 300) + } + 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.calloutSB + self.doneButton.backgroundColor = theme.colors.accent + self.doneButton.tintColor = theme.colors.background + self.doneButton.setTitleColor(theme.colors.background, for: .normal) + 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 + } + + 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 setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.roomDetailsTitle + self.doneButton.layer.masksToBounds = true + self.doneButton.layer.cornerRadius = 8.0 + self.doneButton.setTitle(VectorL10n.join, for: .normal) + } + + private func render(viewState: ShowSpaceChildRoomDetailViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let roomInfo, let avatarViewData): + self.renderLoaded(roomInfo: roomInfo, avatarViewData: avatarViewData) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(roomInfo: MXSpaceChildInfo, avatarViewData: AvatarViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.titleLabel.text = roomInfo.name + self.avatarView.fill(with: avatarViewData) + self.membersLabel.text = "\(roomInfo.activeMemberCount)" + self.topicLabel.text = roomInfo.topic + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + @IBAction private func doneButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .complete) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - ShowSpaceChildRoomDetailViewModelViewDelegate +extension ShowSpaceChildRoomDetailViewController: ShowSpaceChildRoomDetailViewModelViewDelegate { + + func showSpaceChildRoomDetailViewModel(_ viewModel: ShowSpaceChildRoomDetailViewModelType, didUpdateViewState viewSate: ShowSpaceChildRoomDetailViewState) { + self.render(viewState: viewSate) + } +} + +// MARK: - SlidingModalPresentable + +extension ShowSpaceChildRoomDetailViewController: SlidingModalPresentable { + + func allowsDismissOnBackgroundTap() -> Bool { + return true + } + + func layoutHeightFittingWidth(_ width: CGFloat) -> CGFloat { + return self.preferredContentSize.height + } + +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModel.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModel.swift new file mode 100644 index 000000000..437a13e93 --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModel.swift @@ -0,0 +1,77 @@ +// 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 Foundation + +final class ShowSpaceChildRoomDetailViewModel: ShowSpaceChildRoomDetailViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let childInfo: MXSpaceChildInfo + + private var currentOperation: MXHTTPOperation? + private var userDisplayName: String? + + // MARK: Public + + weak var viewDelegate: ShowSpaceChildRoomDetailViewModelViewDelegate? + weak var coordinatorDelegate: ShowSpaceChildRoomDetailViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, childInfo: MXSpaceChildInfo) { + self.session = session + self.childInfo = childInfo + } + + deinit { + self.cancelOperations() + } + + // MARK: - Public + + func process(viewAction: ShowSpaceChildRoomDetailViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .complete: + self.coordinatorDelegate?.showSpaceChildRoomDetailViewModel(self, didCompleteWithUserDisplayName: self.userDisplayName) + case .cancel: + self.cancelOperations() + self.coordinatorDelegate?.showSpaceChildRoomDetailViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + let avatarViewData = AvatarViewData(avatarUrl: self.childInfo.avatarUrl, mediaManager: self.session.mediaManager, fallbackImage: .matrixItem(self.childInfo.childRoomId, self.childInfo.name)) + self.update(viewState: .loaded(self.childInfo, avatarViewData)) + } + + private func update(viewState: ShowSpaceChildRoomDetailViewState) { + self.viewDelegate?.showSpaceChildRoomDetailViewModel(self, didUpdateViewState: viewState) + } + + private func cancelOperations() { + self.currentOperation?.cancel() + } +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModelType.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModelType.swift new file mode 100644 index 000000000..f9268789a --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewModelType.swift @@ -0,0 +1,37 @@ +// 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 Foundation + +protocol ShowSpaceChildRoomDetailViewModelViewDelegate: AnyObject { + func showSpaceChildRoomDetailViewModel(_ viewModel: ShowSpaceChildRoomDetailViewModelType, didUpdateViewState viewSate: ShowSpaceChildRoomDetailViewState) +} + +protocol ShowSpaceChildRoomDetailViewModelCoordinatorDelegate: AnyObject { + func showSpaceChildRoomDetailViewModel(_ viewModel: ShowSpaceChildRoomDetailViewModelType, didCompleteWithUserDisplayName userDisplayName: String?) + func showSpaceChildRoomDetailViewModelDidCancel(_ viewModel: ShowSpaceChildRoomDetailViewModelType) +} + +/// Protocol describing the view model used by `ShowSpaceChildRoomDetailViewController` +protocol ShowSpaceChildRoomDetailViewModelType { + + var viewDelegate: ShowSpaceChildRoomDetailViewModelViewDelegate? { get set } + var coordinatorDelegate: ShowSpaceChildRoomDetailViewModelCoordinatorDelegate? { get set } + + func process(viewAction: ShowSpaceChildRoomDetailViewAction) +} diff --git a/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewState.swift b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewState.swift new file mode 100644 index 000000000..4e84b7c5b --- /dev/null +++ b/Riot/Modules/Spaces/SpaceRoomList/SpaceChildRoomDetail/ShowSpaceChildRoomDetailViewState.swift @@ -0,0 +1,26 @@ +// 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 Foundation + +/// ShowSpaceChildRoomDetailViewController view state +enum ShowSpaceChildRoomDetailViewState { + case loading + case loaded(_ roomInfo: MXSpaceChildInfo, _ avatarViewData: AvatarViewData) + case error(Error) +}