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 19bf29e854
commit eeed91a64e
2 changed files with 77 additions and 19 deletions

View File

@@ -79,6 +79,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private var mediaEventsTimelineFlowCoordinator: MediaEventsTimelineFlowCoordinator?
// periphery:ignore - used to avoid deallocation
private var childRoomFlowCoordinator: RoomFlowCoordinator?
// periphery:ignore - retaining purpose
private var spaceFlowCoordinator: SpaceFlowCoordinator?
private let stateMachine: StateMachine<State, Event> = .init(state: .initial)
@@ -257,24 +259,36 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
switch room {
case .joined(let roomProxy):
await storeAndSubscribeToRoomProxy(roomProxy)
guard case let .eventFocus(focusEvent) = presentationAction else {
// If is not a focus event just handle the presentation action directly in `presentRoom`
stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated))
return
}
// Otherwise check if the focussed event exists to handle a possible error or theaded event.
switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) {
case .success(let event):
if flowParameters.appSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: threadRootEventID, shouldSetPin: false))), userInfo: EventUserInfo(animated: animated))
stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: focusEvent.eventID), userInfo: EventUserInfo(animated: false))
} else {
stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated))
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)
guard case let .eventFocus(focusEvent) = presentationAction else {
// If is not a focus event just handle the presentation action directly in `presentRoom`
stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated))
return
}
// Otherwise check if the focussed event exists to handle a possible error or theaded event.
switch await roomProxy.loadOrFetchEventDetails(for: focusEvent.eventID) {
case .success(let event):
if flowParameters.appSettings.threadsEnabled, let threadRootEventID = event.threadRootEventId() {
stateMachine.tryEvent(.presentRoom(presentationAction: .eventFocus(.init(eventID: threadRootEventID, shouldSetPin: false))), userInfo: EventUserInfo(animated: animated))
stateMachine.tryEvent(.presentThread(threadRootEventID: threadRootEventID, focusEventID: focusEvent.eventID), userInfo: EventUserInfo(animated: false))
} else {
stateMachine.tryEvent(.presentRoom(presentationAction: presentationAction), userInfo: EventUserInfo(animated: animated))
}
case .failure:
showErrorIndicator()
stateMachine.tryEvent(.presentRoom(presentationAction: nil), userInfo: EventUserInfo(animated: animated))
}
case .failure:
showErrorIndicator()
stateMachine.tryEvent(.presentRoom(presentationAction: nil), userInfo: EventUserInfo(animated: animated))
}
default:
stateMachine.tryEvent(.presentJoinRoomScreen(via: via), userInfo: EventUserInfo(animated: animated))
@@ -322,7 +336,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables)
}
// swiftlint:disable:next function_body_length
// swiftlint:disable:next function_body_length cyclomatic_complexity
private func setupStateMachine() {
addRouteMapping(stateMachine: stateMachine)
@@ -353,6 +367,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
// 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, _)):
presentReportContent(for: itemID, from: senderID)
@@ -1521,7 +1543,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
case .verifyUser(let userID):
actionsSubject.send(.verifyUser(userID: userID))
case .continueWithSpaceFlow(let spaceRoomListProxy):
#warning("Present the space as a child.")
stateMachine.tryEvent(.startSpaceFlow, userInfo: EventUserInfo(animated: true, spaceRoomListProxy: spaceRoomListProxy))
case .finished:
stateMachine.tryEvent(.dismissChildFlow)
}
@@ -1610,6 +1632,31 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
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 func showLoadingIndicator(delay: Duration? = nil,

View File

@@ -79,6 +79,9 @@ extension RoomFlowCoordinator {
case presentingChild(childRoomID: String, previousState: State)
/// The flow is complete and is handing control of the stack back to its parent.
case complete
/// A space flow is in progress
case spaceFlow(previousState: State)
}
struct EventUserInfo {
@@ -98,6 +101,9 @@ extension RoomFlowCoordinator {
case presentThread(threadRootEventID: String, focusEventID: String?)
case dismissThread
case startSpaceFlow
case finishedSpaceFlow
case presentReportContent(itemID: TimelineItemIdentifier, senderID: String)
case dismissReportContent
@@ -341,6 +347,11 @@ extension RoomFlowCoordinator {
return .presentingChild(childRoomID: roomID, previousState: fromState)
case (.presentingChild(_, let previousState), .dismissChildFlow):
return previousState
case (.presentingChild(_, let previousState), .startSpaceFlow):
return .spaceFlow(previousState: previousState)
case (.spaceFlow(let previousState), .finishedSpaceFlow):
return previousState
case (_, .presentRoomMemberDetails(userID: let userID)):
return .roomMemberDetails(userID: userID, previousState: fromState)