mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
dd support for deeplinking/navigating into the same room multiple times
- fix bugs around the view <-> view models going out of sync - unwind the stack if the room is already presented
This commit is contained in:
@@ -19,6 +19,7 @@ import SwiftState
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
|
// swiftlint:disable file_length
|
||||||
enum RoomFlowCoordinatorAction: Equatable {
|
enum RoomFlowCoordinatorAction: Equatable {
|
||||||
case presentRoom(roomID: String)
|
case presentRoom(roomID: String)
|
||||||
case presentCallScreen(roomProxy: RoomProxyProtocol)
|
case presentCallScreen(roomProxy: RoomProxyProtocol)
|
||||||
@@ -38,7 +39,7 @@ enum RoomFlowCoordinatorAction: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:disable file_length
|
// swiftlint:disable:next type_body_length
|
||||||
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
private let roomID: String
|
private let roomID: String
|
||||||
private let userSession: UserSessionProtocol
|
private let userSession: UserSessionProtocol
|
||||||
@@ -104,6 +105,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
func handleAppRoute(_ appRoute: AppRoute, animated: Bool) {
|
||||||
|
guard stateMachine.state != .complete else {
|
||||||
|
fatalError("This flow coordinator is `finished` ☠️")
|
||||||
|
}
|
||||||
|
|
||||||
switch appRoute {
|
switch appRoute {
|
||||||
case .room(let roomID):
|
case .room(let roomID):
|
||||||
Task {
|
Task {
|
||||||
@@ -324,7 +329,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
dismissFlow(animated: animated)
|
dismissFlow(animated: animated)
|
||||||
|
|
||||||
case (_, .presentRoom, .room):
|
case (_, .presentRoom, .room):
|
||||||
Task { await self.presentRoom(animated: animated) }
|
Task { await self.presentRoom(fromState: context.fromState, animated: animated) }
|
||||||
case (_, .dismissFlow, .complete):
|
case (_, .dismissFlow, .complete):
|
||||||
dismissFlow(animated: animated)
|
dismissFlow(animated: animated)
|
||||||
|
|
||||||
@@ -463,11 +468,25 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// Updates the navigation stack so it displays the timeline for the given room
|
/// Updates the navigation stack so it displays the timeline for the given room
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - animated: whether it should animate the transition
|
/// - animated: whether it should animate the transition
|
||||||
private func presentRoom(animated: Bool) async {
|
private func presentRoom(fromState: State, animated: Bool) async {
|
||||||
// If any sheets are presented dismiss them, rely on their dismissal callbacks to transition the state machine
|
// If any sheets are presented dismiss them, rely on their dismissal callbacks to transition the state machine
|
||||||
// through the correct states before presenting the room
|
// through the correct states before presenting the room
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
|
||||||
|
// Handle selecting the same room again
|
||||||
|
if !isChildFlow {
|
||||||
|
// First unwind the navigation stack
|
||||||
|
navigationStackCoordinator.popToRoot(animated: animated)
|
||||||
|
|
||||||
|
// And then decide if the room actually needs to be presented again
|
||||||
|
switch fromState {
|
||||||
|
case .initial, .roomDetails(isRoot: true), .joinRoomScreen:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return // The room is already on the stack, no need to present it again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
// Flag the room as read on entering, the timeline will take care of the read receipts
|
// Flag the room as read on entering, the timeline will take care of the read receipts
|
||||||
await roomProxy.flagAsUnread(false)
|
await roomProxy.flagAsUnread(false)
|
||||||
@@ -649,7 +668,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
if isRoot {
|
if isRoot {
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
navigationStackCoordinator.setRootCoordinator(coordinator, animated: animated) { [weak self] in
|
||||||
self?.stateMachine.tryEvent(.dismissFlow)
|
guard let self else { return }
|
||||||
|
if stateMachine.state != .room { // The root has been replaced by a room
|
||||||
|
stateMachine.tryEvent(.dismissFlow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
navigationStackCoordinator.push(coordinator, animated: animated) { [weak self] in
|
||||||
|
|||||||
@@ -267,8 +267,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentHomeScreen()
|
presentHomeScreen()
|
||||||
attemptStartingOnboarding()
|
attemptStartingOnboarding()
|
||||||
|
|
||||||
case(.roomList, .selectRoom(let roomID, let showingRoomDetails), .roomList):
|
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let showingRoomDetails), .roomList):
|
||||||
Task { await self.startRoomFlow(roomID: roomID, showingRoomDetails: showingRoomDetails, animated: animated) }
|
if selectedRoomID == roomID {
|
||||||
|
if let roomFlowCoordinator {
|
||||||
|
roomFlowCoordinator.handleAppRoute(.room(roomID: roomID), animated: animated)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Task { await self.startRoomFlow(roomID: roomID, showingRoomDetails: showingRoomDetails, animated: animated) }
|
||||||
|
}
|
||||||
case(.roomList, .deselectRoom, .roomList):
|
case(.roomList, .deselectRoom, .roomList):
|
||||||
dismissRoomFlow(animated: animated)
|
dismissRoomFlow(animated: animated)
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
try await clearRoute(expectedActions: [.finished])
|
try await clearRoute(expectedActions: [.finished])
|
||||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||||
|
|
||||||
|
await setupRoomFlowCoordinator(roomType: .invited(roomID: "InvitedRoomID"))
|
||||||
|
|
||||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||||
XCTAssert(navigationStackCoordinator.rootCoordinator is JoinRoomScreenCoordinator)
|
XCTAssert(navigationStackCoordinator.rootCoordinator is JoinRoomScreenCoordinator)
|
||||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||||
@@ -188,7 +190,8 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
|||||||
try await clearRoute(expectedActions: [.finished])
|
try await clearRoute(expectedActions: [.finished])
|
||||||
XCTAssertNil(navigationStackCoordinator.stackCoordinators.last, "A child room flow should remove the join room scren on dismissal")
|
XCTAssertNil(navigationStackCoordinator.stackCoordinators.last, "A child room flow should remove the join room scren on dismissal")
|
||||||
|
|
||||||
navigationStackCoordinator.popToRoot()
|
await setupRoomFlowCoordinator(asChildFlow: true, roomType: .invited(roomID: "InvitedRoomID"))
|
||||||
|
navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator())
|
||||||
|
|
||||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||||
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator, "A child room flow should push onto the stack, leaving the root alone.")
|
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator, "A child room flow should push onto the stack, leaving the root alone.")
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Fix deeplinking/navigating into the same room multiple times
|
||||||
Reference in New Issue
Block a user