Add RoomCoordinator.

This commit is contained in:
SBiOSoftWhare
2021-10-05 09:41:04 +02:00
parent 943d35afb1
commit 1c0d9d222e
5 changed files with 516 additions and 0 deletions
+235
View File
@@ -0,0 +1,235 @@
// File created from ScreenTemplate
// $ createScreen.sh Room Room
/*
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 RoomCoordinator: NSObject, RoomCoordinatorProtocol {
// MARK: - Properties
// MARK: Private
private let parameters: RoomCoordinatorParameters
private let roomViewController: RoomViewController
private let activityIndicatorPresenter: ActivityIndicatorPresenterType
private var selectedEventId: String?
private var roomDataSourceManager: MXKRoomDataSourceManager {
return MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
}
/// Indicate true if the Coordinator has started once
private var hasStartedOnce: Bool {
return self.roomViewController.delegate != nil
}
private var navigationRouter: NavigationRouterType? {
var finalNavigationRouter: NavigationRouterType?
if let navigationRouter = self.parameters.navigationRouter {
finalNavigationRouter = navigationRouter
} else if let navigationRouterStore = self.parameters.navigationRouterStore, let currentNavigationController = self.roomViewController.navigationController {
// If no navigationRouter has been provided, try to get the navigation router from the current RoomViewController navigation controller if exists
finalNavigationRouter = navigationRouterStore.getOrCreateNavigationRouter(for: currentNavigationController)
}
return finalNavigationRouter
}
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
weak var delegate: RoomCoordinatorDelegate?
var canReleaseRoomDataSource: Bool {
// If the displayed data is not a preview, let the manager release the room data source
// (except if the view controller has the room data source ownership).
return self.parameters.previewData == nil && self.roomViewController.roomDataSource != nil && self.roomViewController.hasRoomDataSourceOwnership == false
}
// MARK: - Setup
init(parameters: RoomCoordinatorParameters) {
self.parameters = parameters
self.selectedEventId = parameters.eventId
self.roomViewController = RoomViewController.instantiate()
self.activityIndicatorPresenter = ActivityIndicatorPresenter()
super.init()
}
// MARK: - Public
func start() {
self.roomViewController.delegate = self
// Detect when view controller has been dismissed by gesture when presented modally (not in full screen).
self.roomViewController.presentationController?.delegate = self
if let eventId = self.selectedEventId {
self.start(with: self.parameters.roomId, and: eventId)
} else {
self.start(with: self.parameters.roomId)
}
// Add `roomViewController` to the NavigationRouter, only if it has been explicity set as parameter
if let navigationRouter = self.parameters.navigationRouter {
if navigationRouter.modules.isEmpty == false {
navigationRouter.push(self.roomViewController, animated: true, popCompletion: nil)
} else {
navigationRouter.setRootModule(self.roomViewController, popCompletion: nil)
}
}
}
/// Use this method when the room screen is already shown and you want to go to a specific event.
/// i.e User tap on push notification message for the current displayed room
func start(withEventId eventId: String) {
self.selectedEventId = eventId
if self.hasStartedOnce {
self.start(with: self.parameters.roomId, and: eventId)
} else {
self.start()
}
}
func toPresentable() -> UIViewController {
return self.roomViewController
}
// MARK: - Private
private func start(with roomId: String) {
// Present activity indicator when retrieving roomDataSource for given room ID
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
let roomDataSourceManager: MXKRoomDataSourceManager = MXKRoomDataSourceManager.sharedManager(forMatrixSession: self.parameters.session)
// LIVE: Show the room live timeline managed by MXKRoomDataSourceManager
roomDataSourceManager.roomDataSource(forRoom: roomId, create: true, onComplete: { [weak self] (roomDataSource) in
guard let self = self else {
return
}
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
if let roomDataSource = roomDataSource {
self.roomViewController.displayRoom(roomDataSource)
}
})
}
private func start(with roomId: String, and eventId: String) {
// Present activity indicator when retrieving roomDataSource for given room ID
self.activityIndicatorPresenter.presentActivityIndicator(on: roomViewController.view, animated: false)
// Open the room on the requested event
RoomDataSource.load(withRoomId: roomId,
initialEventId: eventId,
andMatrixSession: self.parameters.session) { [weak self] (dataSource) in
guard let self = self else {
return
}
self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true)
guard let roomDataSource = dataSource as? RoomDataSource else {
return
}
roomDataSource.markTimelineInitialEvent = true
self.roomViewController.displayRoom(roomDataSource)
// Give the data source ownership to the room view controller.
self.roomViewController.hasRoomDataSourceOwnership = true
}
}
}
// MARK: - RoomIdentifiable
extension RoomCoordinator: RoomIdentifiable {
var roomId: String? {
return self.parameters.roomId
}
var mxSession: MXSession? {
self.parameters.session
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension RoomCoordinator: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.delegate?.roomCoordinatorDidDismissInteractively(self)
}
}
// MARK: - RoomViewControllerDelegate
extension RoomCoordinator: RoomViewControllerDelegate {
func roomViewController(_ roomViewController: RoomViewController, showRoomWithId roomID: String) {
self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID)
}
func roomViewController(_ roomViewController: RoomViewController, showMemberDetails roomMember: MXRoomMember) {
// TODO:
}
func roomViewControllerShowRoomDetails(_ roomViewController: RoomViewController) {
// TODO:
}
func roomViewControllerDidLeaveRoom(_ roomViewController: RoomViewController) {
self.delegate?.roomCoordinatorDidLeaveRoom(self)
}
func roomViewControllerPreviewDidTapCancel(_ roomViewController: RoomViewController) {
self.delegate?.roomCoordinatorDidCancelRoomPreview(self)
}
func roomViewController(_ roomViewController: RoomViewController, startChatWithUserId userId: String, completion: @escaping () -> Void) {
AppDelegate.theDelegate().createDirectChat(withUserId: userId, completion: completion)
}
func roomViewController(_ roomViewController: RoomViewController, showCompleteSecurityFor session: MXSession) {
AppDelegate.theDelegate().presentCompleteSecurity(for: session)
}
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkFragment fragment: String, from universalLinkURL: URL?) -> Bool {
return AppDelegate.theDelegate().handleUniversalLinkFragment(fragment, from: universalLinkURL)
}
func roomViewController(_ roomViewController: RoomViewController, handleUniversalLinkURL universalLinkURL: URL) -> Bool {
return AppDelegate.theDelegate().handleUniversalLinkURL(universalLinkURL)
}
}
@@ -0,0 +1,148 @@
//
// 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
@objc protocol RoomCoordinatorBridgePresenterDelegate {
func roomCoordinatorBridgePresenterDidLeaveRoom(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenterDidCancelRoomPreview(_ bridgePresenter: RoomCoordinatorBridgePresenter)
func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didSelectRoomWithId roomId: String)
func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter)
}
@objcMembers
class RoomCoordinatorBridgePresenterParameters: NSObject {
/// The matrix session in which the room should be available.
let session: MXSession
/// The room identifier
let roomId: String
/// If not nil, the room will be opened on this event.
let eventId: String?
/// The data for the room preview.
let previewData: RoomPreviewData?
init(session: MXSession,
roomId: String,
eventId: String?,
previewData: RoomPreviewData?) {
self.session = session
self.roomId = roomId
self.eventId = eventId
self.previewData = previewData
}
}
/// RoomCoordinatorBridgePresenter enables to start RoomCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
/// **WARNING**: This class breaks the Coordinator abstraction and it has been introduced for **Objective-C compatibility only** (mainly for integration in legacy view controllers). Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
@objcMembers
final class RoomCoordinatorBridgePresenter: NSObject {
// MARK: - Properties
// MARK: Private
private let bridgeParameters: RoomCoordinatorBridgePresenterParameters
private var coordinator: RoomCoordinator?
// MARK: Public
weak var delegate: RoomCoordinatorBridgePresenterDelegate?
// MARK: - Setup
init(parameters: RoomCoordinatorBridgePresenterParameters) {
self.bridgeParameters = parameters
super.init()
}
// MARK: - Public
func present(from viewController: UIViewController, animated: Bool) {
let coordinator = self.createRoomCoordinator()
coordinator.delegate = self
let presentable = coordinator.toPresentable()
presentable.modalPresentationStyle = .formSheet
viewController.present(presentable, animated: animated, completion: nil)
coordinator.start()
self.coordinator = coordinator
}
func push(from navigationController: UINavigationController, animated: Bool) {
let navigationRouter = NavigationRouterStore.shared.getOrCreateNavigationRouter(for: navigationController)
let coordinator = self.createRoomCoordinator(with: navigationRouter)
coordinator.delegate = self
coordinator.start() // Will trigger view controller push
self.coordinator = coordinator
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
guard let coordinator = self.coordinator else {
return
}
coordinator.toPresentable().dismiss(animated: animated) {
self.coordinator = nil
if let completion = completion {
completion()
}
}
}
// MARK: - Private
private func createRoomCoordinator(with navigationRouter: NavigationRouterType = NavigationRouter(navigationController: RiotNavigationController())) -> RoomCoordinator {
let coordinatorParameters: RoomCoordinatorParameters
if let previewData = self.bridgeParameters.previewData {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, previewData: previewData)
} else {
coordinatorParameters = RoomCoordinatorParameters(navigationRouter: navigationRouter, session: self.bridgeParameters.session, roomId: self.bridgeParameters.roomId, eventId: self.bridgeParameters.eventId)
}
return RoomCoordinator(parameters: coordinatorParameters)
}
}
// MARK: - RoomNotificationSettingsCoordinatorDelegate
extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate {
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String) {
self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId)
}
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidLeaveRoom(self)
}
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidCancelRoomPreview(self)
}
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) {
self.delegate?.roomCoordinatorBridgePresenterDidDismissInteractively(self)
}
}
@@ -0,0 +1,76 @@
//
// 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
/// RoomCoordinator input parameters
struct RoomCoordinatorParameters {
// MARK: - Properties
/// The navigation router that manage physical navigation
let navigationRouter: NavigationRouterType?
/// The navigation router store that enables to get a NavigationRouter from a navigation controller
/// `navigationRouter` property takes priority on `navigationRouterStore`
let navigationRouterStore: NavigationRouterStoreProtocol?
/// The matrix session in which the room should be available.
let session: MXSession
/// The room identifier
let roomId: String
/// If not nil, the room will be opened on this event.
let eventId: String?
/// The data for the room preview.
let previewData: RoomPreviewData?
// MARK: - Setup
private init(navigationRouter: NavigationRouterType?,
navigationRouterStore: NavigationRouterStoreProtocol?,
session: MXSession,
roomId: String,
eventId: String?,
previewData: RoomPreviewData?) {
self.navigationRouter = navigationRouter
self.navigationRouterStore = navigationRouterStore
self.session = session
self.roomId = roomId
self.eventId = eventId
self.previewData = previewData
}
/// Init to present a joined room
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
session: MXSession,
roomId: String,
eventId: String? = nil) {
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: session, roomId: roomId, eventId: eventId, previewData: nil)
}
/// Init to present a room preview
init(navigationRouter: NavigationRouterType? = nil,
navigationRouterStore: NavigationRouterStoreProtocol? = nil,
previewData: RoomPreviewData) {
self.init(navigationRouter: navigationRouter, navigationRouterStore: navigationRouterStore, session: previewData.mxSession, roomId: previewData.roomId, eventId: nil, previewData: previewData)
}
}
@@ -0,0 +1,33 @@
// File created from ScreenTemplate
// $ createScreen.sh Room Room
/*
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 RoomCoordinatorDelegate: AnyObject {
func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol)
func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol)
func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String)
func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol)
}
/// `RoomCoordinatorProtocol` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
protocol RoomCoordinatorProtocol: Coordinator, Presentable, RoomIdentifiable {
var delegate: RoomCoordinatorDelegate? { get }
var canReleaseRoomDataSource: Bool { get }
}
+24
View File
@@ -0,0 +1,24 @@
//
// 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
/// `RoomIdentifiable` describes an object tied to a specific room id.
/// Useful to identify existing objects that should be removed when the user leaves a room for example.
protocol RoomIdentifiable {
var roomId: String? { get }
var mxSession: MXSession? { get }
}