mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Fixes #317 - Adopt a split layout for iPad and Mac apps
Rename navigation components: SplitScreenCoordinator -> NavigationSplitCoordinator, StackScreenCoordinator -> NavigationStackCoordinator and SingleScreenCoordinator -> NavigationRootCoordinator [0c161039] Tweak navigation logging [826c19cf] Move the navigation dismissal callbacks to the NavigationModule, add SingleScreenCoordinator tests [b8830d9c] Add tests [252ad119] Merge the StackScreenCoordinator and SplitScreenCoordinators into a single file and stop publicly exposing their internal workings. Add more documentation. [37671699] Cleanup navigation logging [51406184] Use the parent SplitScreenCoordinator to present embedded StackScreenCoordinator sheets [b94b04c9] Retract the room "syncing" indicator when dismissing a room [1467b0ac] Correctly move to the no room selected state when popping in compact layouts [10bf2ad8] Allow nilling root coordinators, replace present/dismiss sheet with setSheetCoordinator(?) [33716784] Add single screen coordinator fade transition animation [3cbe65e7] Prevent the timeline table view from being reused between different rooms [9c94c50b] Move files around [c10b6bc5] Adapt the user session state machine to the split layout [7115a319] Fix unit and UI tests [1ece59e8] Fix login flows [6884dc3b] Use modules everywhere the underlying object is a NavigationModule [ab08d44c] Rename navigation components to: SingleScreenCoordinator, SplitScreenCoordinator and StackScreenCoordinator [ada2be57] Add SplitNavigationController * Remove the navigationRootCoordinator from the UserSessionFlowCoordinator
This commit is contained in:
@@ -24,18 +24,26 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
private let stateMachine: UserSessionFlowCoordinatorStateMachine
|
||||
|
||||
private let userSession: UserSessionProtocol
|
||||
private let navigationController: NavigationController
|
||||
private let navigationSplitCoordinator: NavigationSplitCoordinator
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let emojiProvider: EmojiProviderProtocol = EmojiProvider()
|
||||
|
||||
private let sidebarNavigationStackCoordinator: NavigationStackCoordinator
|
||||
private let detailNavigationStackCoordinator: NavigationStackCoordinator
|
||||
|
||||
var callback: ((UserSessionFlowCoordinatorAction) -> Void)?
|
||||
|
||||
init(userSession: UserSessionProtocol, navigationController: NavigationController, bugReportService: BugReportServiceProtocol) {
|
||||
init(userSession: UserSessionProtocol, navigationSplitCoordinator: NavigationSplitCoordinator, bugReportService: BugReportServiceProtocol) {
|
||||
stateMachine = UserSessionFlowCoordinatorStateMachine()
|
||||
self.userSession = userSession
|
||||
self.navigationController = navigationController
|
||||
self.navigationSplitCoordinator = navigationSplitCoordinator
|
||||
self.bugReportService = bugReportService
|
||||
|
||||
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
||||
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
|
||||
|
||||
navigationSplitCoordinator.setSidebarCoordinator(sidebarNavigationStackCoordinator)
|
||||
|
||||
setupStateMachine()
|
||||
}
|
||||
|
||||
@@ -50,7 +58,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func tryDisplayingRoomScreen(roomId: String) {
|
||||
stateMachine.processEvent(.showRoomScreen(roomId: roomId))
|
||||
stateMachine.processEvent(.selectRoom(roomId: roomId))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
@@ -61,27 +69,32 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .start, .homeScreen):
|
||||
case (.initial, .start, .roomList):
|
||||
self.presentHomeScreen()
|
||||
|
||||
case(.homeScreen, .showRoomScreen, .roomScreen(let roomId)):
|
||||
self.presentRoomWithIdentifier(roomId)
|
||||
case(.roomScreen, .dismissedRoomScreen, .homeScreen):
|
||||
break
|
||||
case(.roomList(let currentRoomId), .selectRoom, .roomList(let selectedRoomId)):
|
||||
guard let selectedRoomId,
|
||||
selectedRoomId != currentRoomId else {
|
||||
return
|
||||
}
|
||||
|
||||
case (.homeScreen, .showSessionVerificationScreen, .sessionVerificationScreen):
|
||||
self.presentRoomWithIdentifier(selectedRoomId)
|
||||
case(.roomList, .deselectRoom, .roomList):
|
||||
break
|
||||
|
||||
case (.roomList, .showSessionVerificationScreen, .sessionVerificationScreen):
|
||||
self.presentSessionVerification()
|
||||
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .homeScreen):
|
||||
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList):
|
||||
break
|
||||
|
||||
case (.homeScreen, .showSettingsScreen, .settingsScreen):
|
||||
|
||||
case (.roomList, .showSettingsScreen, .settingsScreen):
|
||||
self.presentSettingsScreen()
|
||||
case (.settingsScreen, .dismissedSettingsScreen, .homeScreen):
|
||||
case (.settingsScreen, .dismissedSettingsScreen, .roomList):
|
||||
break
|
||||
|
||||
case (.homeScreen, .feedbackScreen, .feedbackScreen):
|
||||
case (.roomList, .feedbackScreen, .feedbackScreen):
|
||||
self.presentFeedbackScreen()
|
||||
case (.feedbackScreen, .dismissedFeedbackScreen, .homeScreen):
|
||||
case (.feedbackScreen, .dismissedFeedbackScreen, .roomList):
|
||||
break
|
||||
|
||||
default:
|
||||
@@ -100,7 +113,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession,
|
||||
attributedStringBuilder: AttributedStringBuilder(),
|
||||
bugReportService: bugReportService,
|
||||
navigationController: navigationController)
|
||||
navigationStackCoordinator: detailNavigationStackCoordinator)
|
||||
let coordinator = HomeScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.callback = { [weak self] action in
|
||||
@@ -108,7 +121,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
|
||||
switch action {
|
||||
case .presentRoomScreen(let roomIdentifier):
|
||||
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
||||
self.stateMachine.processEvent(.selectRoom(roomId: roomIdentifier))
|
||||
case .presentSettingsScreen:
|
||||
self.stateMachine.processEvent(.showSettingsScreen)
|
||||
case .presentFeedbackScreen:
|
||||
@@ -119,8 +132,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
self.callback?(.signOut)
|
||||
}
|
||||
}
|
||||
|
||||
navigationController.setRootCoordinator(coordinator)
|
||||
|
||||
sidebarNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
}
|
||||
|
||||
// MARK: Rooms
|
||||
@@ -145,7 +158,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
roomProxy: roomProxy)
|
||||
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationController: navigationController,
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||
timelineController: timelineController,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
roomName: roomProxy.displayName ?? roomProxy.name,
|
||||
@@ -153,9 +166,17 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
emojiProvider: emojiProvider)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationController.push(coordinator) { [weak self] in
|
||||
detailNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
navigationSplitCoordinator.setDetailCoordinator(detailNavigationStackCoordinator) { [weak self, roomIdentifier] in
|
||||
guard let self else { return }
|
||||
self.stateMachine.processEvent(.dismissedRoomScreen)
|
||||
|
||||
// Move the state machine to no room selected if the room currently being dimissed
|
||||
// is the same as the one selected in the state machine.
|
||||
// This generally happens when popping the room screen while in a compact layout
|
||||
if case let .roomList(selectedRoomId) = self.stateMachine.state, selectedRoomId == roomIdentifier {
|
||||
self.stateMachine.processEvent(.deselectRoom)
|
||||
self.detailNavigationStackCoordinator.setRootCoordinator(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,11 +184,11 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
// MARK: Settings
|
||||
|
||||
private func presentSettingsScreen() {
|
||||
let settingsNavigationController = NavigationController()
|
||||
let settingsNavigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let userNotificationController = UserNotificationController(rootCoordinator: settingsNavigationController)
|
||||
let userNotificationController = UserNotificationController(rootCoordinator: settingsNavigationStackCoordinator)
|
||||
|
||||
let parameters = SettingsCoordinatorParameters(navigationController: settingsNavigationController,
|
||||
let parameters = SettingsCoordinatorParameters(navigationStackCoordinator: settingsNavigationStackCoordinator,
|
||||
userNotificationController: userNotificationController,
|
||||
userSession: userSession,
|
||||
bugReportService: bugReportService)
|
||||
@@ -176,16 +197,16 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
self.navigationController.dismissSheet()
|
||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
case .logout:
|
||||
self.navigationController.dismissSheet()
|
||||
self.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
self.callback?(.signOut)
|
||||
}
|
||||
}
|
||||
|
||||
settingsNavigationController.setRootCoordinator(settingsCoordinator)
|
||||
settingsNavigationStackCoordinator.setRootCoordinator(settingsCoordinator)
|
||||
|
||||
navigationController.presentSheet(userNotificationController) { [weak self] in
|
||||
navigationSplitCoordinator.setSheetCoordinator(userNotificationController) { [weak self] in
|
||||
self?.stateMachine.processEvent(.dismissedSettingsScreen)
|
||||
}
|
||||
}
|
||||
@@ -202,10 +223,10 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
let coordinator = SessionVerificationCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.callback = { [weak self] in
|
||||
self?.navigationController.dismissSheet()
|
||||
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
navigationController.presentSheet(coordinator) { [weak self] in
|
||||
navigationSplitCoordinator.setSheetCoordinator(coordinator) { [weak self] in
|
||||
self?.stateMachine.processEvent(.dismissedSessionVerificationScreen)
|
||||
}
|
||||
}
|
||||
@@ -213,9 +234,9 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
// MARK: Bug reporting
|
||||
|
||||
private func presentFeedbackScreen(for image: UIImage? = nil) {
|
||||
let feedbackNavigationController = NavigationController()
|
||||
let feedbackNavigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let userNotificationController = UserNotificationController(rootCoordinator: feedbackNavigationController)
|
||||
let userNotificationController = UserNotificationController(rootCoordinator: feedbackNavigationStackCoordinator)
|
||||
|
||||
let parameters = BugReportCoordinatorParameters(bugReportService: bugReportService,
|
||||
userNotificationController: userNotificationController,
|
||||
@@ -223,12 +244,12 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
isModallyPresented: true)
|
||||
let coordinator = BugReportCoordinator(parameters: parameters)
|
||||
coordinator.completion = { [weak self] _ in
|
||||
self?.navigationController.dismissSheet()
|
||||
self?.navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
feedbackNavigationController.setRootCoordinator(coordinator)
|
||||
feedbackNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
|
||||
navigationController.presentSheet(userNotificationController) { [weak self] in
|
||||
navigationSplitCoordinator.setSheetCoordinator(userNotificationController) { [weak self] in
|
||||
self?.stateMachine.processEvent(.dismissedFeedbackScreen)
|
||||
}
|
||||
}
|
||||
|
||||
+31
-31
@@ -24,20 +24,16 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
case initial
|
||||
|
||||
/// Showing the home screen
|
||||
case homeScreen
|
||||
|
||||
/// Showing a particular room's timeline
|
||||
/// - Parameter roomId: that room's identifier
|
||||
case roomScreen(roomId: String)
|
||||
case roomList(selectedRoomId: String?)
|
||||
|
||||
/// Showing the session verification flows
|
||||
case sessionVerificationScreen(selectedRoomId: String?)
|
||||
|
||||
/// Showing the session verification flows
|
||||
case sessionVerificationScreen
|
||||
|
||||
/// Showing the session verification flows
|
||||
case feedbackScreen
|
||||
case feedbackScreen(selectedRoomId: String?)
|
||||
|
||||
/// Showing the settings screen
|
||||
case settingsScreen
|
||||
case settingsScreen(selectedRoomId: String?)
|
||||
}
|
||||
|
||||
/// Events that can be triggered on the AppCoordinator state machine
|
||||
@@ -47,9 +43,9 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
|
||||
/// Request presentation for a particular room
|
||||
/// - Parameter roomId:the room identifier
|
||||
case showRoomScreen(roomId: String)
|
||||
case selectRoom(roomId: String)
|
||||
/// The room screen has been dismissed
|
||||
case dismissedRoomScreen
|
||||
case deselectRoom
|
||||
|
||||
/// Request presentation of the settings screen
|
||||
case showSettingsScreen
|
||||
@@ -69,35 +65,39 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
|
||||
private let stateMachine: StateMachine<State, Event>
|
||||
|
||||
var state: UserSessionFlowCoordinatorStateMachine.State {
|
||||
stateMachine.state
|
||||
}
|
||||
|
||||
init() {
|
||||
stateMachine = StateMachine(state: .initial)
|
||||
configure()
|
||||
}
|
||||
|
||||
private func configure() {
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .homeScreen])
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(selectedRoomId: nil)])
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, _ in
|
||||
switch (event, fromState) {
|
||||
case (.showRoomScreen(let roomId), .homeScreen):
|
||||
return .roomScreen(roomId: roomId)
|
||||
case (.dismissedRoomScreen, .roomScreen):
|
||||
return .homeScreen
|
||||
case (.selectRoom(let roomId), .roomList):
|
||||
return .roomList(selectedRoomId: roomId)
|
||||
case (.deselectRoom, .roomList):
|
||||
return .roomList(selectedRoomId: nil)
|
||||
|
||||
case (.showSettingsScreen, .homeScreen):
|
||||
return .settingsScreen
|
||||
case (.dismissedSettingsScreen, .settingsScreen):
|
||||
return .homeScreen
|
||||
case (.showSettingsScreen, .roomList(let selectedRoomId)):
|
||||
return .settingsScreen(selectedRoomId: selectedRoomId)
|
||||
case (.dismissedSettingsScreen, .settingsScreen(let selectedRoomId)):
|
||||
return .roomList(selectedRoomId: selectedRoomId)
|
||||
|
||||
case (.feedbackScreen, .homeScreen):
|
||||
return .feedbackScreen
|
||||
case (.dismissedFeedbackScreen, .feedbackScreen):
|
||||
return .homeScreen
|
||||
case (.feedbackScreen, .roomList(let selectedRoomId)):
|
||||
return .feedbackScreen(selectedRoomId: selectedRoomId)
|
||||
case (.dismissedFeedbackScreen, .feedbackScreen(let selectedRoomId)):
|
||||
return .roomList(selectedRoomId: selectedRoomId)
|
||||
|
||||
case (.showSessionVerificationScreen, .homeScreen):
|
||||
return .sessionVerificationScreen
|
||||
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen):
|
||||
return .homeScreen
|
||||
case (.showSessionVerificationScreen, .roomList(let selectedRoomId)):
|
||||
return .sessionVerificationScreen(selectedRoomId: selectedRoomId)
|
||||
case (.dismissedSessionVerificationScreen, .sessionVerificationScreen(let selectedRoomId)):
|
||||
return .roomList(selectedRoomId: selectedRoomId)
|
||||
|
||||
default:
|
||||
return nil
|
||||
@@ -132,8 +132,8 @@ class UserSessionFlowCoordinatorStateMachine {
|
||||
/// Flag indicating the machine is displaying room screen with given room identifier
|
||||
func isDisplayingRoomScreen(withRoomId roomId: String) -> Bool {
|
||||
switch stateMachine.state {
|
||||
case .roomScreen(let displayedRoomId):
|
||||
return roomId == displayedRoomId
|
||||
case .roomList(let selectedRoomId):
|
||||
return roomId == selectedRoomId
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user