Fixes #918, #919 - Introduce a RoomTimelineFlowCoordinator and related FlowCoordinator protocol

Squashed commits:
Add unit tests and move the state machine into the FlowCoordinator
[bb686861] Replace the RoomFlowCoordinator's public interface with just `handleAppRoute`
[0d9a4f8d] Remove the navigationStackCoordinator dependency from the roomScreenCoordinator
[4b5fbdf2] Allow rooms to be selected from any other state
[41dbd127] Move all missing coordinators to the RoomFlowCoordinator and state machines
[f32431b7] The UserSessionFlowCoordinator does not need to conform to the CoordinatorProtocol
[0f07e87d] Fix leaving a room dismissing the currently selected one when different
[138385a2] Rewind the navigation stack when re-selecting the same room (iPad)
[0727eb93] Fix presenting different room details from the side menu on iPads
[faf4cc60] Fix selecting the same room multiple times
[fb3391da] Move room details presentation responsibility to the RoomFlowCoordinator. Fixed invitation flows.
[fa2a68d9] Rename RoomTimelineFlowCoordinator -> RoomFlowCoordinator
[0c9c06b5] Start moving things away from the RoomScreenCoordinator and into the RoomTimelineFlowCoordinator
[86cbbdcc] Introduce a RoomTimelineFlowCoordinator to deal with timeline related operations
[9b2381be] Introduce the FlowCoordinatorProtocol
This commit is contained in:
Stefan Ceriu
2023-05-18 12:45:34 +03:00
committed by Stefan Ceriu
parent f2b7faa183
commit 0082aba9d4
12 changed files with 715 additions and 334 deletions
@@ -14,30 +14,34 @@
// limitations under the License.
//
import Combine
import SwiftUI
struct RoomScreenCoordinatorParameters {
let navigationStackCoordinator: NavigationStackCoordinator
let roomProxy: RoomProxyProtocol
let timelineController: RoomTimelineControllerProtocol
let mediaProvider: MediaProviderProtocol
let emojiProvider: EmojiProviderProtocol
let userDiscoveryService: UserDiscoveryServiceProtocol
}
enum RoomScreenCoordinatorAction {
case leftRoom
case presentMediaViewer(file: MediaFileHandleProxy, title: String?)
case presentReportContent(itemID: String, senderID: String)
case presentMediaUploadPicker(MediaPickerScreenSource)
case presentMediaUploadPreviewScreen(URL)
case presentRoomDetails
case presentEmojiPicker(itemID: String)
}
final class RoomScreenCoordinator: CoordinatorProtocol {
private var parameters: RoomScreenCoordinatorParameters
private var viewModel: RoomScreenViewModelProtocol
private var navigationStackCoordinator: NavigationStackCoordinator {
parameters.navigationStackCoordinator
}
var callback: ((RoomScreenCoordinatorAction) -> Void)?
private let actionsSubject: PassthroughSubject<RoomScreenCoordinatorAction, Never> = .init()
var actions: AnyPublisher<RoomScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: RoomScreenCoordinatorParameters) {
self.parameters = parameters
@@ -55,21 +59,21 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
switch action {
case .displayRoomDetails:
self.displayRoomDetails()
case .displayMediaFile(let file, let title):
self.displayFilePreview(for: file, with: title)
actionsSubject.send(.presentRoomDetails)
case .displayMediaViewer(let file, let title):
actionsSubject.send(.presentMediaViewer(file: file, title: title))
case .displayEmojiPicker(let itemID):
self.displayEmojiPickerScreen(for: itemID)
actionsSubject.send(.presentEmojiPicker(itemID: itemID))
case .displayReportContent(let itemID, let senderID):
self.displayReportContent(for: itemID, from: senderID)
actionsSubject.send(.presentReportContent(itemID: itemID, senderID: senderID))
case .displayCameraPicker:
self.displayMediaPickerWithSource(.camera)
actionsSubject.send(.presentMediaUploadPicker(.camera))
case .displayMediaPicker:
self.displayMediaPickerWithSource(.photoLibrary)
actionsSubject.send(.presentMediaUploadPicker(.photoLibrary))
case .displayDocumentPicker:
self.displayMediaPickerWithSource(.documents)
actionsSubject.send(.presentMediaUploadPicker(.documents))
case .displayMediaUploadPreviewScreen(let url):
self.displayMediaUploadPreviewScreenForFile(at: url)
actionsSubject.send(.presentMediaUploadPreviewScreen(url))
}
}
}
@@ -81,123 +85,4 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView {
AnyView(RoomScreen(context: viewModel.context))
}
// MARK: - Private
private func displayMediaPickerWithSource(_ source: MediaPickerScreenSource) {
let stackCoordinator = NavigationStackCoordinator()
let userIndicatorController = UserIndicatorController(rootCoordinator: stackCoordinator)
let mediaPickerCoordinator = MediaPickerScreenCoordinator(userIndicatorController: userIndicatorController, source: source) { [weak self] action in
switch action {
case .cancel:
self?.navigationStackCoordinator.setSheetCoordinator(nil)
case .selectMediaAtURL(let url):
self?.displayMediaUploadPreviewScreenForFile(at: url)
}
}
stackCoordinator.setRootCoordinator(mediaPickerCoordinator)
navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
}
private func displayMediaUploadPreviewScreenForFile(at url: URL) {
let stackCoordinator = NavigationStackCoordinator()
let userIndicatorController = UserIndicatorController(rootCoordinator: stackCoordinator)
let parameters = MediaUploadPreviewScreenCoordinatorParameters(userIndicatorController: userIndicatorController,
roomProxy: parameters.roomProxy,
mediaUploadingPreprocessor: MediaUploadingPreprocessor(),
title: url.lastPathComponent,
url: url)
let mediaUploadPreviewScreenCoordinator = MediaUploadPreviewScreenCoordinator(parameters: parameters) { [weak self] action in
switch action {
case .dismiss:
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
}
stackCoordinator.setRootCoordinator(mediaUploadPreviewScreenCoordinator)
navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
}
private func displayFilePreview(for file: MediaFileHandleProxy, with title: String?) {
let params = FilePreviewScreenCoordinatorParameters(mediaFile: file, title: title)
let coordinator = FilePreviewScreenCoordinator(parameters: params)
coordinator.callback = { [weak self] _ in
self?.navigationStackCoordinator.pop()
}
navigationStackCoordinator.push(coordinator)
}
private func displayEmojiPickerScreen(for itemId: String) {
let emojiPickerNavigationStackCoordinator = NavigationStackCoordinator()
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: parameters.emojiProvider,
itemId: itemId)
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
coordinator.callback = { [weak self] action in
switch action {
case let .emojiSelected(emoji: emoji, itemId: itemId):
MXLog.debug("Selected \(emoji) for \(itemId)")
self?.navigationStackCoordinator.setSheetCoordinator(nil)
Task {
await self?.parameters.timelineController.sendReaction(emoji, to: itemId)
}
case .dismiss:
self?.navigationStackCoordinator.setSheetCoordinator(nil)
}
}
emojiPickerNavigationStackCoordinator.setRootCoordinator(coordinator)
emojiPickerNavigationStackCoordinator.presentationDetents = [.medium, .large]
navigationStackCoordinator.setSheetCoordinator(emojiPickerNavigationStackCoordinator)
}
private func displayRoomDetails() {
let params = RoomDetailsScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
roomProxy: parameters.roomProxy,
mediaProvider: parameters.mediaProvider,
userDiscoveryService: parameters.userDiscoveryService)
let coordinator = RoomDetailsScreenCoordinator(parameters: params)
coordinator.callback = { [weak self] action in
switch action {
case .cancel:
self?.navigationStackCoordinator.pop()
case .leftRoom:
self?.callback?(.leftRoom)
}
}
navigationStackCoordinator.push(coordinator)
}
private func displayReportContent(for itemID: String, from senderID: String) {
let navigationCoordinator = NavigationStackCoordinator()
let userIndicatorController = UserIndicatorController(rootCoordinator: navigationCoordinator)
let parameters = ReportContentScreenCoordinatorParameters(itemID: itemID,
senderID: senderID,
roomProxy: parameters.roomProxy,
userIndicatorController: userIndicatorController)
let coordinator = ReportContentScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] completion in
self?.navigationStackCoordinator.setSheetCoordinator(nil)
switch completion {
case .cancel: break
case .finish:
self?.showSuccess(label: L10n.commonReportSubmitted)
}
}
navigationCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(userIndicatorController)
}
private func showSuccess(label: String) {
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: label, iconName: "checkmark"))
}
}
@@ -20,7 +20,7 @@ import UIKit
enum RoomScreenViewModelAction {
case displayRoomDetails
case displayMediaFile(file: MediaFileHandleProxy, title: String?)
case displayMediaViewer(file: MediaFileHandleProxy, title: String?)
case displayEmojiPicker(itemID: String)
case displayReportContent(itemID: String, senderID: String)
case displayCameraPicker
@@ -154,7 +154,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
switch action {
case .displayMediaFile(let file, let title):
callback?(.displayMediaFile(file: file, title: title))
callback?(.displayMediaViewer(file: file, title: title))
case .none:
break
}