feat: report a room

updated tests

updated tests

added a feature flag to report room

removed delay

rename
This commit is contained in:
Mauro Romito
2025-04-02 17:44:24 +02:00
committed by Mauro
parent 8149107aef
commit 2ee5c1b403
31 changed files with 750 additions and 104 deletions
@@ -414,6 +414,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
return .securityAndPrivacy(previousState: fromState)
case (.securityAndPrivacy(let previousState), .dismissSecurityAndPrivacyScreen):
return previousState
case (.roomDetails, .presentReportRoomScreen):
return .reportRoom(previousState: fromState)
case (.reportRoom(let previousState), .dismissReportRoomScreen):
return previousState
default:
return nil
@@ -585,6 +590,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
presentSecurityAndPrivacyScreen()
case (.securityAndPrivacy, .dismissSecurityAndPrivacyScreen, .roomDetails):
break
case (.roomDetails, .presentReportRoomScreen, .reportRoom):
presentReportRoom()
case (.reportRoom, .dismissReportRoomScreen, .roomDetails):
break
// Child flow
case (_, .startChildFlow(let roomID, let via, let entryPoint), .presentingChild):
@@ -869,6 +879,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.tryEvent(.presentSecurityAndPrivacyScreen)
case .presentRecipientDetails(let userID):
stateMachine.tryEvent(.presentRoomMemberDetails(userID: userID))
case .presentReportRoomScreen:
stateMachine.tryEvent(.presentReportRoomScreen)
}
}
.store(in: &cancellables)
@@ -1516,6 +1528,29 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
navigationStackCoordinator.setSheetCoordinator(stackCoordinator)
}
private func presentReportRoom() {
let stackCoordinator = NavigationStackCoordinator()
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userIndicatorController: userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss(let shouldLeaveRoom):
if shouldLeaveRoom {
stateMachine.tryEvent(.dismissFlow)
}
navigationStackCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
stackCoordinator.setRootCoordinator(coordinator)
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
self?.stateMachine.tryEvent(.dismissReportRoomScreen)
}
}
// MARK: - Other flows
private func startChildFlow(for roomID: String, via: [String], entryPoint: RoomFlowCoordinatorEntryPoint) async {
@@ -1686,6 +1721,7 @@ private extension RoomFlowCoordinator {
case knockRequestsList(previousState: State)
case mediaEventsTimeline(previousState: State)
case securityAndPrivacy(previousState: State)
case reportRoom(previousState: State)
/// A child flow is in progress.
case presentingChild(childRoomID: String, previousState: State)
@@ -1772,5 +1808,8 @@ private extension RoomFlowCoordinator {
case presentSecurityAndPrivacyScreen
case dismissSecurityAndPrivacyScreen
case presentReportRoomScreen
case dismissReportRoomScreen
}
}
@@ -172,7 +172,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
}
case .roomDetails(let roomID):
if stateMachine.state.selectedRoomID == roomID {
if stateMachine.state.roomListSelectedRoomID == roomID {
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
} else {
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .roomDetails), userInfo: .init(animated: animated))
@@ -245,8 +245,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case (.initial, .start, .roomList):
presentHomeScreen()
attemptStartingOnboarding()
case(.roomList(let selectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
if selectedRoomID == roomID,
case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
if roomListSelectedRoomID == roomID,
!entryPoint.isEventID, // Don't reuse the existing room so the live timeline is hidden while the detached timeline is loading.
let roomFlowCoordinator {
let route: AppRoute = switch entryPoint {
@@ -307,9 +307,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
case (.userProfileScreen, .dismissedUserProfileScreen, .roomList):
break
case (.roomList(let selectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)):
case (.roomList, .presentReportRoomScreen(let roomID), .reportRoomScreen):
Task { await self.presentReportRoom(for: roomID) }
case (.reportRoomScreen, .dismissedReportRoomScreen, .roomList):
break
case (.roomList(let roomListSelectedRoomID), .showShareExtensionRoomList, .shareExtensionRoomList(let sharePayload)):
Task {
if selectedRoomID != nil {
if roomListSelectedRoomID != nil {
self.clearRoute(animated: animated)
try? await Task.sleep(for: .seconds(1.5))
}
@@ -326,8 +331,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
stateMachine.addTransitionHandler { [weak self] context in
switch context.toState {
case .roomList(let selectedRoomID):
self?.selectedRoomSubject.send(selectedRoomID)
case .roomList(let roomListSelectedRoomID):
self?.selectedRoomSubject.send(roomListSelectedRoomID)
default:
break
}
@@ -496,9 +501,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
handleAppRoute(.room(roomID: roomID, via: []), animated: true)
case .presentRoomDetails(let roomID):
handleAppRoute(.roomDetails(roomID: roomID), animated: true)
case .presentReportRoom(let roomID):
stateMachine.processEvent(.presentReportRoomScreen(roomID: roomID))
case .roomLeft(let roomID):
if case .roomList(selectedRoomID: let selectedRoomID) = stateMachine.state,
selectedRoomID == roomID {
if case .roomList(roomListSelectedRoomID: let roomListSelectedRoomID) = stateMachine.state,
roomListSelectedRoomID == roomID {
clearRoute(animated: true)
}
case .presentSettingsScreen:
@@ -528,6 +535,35 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
navigationRootCoordinator.setRootCoordinator(navigationSplitCoordinator)
}
private func presentReportRoom(for roomID: String) async {
guard let roomProxyType = await userSession.clientProxy.roomForIdentifier(roomID),
case let .joined(roomProxy) = roomProxyType else {
MXLog.error("Failed to get room proxy for room: \(roomID)")
return
}
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ReportRoomScreenCoordinator(parameters: .init(roomProxy: roomProxy,
userIndicatorController: ServiceLocator.shared.userIndicatorController))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }
switch action {
case .dismiss(let shouldLeaveRoom):
if shouldLeaveRoom,
case .roomList(let roomListSelectedRoomID) = stateMachine.state,
roomListSelectedRoomID == roomID {
clearRoute(animated: true)
}
navigationSplitCoordinator.setSheetCoordinator(nil)
}
}
.store(in: &cancellables)
navigationStackCoordinator.setRootCoordinator(coordinator)
navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in
self?.stateMachine.processEvent(.dismissedReportRoomScreen)
}
}
private func runLogoutFlow() async {
let secureBackupController = userSession.clientProxy.secureBackupController
@@ -15,49 +15,53 @@ class UserSessionFlowCoordinatorStateMachine {
/// The initial state, used before the coordinator starts
case initial
/// Showing the home screen. The `selectedRoomID` represents the timeline shown on the detail panel (if any)
case roomList(selectedRoomID: String?)
/// Showing the home screen. The `roomListSelectedRoomID` represents the timeline shown on the detail panel (if any)
case roomList(roomListSelectedRoomID: String?)
/// Showing the feedback screen.
case feedbackScreen(selectedRoomID: String?)
case feedbackScreen(roomListSelectedRoomID: String?)
/// Showing the settings screen
case settingsScreen(selectedRoomID: String?)
case settingsScreen(roomListSelectedRoomID: String?)
/// Showing the recovery key screen.
case recoveryKeyScreen(selectedRoomID: String?)
case recoveryKeyScreen(roomListSelectedRoomID: String?)
/// Showing the encryption reset flow.
case encryptionResetFlow(selectedRoomID: String?)
case encryptionResetFlow(roomListSelectedRoomID: String?)
/// Showing the start chat screen
case startChatScreen(selectedRoomID: String?)
case startChatScreen(roomListSelectedRoomID: String?)
/// Showing the logout flows
case logoutConfirmationScreen(selectedRoomID: String?)
case logoutConfirmationScreen(roomListSelectedRoomID: String?)
/// Showing Room Directory Search screen
case roomDirectorySearchScreen(selectedRoomID: String?)
case roomDirectorySearchScreen(roomListSelectedRoomID: String?)
/// Showing the user profile screen. This screen clears the navigation.
case userProfileScreen
/// Showing the report room screen, for the given room identrifier
case reportRoomScreen(roomListSelectedRoomID: String?)
case shareExtensionRoomList(sharePayload: ShareExtensionPayload)
/// The selected room ID from the state if available.
var selectedRoomID: String? {
var roomListSelectedRoomID: String? {
switch self {
case .initial, .userProfileScreen, .shareExtensionRoomList:
nil
case .roomList(let selectedRoomID),
.feedbackScreen(let selectedRoomID),
.settingsScreen(let selectedRoomID),
.recoveryKeyScreen(let selectedRoomID),
.encryptionResetFlow(let selectedRoomID),
.startChatScreen(let selectedRoomID),
.logoutConfirmationScreen(let selectedRoomID),
.roomDirectorySearchScreen(let selectedRoomID):
selectedRoomID
case .roomList(let roomListSelectedRoomID),
.feedbackScreen(let roomListSelectedRoomID),
.settingsScreen(let roomListSelectedRoomID),
.recoveryKeyScreen(let roomListSelectedRoomID),
.encryptionResetFlow(let roomListSelectedRoomID),
.startChatScreen(let roomListSelectedRoomID),
.logoutConfirmationScreen(let roomListSelectedRoomID),
.roomDirectorySearchScreen(let roomListSelectedRoomID),
.reportRoomScreen(let roomListSelectedRoomID):
roomListSelectedRoomID
}
}
}
@@ -121,6 +125,9 @@ class UserSessionFlowCoordinatorStateMachine {
case showShareExtensionRoomList(sharePayload: ShareExtensionPayload)
case dismissedShareExtensionRoomList
case presentReportRoomScreen(roomID: String)
case dismissedReportRoomScreen
}
private let stateMachine: StateMachine<State, Event>
@@ -140,59 +147,64 @@ class UserSessionFlowCoordinatorStateMachine {
}
private func configure() {
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(selectedRoomID: nil)])
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(roomListSelectedRoomID: nil)])
stateMachine.addRouteMapping { event, fromState, _ in
switch (fromState, event) {
case (.roomList, .selectRoom(let roomID, _, _)):
return .roomList(selectedRoomID: roomID)
return .roomList(roomListSelectedRoomID: roomID)
case (.roomList, .deselectRoom):
return .roomList(selectedRoomID: nil)
return .roomList(roomListSelectedRoomID: nil)
case (.roomList(let selectedRoomID), .showSettingsScreen):
return .settingsScreen(selectedRoomID: selectedRoomID)
case (.settingsScreen(let selectedRoomID), .dismissedSettingsScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .showSettingsScreen):
return .settingsScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.settingsScreen(let roomListSelectedRoomID), .dismissedSettingsScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .feedbackScreen):
return .feedbackScreen(selectedRoomID: selectedRoomID)
case (.feedbackScreen(let selectedRoomID), .dismissedFeedbackScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.feedbackScreen(let roomListSelectedRoomID), .dismissedFeedbackScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showRecoveryKeyScreen):
return .recoveryKeyScreen(selectedRoomID: selectedRoomID)
case (.recoveryKeyScreen(let selectedRoomID), .dismissedRecoveryKeyScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .showRecoveryKeyScreen):
return .recoveryKeyScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.recoveryKeyScreen(let roomListSelectedRoomID), .dismissedRecoveryKeyScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .startEncryptionResetFlow):
return .encryptionResetFlow(selectedRoomID: selectedRoomID)
case (.encryptionResetFlow(let selectedRoomID), .finishedEncryptionResetFlow):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .startEncryptionResetFlow):
return .encryptionResetFlow(roomListSelectedRoomID: roomListSelectedRoomID)
case (.encryptionResetFlow(let roomListSelectedRoomID), .finishedEncryptionResetFlow):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showStartChatScreen):
return .startChatScreen(selectedRoomID: selectedRoomID)
case (.startChatScreen(let selectedRoomID), .dismissedStartChatScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .showStartChatScreen):
return .startChatScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showLogoutConfirmationScreen):
return .logoutConfirmationScreen(selectedRoomID: selectedRoomID)
case (.logoutConfirmationScreen(let selectedRoomID), .dismissedLogoutConfirmationScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .showLogoutConfirmationScreen):
return .logoutConfirmationScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.logoutConfirmationScreen(let roomListSelectedRoomID), .dismissedLogoutConfirmationScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomList(let selectedRoomID), .showRoomDirectorySearchScreen):
return .roomDirectorySearchScreen(selectedRoomID: selectedRoomID)
case (.roomDirectorySearchScreen(let selectedRoomID), .dismissedRoomDirectorySearchScreen):
return .roomList(selectedRoomID: selectedRoomID)
case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
case (_, .showUserProfileScreen):
return .userProfileScreen
case (.userProfileScreen, .dismissedUserProfileScreen):
return .roomList(selectedRoomID: nil)
return .roomList(roomListSelectedRoomID: nil)
case (.roomList, .showShareExtensionRoomList(let sharePayload)):
return .shareExtensionRoomList(sharePayload: sharePayload)
case (.shareExtensionRoomList, .dismissedShareExtensionRoomList):
return .roomList(selectedRoomID: nil)
return .roomList(roomListSelectedRoomID: nil)
case (.roomList(let roomListSelectedRoomID), .presentReportRoomScreen):
return .reportRoomScreen(roomListSelectedRoomID: roomListSelectedRoomID)
case (.reportRoomScreen(let roomListSelectedRoomID), .dismissedReportRoomScreen):
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
default:
return nil
@@ -231,8 +243,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 .roomList(let selectedRoomID):
return roomID == selectedRoomID
case .roomList(let roomListSelectedRoomID):
return roomID == roomListSelectedRoomID
default:
return false
}