Handle in-timeline permalinks to spaces

This will continue the RoomFlow with a SpaceFlow, fetch the SpaceRoomList from the SpaceService and render its rooms.
This commit is contained in:
Stefan Ceriu
2025-10-07 15:06:13 +03:00
committed by Stefan Ceriu
parent 27f7085dc1
commit b0a2db8ed3
2 changed files with 77 additions and 19 deletions
@@ -79,6 +79,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private var mediaEventsTimelineFlowCoordinator: MediaEventsTimelineFlowCoordinator? private var mediaEventsTimelineFlowCoordinator: MediaEventsTimelineFlowCoordinator?
// periphery:ignore - used to avoid deallocation // periphery:ignore - used to avoid deallocation
private var childRoomFlowCoordinator: RoomFlowCoordinator? private var childRoomFlowCoordinator: RoomFlowCoordinator?
// periphery:ignore - retaining purpose
private var spaceFlowCoordinator: SpaceFlowCoordinator?
private let stateMachine: StateMachine<State, Event> = .init(state: .initial) private let stateMachine: StateMachine<State, Event> = .init(state: .initial)
@@ -257,12 +259,23 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch room { switch room {
case .joined(let roomProxy): case .joined(let roomProxy):
if roomProxy.infoPublisher.value.isSpace {
switch await userSession.clientProxy.spaceService.spaceRoomList(spaceID: roomProxy.id, parent: nil) {
case .success(let spaceRoomListProxy):
actionsSubject.send(.continueWithSpaceFlow(spaceRoomListProxy))
case .failure:
showErrorIndicator()
stateMachine.tryEvent(.dismissFlow)
}
} else {
await storeAndSubscribeToRoomProxy(roomProxy) await storeAndSubscribeToRoomProxy(roomProxy)
guard case let .eventFocus(focusEvent) = presentationAction else { guard case let .eventFocus(focusEvent) = presentationAction else {
// If is not a focus event just handle the presentation action directly in `presentRoom` // If is not a focus event just handle the presentation action directly in `presentRoom`
stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated)) stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated))
return return
} }
// Otherwise check if the focussed event exists to handle a possible error or theaded event. // Otherwise check if the focussed event exists to handle a possible error or theaded event.
switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) { switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) {
case .success(let event): case .success(let event):
@@ -276,6 +289,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
showErrorIndicator() showErrorIndicator()
stateMachine.tryEvent(.presentRoom(presentationAction: nil), userInfo: EventUserInfo(animated: animated)) stateMachine.tryEvent(.presentRoom(presentationAction: nil), userInfo: EventUserInfo(animated: animated))
} }
}
default: default:
stateMachine.tryEvent(.presentJoinRoomScreen(via: via), userInfo: EventUserInfo(animated: animated)) stateMachine.tryEvent(.presentJoinRoomScreen(via: via), userInfo: EventUserInfo(animated: animated))
} }
@@ -322,7 +336,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables) .store(in: &cancellables)
} }
// swiftlint:disable:next function_body_length // swiftlint:disable:next function_body_length cyclomatic_complexity
private func setupStateMachine() { private func setupStateMachine() {
addRouteMapping(stateMachine: stateMachine) addRouteMapping(stateMachine: stateMachine)
@@ -353,6 +367,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
// Thread + Room // Thread + Room
case (_, .startSpaceFlow, .spaceFlow):
guard let spaceRoomListProxy = (context.userInfo as? EventUserInfo)?.spaceRoomListProxy else {
fatalError("The space room list proxy is required to present a space.")
}
startSpaceFlow(spaceRoomListProxy: spaceRoomListProxy, animated: animated)
case (.spaceFlow, .finishedSpaceFlow, _):
spaceFlowCoordinator = nil
case (_, .presentReportContent, .reportContent(let itemID, let senderID, _)): case (_, .presentReportContent, .reportContent(let itemID, let senderID, _)):
presentReportContent(for: itemID, from: senderID) presentReportContent(for: itemID, from: senderID)
@@ -1521,7 +1543,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .verifyUser(let userID): case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID)) actionsSubject.send(.verifyUser(userID: userID))
case .continueWithSpaceFlow(let spaceRoomListProxy): case .continueWithSpaceFlow(let spaceRoomListProxy):
#warning("Present the space as a child.") stateMachine.tryEvent(.startSpaceFlow, userInfo: EventUserInfo(animated: true, spaceRoomListProxy: spaceRoomListProxy))
case .finished: case .finished:
stateMachine.tryEvent(.dismissChildFlow) stateMachine.tryEvent(.dismissChildFlow)
} }
@@ -1610,6 +1632,31 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
flowCoordinator.start() flowCoordinator.start()
} }
private func startSpaceFlow(spaceRoomListProxy: SpaceRoomListProxyProtocol, animated: Bool) {
let coordinator = SpaceFlowCoordinator(entryPoint: .space(spaceRoomListProxy),
spaceServiceProxy: userSession.clientProxy.spaceService,
isChildFlow: true,
navigationStackCoordinator: navigationStackCoordinator,
flowParameters: flowParameters)
coordinator.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .presentCallScreen(let roomProxy):
actionsSubject.send(.presentCallScreen(roomProxy: roomProxy))
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .finished:
stateMachine.tryEvent(.finishedSpaceFlow)
}
}
.store(in: &cancellables)
spaceFlowCoordinator = coordinator
coordinator.start()
}
private static let loadingIndicatorID = "\(RoomFlowCoordinator.self)-Loading" private static let loadingIndicatorID = "\(RoomFlowCoordinator.self)-Loading"
private func showLoadingIndicator(delay: Duration? = nil, private func showLoadingIndicator(delay: Duration? = nil,
@@ -79,6 +79,9 @@ extension RoomFlowCoordinator {
case presentingChild(childRoomID: String, previousState: State) case presentingChild(childRoomID: String, previousState: State)
/// The flow is complete and is handing control of the stack back to its parent. /// The flow is complete and is handing control of the stack back to its parent.
case complete case complete
/// A space flow is in progress
case spaceFlow(previousState: State)
} }
struct EventUserInfo { struct EventUserInfo {
@@ -98,6 +101,9 @@ extension RoomFlowCoordinator {
case presentThread(threadRootEventID: String, focusEventID: String?) case presentThread(threadRootEventID: String, focusEventID: String?)
case dismissThread case dismissThread
case startSpaceFlow
case finishedSpaceFlow
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String) case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
case dismissReportContent case dismissReportContent
@@ -342,6 +348,11 @@ extension RoomFlowCoordinator {
case (.presentingChild(_, let previousState), .dismissChildFlow): case (.presentingChild(_, let previousState), .dismissChildFlow):
return previousState return previousState
case (.presentingChild(_, let previousState), .startSpaceFlow):
return .spaceFlow(previousState: previousState)
case (.spaceFlow(let previousState), .finishedSpaceFlow):
return previousState
case (_, .presentRoomMemberDetails(userID: let userID)): case (_, .presentRoomMemberDetails(userID: let userID)):
return .roomMemberDetails(userID: userID, previousState: fromState) return .roomMemberDetails(userID: userID, previousState: fromState)
case (.roomMemberDetails(_, let previousState), .dismissRoomMemberDetails): case (.roomMemberDetails(_, let previousState), .dismissRoomMemberDetails):