mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Accept space invites from a home screen cell. (#4554)
* Push the SpaceScreen after accepting an invite from the room list. * Add a test for accepting a space invite.
This commit is contained in:
@@ -29,6 +29,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
// periphery:ignore - retaining purpose
|
||||
private var roomFlowCoordinator: RoomFlowCoordinator?
|
||||
// periphery:ignore - retaining purpose
|
||||
private var spaceFlowCoordinator: SpaceFlowCoordinator?
|
||||
|
||||
// periphery:ignore - retaining purpose
|
||||
private var bugReportFlowCoordinator: BugReportFlowCoordinator?
|
||||
@@ -117,7 +119,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
case .roomDetails(let roomID):
|
||||
if stateMachine.state.roomListSelectedRoomID == roomID {
|
||||
if case .room(roomID) = stateMachine.state.detailState {
|
||||
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||
} else {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .roomDetails), userInfo: .init(animated: animated))
|
||||
@@ -154,7 +156,7 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.processEvent(.showShareExtensionRoomList(sharePayload: payload), userInfo: .init(animated: animated))
|
||||
}
|
||||
case .transferOwnership(let roomID):
|
||||
if stateMachine.state.roomListSelectedRoomID == roomID {
|
||||
if case .room(roomID) = stateMachine.state.detailState {
|
||||
roomFlowCoordinator?.handleAppRoute(appRoute, animated: animated)
|
||||
} else {
|
||||
stateMachine.processEvent(.selectRoom(roomID: roomID, via: [], entryPoint: .transferOwnership))
|
||||
@@ -178,12 +180,15 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func setupStateMachine() {
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
guard let self else { return }
|
||||
let animated = (context.userInfo as? ChatsFlowCoordinatorStateMachine.EventUserInfo)?.animated ?? true
|
||||
|
||||
let userInfo = context.userInfo as? ChatsFlowCoordinatorStateMachine.EventUserInfo
|
||||
let animated = userInfo?.animated ?? true
|
||||
|
||||
switch (context.fromState, context.event, context.toState) {
|
||||
case (.initial, .start, .roomList):
|
||||
presentHomeScreen()
|
||||
case(.roomList(let roomListSelectedRoomID), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
||||
if roomListSelectedRoomID == roomID,
|
||||
case(.roomList(let detailState), .selectRoom(let roomID, let via, let entryPoint), .roomList):
|
||||
if case .room(roomID) = detailState,
|
||||
!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 {
|
||||
@@ -195,11 +200,20 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
roomFlowCoordinator.handleAppRoute(route, animated: animated)
|
||||
} else {
|
||||
if case .space = detailState {
|
||||
dismissRoomFlow(animated: animated)
|
||||
}
|
||||
startRoomFlow(roomID: roomID, via: via, entryPoint: entryPoint, animated: animated)
|
||||
}
|
||||
actionsSubject.send(.hideCallScreenOverlay) // Turn any active call into a PiP so that navigation from a notification is visible to the user.
|
||||
case(.roomList, .deselectRoom, .roomList):
|
||||
dismissRoomFlow(animated: animated)
|
||||
|
||||
case(.roomList, .startSpaceFlow, .roomList):
|
||||
guard let spaceRoomListProxy = userInfo?.spaceRoomListProxy else { fatalError("A space room list proxy is required.") }
|
||||
startSpaceFlow(spaceRoomListProxy: spaceRoomListProxy, animated: animated)
|
||||
case (.roomList, .finishedSpaceFlow, .roomList):
|
||||
dismissSpaceFlow(animated: animated)
|
||||
|
||||
case (.roomList, .feedbackScreen, .feedbackScreen):
|
||||
bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator),
|
||||
@@ -264,8 +278,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
stateMachine.addTransitionHandler { [weak self] context in
|
||||
switch context.toState {
|
||||
case .roomList(let roomListSelectedRoomID):
|
||||
self?.selectedRoomSubject.send(roomListSelectedRoomID)
|
||||
case .roomList(detailState: .room(let detailStateRoomID)):
|
||||
self?.selectedRoomSubject.send(detailStateRoomID)
|
||||
default:
|
||||
break
|
||||
}
|
||||
@@ -351,9 +365,11 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
handleAppRoute(.roomDetails(roomID: roomID), animated: true)
|
||||
case .presentReportRoom(let roomID):
|
||||
stateMachine.processEvent(.presentReportRoomScreen(roomID: roomID))
|
||||
case .presentSpace(let spaceRoomListProxy):
|
||||
stateMachine.processEvent(.startSpaceFlow, userInfo: .init(animated: true, spaceRoomListProxy: spaceRoomListProxy))
|
||||
case .roomLeft(let roomID):
|
||||
if case .roomList(roomListSelectedRoomID: let roomListSelectedRoomID) = stateMachine.state,
|
||||
roomListSelectedRoomID == roomID {
|
||||
if case .roomList(detailState: .room(let detailStateRoomID)) = stateMachine.state,
|
||||
detailStateRoomID == roomID {
|
||||
clearRoute(animated: true)
|
||||
}
|
||||
case .presentSettingsScreen:
|
||||
@@ -398,8 +414,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
switch action {
|
||||
case .dismiss(let shouldLeaveRoom):
|
||||
if shouldLeaveRoom,
|
||||
case .roomList(let roomListSelectedRoomID) = stateMachine.state,
|
||||
roomListSelectedRoomID == roomID {
|
||||
case .roomList(detailState: .room(let detailStateRoomID)) = stateMachine.state,
|
||||
detailStateRoomID == roomID {
|
||||
clearRoute(animated: true)
|
||||
}
|
||||
navigationSplitCoordinator.setSheetCoordinator(nil)
|
||||
@@ -491,6 +507,43 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomFlowCoordinator = nil
|
||||
}
|
||||
|
||||
// MARK: Space Flow
|
||||
|
||||
private func startSpaceFlow(spaceRoomListProxy: SpaceRoomListProxyProtocol, animated: Bool) {
|
||||
let coordinator = SpaceFlowCoordinator(entryPoint: .space(spaceRoomListProxy),
|
||||
spaceServiceProxy: userSession.clientProxy.spaceService,
|
||||
isChildFlow: false,
|
||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||
flowParameters: flowParameters)
|
||||
coordinator.actionsPublisher
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .presentCallScreen(let roomProxy):
|
||||
actionsSubject.send(.showCallScreen(roomProxy: roomProxy))
|
||||
case .verifyUser(let userID):
|
||||
actionsSubject.send(.sessionVerification(.userInitiator(userID: userID)))
|
||||
case .finished:
|
||||
stateMachine.processEvent(.finishedSpaceFlow)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
spaceFlowCoordinator = coordinator
|
||||
|
||||
if navigationSplitCoordinator.detailCoordinator !== detailNavigationStackCoordinator {
|
||||
navigationSplitCoordinator.setDetailCoordinator(detailNavigationStackCoordinator, animated: animated)
|
||||
}
|
||||
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func dismissSpaceFlow(animated: Bool) {
|
||||
// Based on dismissRoomFlow, past me was very insistent that this must happen after the flow has tidied the stack 😅.
|
||||
navigationSplitCoordinator.setDetailCoordinator(nil, animated: animated)
|
||||
roomFlowCoordinator = nil
|
||||
}
|
||||
|
||||
// MARK: Start Chat
|
||||
|
||||
private func presentStartChat(animated: Bool) {
|
||||
|
||||
@@ -10,63 +10,69 @@ import Foundation
|
||||
import SwiftState
|
||||
|
||||
class ChatsFlowCoordinatorStateMachine {
|
||||
enum DetailState: Hashable {
|
||||
case room(roomID: String)
|
||||
case space
|
||||
}
|
||||
|
||||
/// States the AppCoordinator can find itself in
|
||||
enum State: StateType {
|
||||
/// The initial state, used before the coordinator starts
|
||||
case initial
|
||||
|
||||
/// Showing the home screen. The `roomListSelectedRoomID` represents the timeline shown on the detail panel (if any)
|
||||
case roomList(roomListSelectedRoomID: String?)
|
||||
case roomList(detailState: DetailState?)
|
||||
|
||||
/// Showing the feedback screen.
|
||||
case feedbackScreen(roomListSelectedRoomID: String?)
|
||||
case feedbackScreen(detailState: DetailState?)
|
||||
|
||||
/// Showing the recovery key screen.
|
||||
case recoveryKeyScreen(roomListSelectedRoomID: String?)
|
||||
case recoveryKeyScreen(detailState: DetailState?)
|
||||
|
||||
/// Showing the encryption reset flow.
|
||||
case encryptionResetFlow(roomListSelectedRoomID: String?)
|
||||
case encryptionResetFlow(detailState: DetailState?)
|
||||
|
||||
/// Showing the start chat screen
|
||||
case startChatScreen(roomListSelectedRoomID: String?)
|
||||
case startChatScreen(detailState: DetailState?)
|
||||
|
||||
/// Showing the logout flows
|
||||
case logoutConfirmationScreen(roomListSelectedRoomID: String?)
|
||||
case logoutConfirmationScreen(detailState: DetailState?)
|
||||
|
||||
/// Showing Room Directory Search screen
|
||||
case roomDirectorySearchScreen(roomListSelectedRoomID: String?)
|
||||
case roomDirectorySearchScreen(detailState: DetailState?)
|
||||
|
||||
/// 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 reportRoomScreen(detailState: DetailState?)
|
||||
|
||||
case shareExtensionRoomList(sharePayload: ShareExtensionPayload)
|
||||
|
||||
case declineAndBlockUserScreen(roomListSelectedRoomID: String?)
|
||||
case declineAndBlockUserScreen(detailState: DetailState?)
|
||||
|
||||
/// The selected room ID from the state if available.
|
||||
var roomListSelectedRoomID: String? {
|
||||
var detailState: DetailState? {
|
||||
switch self {
|
||||
case .initial, .userProfileScreen, .shareExtensionRoomList:
|
||||
nil
|
||||
case .roomList(let roomListSelectedRoomID),
|
||||
.feedbackScreen(let roomListSelectedRoomID),
|
||||
.recoveryKeyScreen(let roomListSelectedRoomID),
|
||||
.encryptionResetFlow(let roomListSelectedRoomID),
|
||||
.startChatScreen(let roomListSelectedRoomID),
|
||||
.logoutConfirmationScreen(let roomListSelectedRoomID),
|
||||
.roomDirectorySearchScreen(let roomListSelectedRoomID),
|
||||
.reportRoomScreen(let roomListSelectedRoomID),
|
||||
.declineAndBlockUserScreen(let roomListSelectedRoomID):
|
||||
roomListSelectedRoomID
|
||||
case .roomList(let detailState),
|
||||
.feedbackScreen(let detailState),
|
||||
.recoveryKeyScreen(let detailState),
|
||||
.encryptionResetFlow(let detailState),
|
||||
.startChatScreen(let detailState),
|
||||
.logoutConfirmationScreen(let detailState),
|
||||
.roomDirectorySearchScreen(let detailState),
|
||||
.reportRoomScreen(let detailState),
|
||||
.declineAndBlockUserScreen(let detailState):
|
||||
detailState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EventUserInfo {
|
||||
let animated: Bool
|
||||
var spaceRoomListProxy: SpaceRoomListProxyProtocol?
|
||||
}
|
||||
|
||||
/// Events that can be triggered on the AppCoordinator state machine
|
||||
@@ -82,6 +88,13 @@ class ChatsFlowCoordinatorStateMachine {
|
||||
/// The room screen has been dismissed
|
||||
case deselectRoom
|
||||
|
||||
/// Request presentation of a space.
|
||||
///
|
||||
/// The space's `RoomListProxyProtocol` must be provided in the `EventUserInfo`.
|
||||
case startSpaceFlow
|
||||
/// The space has been dismissed.
|
||||
case finishedSpaceFlow
|
||||
|
||||
/// Request presentation of the feedback screen
|
||||
case feedbackScreen
|
||||
/// The feedback screen has been dismissed
|
||||
@@ -134,59 +147,64 @@ class ChatsFlowCoordinatorStateMachine {
|
||||
}
|
||||
|
||||
private func configure() {
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(roomListSelectedRoomID: nil)])
|
||||
stateMachine.addRoutes(event: .start, transitions: [.initial => .roomList(detailState: nil)])
|
||||
|
||||
stateMachine.addRouteMapping { event, fromState, _ in
|
||||
switch (fromState, event) {
|
||||
case (.roomList, .selectRoom(let roomID, _, _)):
|
||||
return .roomList(roomListSelectedRoomID: roomID)
|
||||
return .roomList(detailState: .room(roomID: roomID))
|
||||
case (.roomList, .deselectRoom):
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
return .roomList(detailState: nil)
|
||||
|
||||
case (.roomList, .startSpaceFlow):
|
||||
return .roomList(detailState: .space)
|
||||
case (.roomList, .finishedSpaceFlow):
|
||||
return .roomList(detailState: nil)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .feedbackScreen):
|
||||
return .feedbackScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.feedbackScreen(let roomListSelectedRoomID), .dismissedFeedbackScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .feedbackScreen):
|
||||
return .feedbackScreen(detailState: detailState)
|
||||
case (.feedbackScreen(let detailState), .dismissedFeedbackScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .showRecoveryKeyScreen):
|
||||
return .recoveryKeyScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.recoveryKeyScreen(let roomListSelectedRoomID), .dismissedRecoveryKeyScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .showRecoveryKeyScreen):
|
||||
return .recoveryKeyScreen(detailState: detailState)
|
||||
case (.recoveryKeyScreen(let detailState), .dismissedRecoveryKeyScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .startEncryptionResetFlow):
|
||||
return .encryptionResetFlow(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.encryptionResetFlow(let roomListSelectedRoomID), .finishedEncryptionResetFlow):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .startEncryptionResetFlow):
|
||||
return .encryptionResetFlow(detailState: detailState)
|
||||
case (.encryptionResetFlow(let detailState), .finishedEncryptionResetFlow):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .showStartChatScreen):
|
||||
return .startChatScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.startChatScreen(let roomListSelectedRoomID), .dismissedStartChatScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .showStartChatScreen):
|
||||
return .startChatScreen(detailState: detailState)
|
||||
case (.startChatScreen(let detailState), .dismissedStartChatScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .showRoomDirectorySearchScreen):
|
||||
return .roomDirectorySearchScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomDirectorySearchScreen(let roomListSelectedRoomID), .dismissedRoomDirectorySearchScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .showRoomDirectorySearchScreen):
|
||||
return .roomDirectorySearchScreen(detailState: detailState)
|
||||
case (.roomDirectorySearchScreen(let detailState), .dismissedRoomDirectorySearchScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case (_, .showUserProfileScreen):
|
||||
return .userProfileScreen
|
||||
case (.userProfileScreen, .dismissedUserProfileScreen):
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
return .roomList(detailState: nil)
|
||||
|
||||
case (.roomList, .showShareExtensionRoomList(let sharePayload)):
|
||||
return .shareExtensionRoomList(sharePayload: sharePayload)
|
||||
case (.shareExtensionRoomList, .dismissedShareExtensionRoomList):
|
||||
return .roomList(roomListSelectedRoomID: nil)
|
||||
return .roomList(detailState: nil)
|
||||
|
||||
case (.roomList(let roomListSelectedRoomID), .presentReportRoomScreen):
|
||||
return .reportRoomScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.reportRoomScreen(let roomListSelectedRoomID), .dismissedReportRoomScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.roomList(let detailState), .presentReportRoomScreen):
|
||||
return .reportRoomScreen(detailState: detailState)
|
||||
case (.reportRoomScreen(let detailState), .dismissedReportRoomScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
case(.roomList(let roomListSelectedRoomID), .presentDeclineAndBlockScreen):
|
||||
return .declineAndBlockUserScreen(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case (.declineAndBlockUserScreen(let roomListSelectedRoomID), .dismissedDeclineAndBlockScreen):
|
||||
return .roomList(roomListSelectedRoomID: roomListSelectedRoomID)
|
||||
case(.roomList(let detailState), .presentDeclineAndBlockScreen):
|
||||
return .declineAndBlockUserScreen(detailState: detailState)
|
||||
case (.declineAndBlockUserScreen(let detailState), .dismissedDeclineAndBlockScreen):
|
||||
return .roomList(detailState: detailState)
|
||||
|
||||
default:
|
||||
return nil
|
||||
@@ -221,8 +239,8 @@ class ChatsFlowCoordinatorStateMachine {
|
||||
/// Flag indicating the machine is displaying room screen with given room identifier
|
||||
func isDisplayingRoomScreen(withRoomID roomID: String) -> Bool {
|
||||
switch stateMachine.state {
|
||||
case .roomList(let roomListSelectedRoomID):
|
||||
return roomID == roomListSelectedRoomID
|
||||
case .roomList(detailState: .room(let detailStateRoomID)):
|
||||
return roomID == detailStateRoomID
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ struct InvitedRoomProxyMockConfiguration {
|
||||
var id = UUID().uuidString
|
||||
var name: String?
|
||||
var avatarURL: URL?
|
||||
var isSpace = false
|
||||
var members: [RoomMemberProxyMock] = .allMembers
|
||||
var inviter: RoomMemberProxyMock = .mockAlice
|
||||
}
|
||||
@@ -44,7 +45,7 @@ extension RoomInfoProxyMock {
|
||||
avatarURL = configuration.avatarURL
|
||||
|
||||
isDirect = false
|
||||
isSpace = false
|
||||
isSpace = configuration.isSpace
|
||||
successor = nil
|
||||
isFavourite = false
|
||||
canonicalAlias = nil
|
||||
|
||||
@@ -341,4 +341,49 @@ extension Array where Element == RoomSummary {
|
||||
isFavourite: false,
|
||||
isTombstoned: false)
|
||||
]
|
||||
|
||||
static let mockSpaceInvites: [Element] = [
|
||||
RoomSummary(room: RoomSDKMock(),
|
||||
id: "someAwesomeSpaceID1",
|
||||
joinRequestType: .invite(inviter: RoomMemberProxyMock.mockCharlie),
|
||||
name: "First space",
|
||||
isDirect: false,
|
||||
isSpace: true,
|
||||
avatarURL: .mockMXCAvatar,
|
||||
heroes: [],
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: "#footest:somewhere.org",
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false,
|
||||
isTombstoned: false),
|
||||
RoomSummary(room: RoomSDKMock(),
|
||||
id: "someAwesomeSpaceID2",
|
||||
joinRequestType: .invite(inviter: RoomMemberProxyMock.mockCharlie),
|
||||
name: "Second space",
|
||||
isDirect: false,
|
||||
isSpace: true,
|
||||
avatarURL: nil,
|
||||
heroes: [],
|
||||
activeMembersCount: 0,
|
||||
lastMessage: nil,
|
||||
lastMessageDate: nil,
|
||||
unreadMessagesCount: 0,
|
||||
unreadMentionsCount: 0,
|
||||
unreadNotificationsCount: 0,
|
||||
notificationMode: nil,
|
||||
canonicalAlias: nil,
|
||||
alternativeAliases: [],
|
||||
hasOngoingCall: false,
|
||||
isMarkedUnread: false,
|
||||
isFavourite: false,
|
||||
isTombstoned: false)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ enum HomeScreenCoordinatorAction {
|
||||
case presentRoomDetails(roomIdentifier: String)
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||
case presentSpace(SpaceRoomListProxyProtocol)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case transferOwnership(roomIdentifier: String)
|
||||
case presentSettingsScreen
|
||||
@@ -67,6 +68,10 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentRoomDetails(roomIdentifier: roomIdentifier))
|
||||
case .presentReportRoom(let roomIdentifier):
|
||||
actionsSubject.send(.presentReportRoom(roomIdentifier: roomIdentifier))
|
||||
case .presentDeclineAndBlock(let userID, let roomID):
|
||||
actionsSubject.send(.presentDeclineAndBlock(userID: userID, roomID: roomID))
|
||||
case .presentSpace(let spaceRoomListProxy):
|
||||
actionsSubject.send(.presentSpace(spaceRoomListProxy))
|
||||
case .roomLeft(roomIdentifier: let roomIdentifier):
|
||||
actionsSubject.send(.roomLeft(roomIdentifier: roomIdentifier))
|
||||
case .presentFeedbackScreen:
|
||||
@@ -85,8 +90,6 @@ final class HomeScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentGlobalSearch)
|
||||
case .logout:
|
||||
actionsSubject.send(.logout)
|
||||
case .presentDeclineAndBlock(let userID, let roomID):
|
||||
actionsSubject.send(.presentDeclineAndBlock(userID: userID, roomID: roomID))
|
||||
case .transferOwnership(let roomIdentifier):
|
||||
actionsSubject.send(.transferOwnership(roomIdentifier: roomIdentifier))
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ import Combine
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
enum HomeScreenViewModelAction: Equatable {
|
||||
enum HomeScreenViewModelAction {
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentRoomDetails(roomIdentifier: String)
|
||||
case presentReportRoom(roomIdentifier: String)
|
||||
case presentDeclineAndBlock(userID: String, roomID: String)
|
||||
case presentSpace(SpaceRoomListProxyProtocol)
|
||||
case roomLeft(roomIdentifier: String)
|
||||
case transferOwnership(roomIdentifier: String)
|
||||
case presentSecureBackupSettings
|
||||
|
||||
@@ -414,11 +414,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
switch await userSession.clientProxy.joinRoom(roomID, via: []) {
|
||||
case .success:
|
||||
actionsSubject.send(.presentRoom(roomIdentifier: roomID))
|
||||
analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect,
|
||||
isSpace: roomProxy.info.isSpace,
|
||||
activeMemberCount: UInt(roomProxy.info.activeMembersCount))
|
||||
appSettings.seenInvites.remove(roomID)
|
||||
await finishAcceptInvite(roomProxy: roomProxy)
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case .invalidInvite:
|
||||
@@ -429,6 +425,28 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
}
|
||||
}
|
||||
|
||||
private func finishAcceptInvite(roomProxy: InvitedRoomProxyProtocol) async {
|
||||
if roomProxy.info.isSpace {
|
||||
let spaceService = userSession.clientProxy.spaceService
|
||||
|
||||
switch await spaceService.spaceRoomList(spaceID: roomProxy.id, parent: nil) {
|
||||
case .success(let spaceRoomListProxy):
|
||||
actionsSubject.send(.presentSpace(spaceRoomListProxy))
|
||||
case .failure(let error):
|
||||
MXLog.error("Failed to get the space room list after accepting invite: \(error)")
|
||||
displayError()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
actionsSubject.send(.presentRoom(roomIdentifier: roomProxy.id))
|
||||
}
|
||||
|
||||
analyticsService.trackJoinedRoom(isDM: roomProxy.info.isDirect,
|
||||
isSpace: roomProxy.info.isSpace,
|
||||
activeMemberCount: UInt(roomProxy.info.activeMembersCount))
|
||||
appSettings.seenInvites.remove(roomProxy.id)
|
||||
}
|
||||
|
||||
private func showDeclineInviteConfirmationAlert(roomID: String) async {
|
||||
guard let room = state.rooms.first(where: { $0.id == roomID }) else {
|
||||
displayError()
|
||||
|
||||
@@ -50,29 +50,29 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
navigationSplitCoordinator: splitCoordinator,
|
||||
flowParameters: flowParameters)
|
||||
|
||||
let deferred = deferFulfillment(stateMachineFactory.chatsFlowStatePublisher) { $0 == .roomList(roomListSelectedRoomID: nil) }
|
||||
let deferred = deferFulfillment(stateMachineFactory.chatsFlowStatePublisher) { $0 == .roomList(detailState: nil) }
|
||||
chatsFlowCoordinator.start()
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testRoomPresentation() async throws {
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
|
||||
try await process(route: .roomList, expectedState: .roomList(detailState: nil))
|
||||
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
|
||||
try await process(route: .roomList, expectedState: .roomList(detailState: nil))
|
||||
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
|
||||
@@ -82,25 +82,25 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
func testRoomAliasPresentation() async throws {
|
||||
clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "1", servers: []))
|
||||
|
||||
try await process(route: .roomAlias("#alias:matrix.org"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomAlias("#alias:matrix.org"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
|
||||
try await process(route: .roomList, expectedState: .roomList(detailState: nil))
|
||||
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
clientProxy.resolveRoomAliasReturnValue = .success(.init(roomId: "2", servers: []))
|
||||
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
|
||||
try await process(route: .roomList, expectedState: .roomList(detailState: nil))
|
||||
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
|
||||
@@ -108,27 +108,27 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRoomDetailsPresentation() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedState: .roomList(roomListSelectedRoomID: nil))
|
||||
try await process(route: .roomList, expectedState: .roomList(detailState: nil))
|
||||
XCTAssertNil(detailNavigationStack?.rootCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
}
|
||||
|
||||
func testStackUnwinding() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
try await process(route: .room(roomID: "2", via: []), expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
}
|
||||
|
||||
func testNoOp() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
@@ -141,17 +141,17 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSwitchToDifferentDetails() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .roomDetails(roomID: "2"), expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
try await process(route: .roomDetails(roomID: "2"), expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
}
|
||||
|
||||
func testPushDetails() async throws {
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
@@ -166,17 +166,17 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testReplaceDetailsWithTimeline() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
}
|
||||
|
||||
func testUserProfileClearsStack() async throws {
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .roomDetails(roomID: "1"), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
XCTAssertNil(splitCoordinator?.sheetCoordinator)
|
||||
@@ -191,7 +191,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRoomClearsStack() async throws {
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -203,7 +203,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue(detailNavigationStack?.stackCoordinators.first is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
|
||||
try await process(route: .room(roomID: "3", via: []), expectedState: .roomList(detailState: .room(roomID: "3")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -211,7 +211,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
|
||||
func testEventRoutes() async throws {
|
||||
// A regular event route should set its room as the root of the stack and focus on the event.
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -229,7 +229,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "2")
|
||||
|
||||
// A subsequent regular event route should clear the stack and set the new room as the root of the stack.
|
||||
try await process(route: .event(eventID: "3", roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
|
||||
try await process(route: .event(eventID: "3", roomID: "3", via: []), expectedState: .roomList(detailState: .room(roomID: "3")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -237,7 +237,7 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertEqual(timelineControllerFactory.buildTimelineControllerRoomProxyInitialFocussedEventIDTimelineItemFactoryMediaProviderReceivedArguments?.initialFocussedEventID, "3")
|
||||
|
||||
// A regular event route for the same room should set a new instance of the room as the root of the stack.
|
||||
try await process(route: .event(eventID: "4", roomID: "3", via: []), expectedState: .roomList(roomListSelectedRoomID: "3"))
|
||||
try await process(route: .event(eventID: "4", roomID: "3", via: []), expectedState: .roomList(detailState: .room(roomID: "3")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(detailNavigationStack?.stackCoordinators.count, 0)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -247,24 +247,24 @@ class ChatsFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testShareMediaRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .mediaFiles(roomID: "2", mediaFiles: [.init(url: .picturesDirectory, suggestedName: nil)])
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertTrue((splitCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is MediaUploadPreviewScreenCoordinator)
|
||||
}
|
||||
|
||||
func testShareTextRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedState: .roomList(roomListSelectedRoomID: "2"))
|
||||
expectedState: .roomList(detailState: .room(roomID: "2")))
|
||||
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(splitCoordinator?.sheetCoordinator, "The media upload sheet shouldn't be shown when sharing text.")
|
||||
|
||||
@@ -265,7 +265,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInviteUnreadBadge() async throws {
|
||||
setupViewModel(withInvites: true)
|
||||
setupViewModel(invites: .rooms)
|
||||
var invites = context.viewState.rooms.invites
|
||||
XCTAssertEqual(invites.count, 2)
|
||||
|
||||
@@ -288,7 +288,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testAcceptInvite() async throws {
|
||||
setupViewModel(withInvites: true)
|
||||
setupViewModel(invites: .rooms)
|
||||
|
||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||
appSettings.seenInvites = Set(invitedRoomIDs)
|
||||
@@ -302,8 +302,25 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
||||
}
|
||||
|
||||
func testAcceptSpaceInvite() async throws {
|
||||
setupViewModel(invites: .spaces)
|
||||
|
||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||
appSettings.seenInvites = Set(invitedRoomIDs)
|
||||
XCTAssertEqual(invitedRoomIDs.count, 2)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) {
|
||||
$0 == .presentSpace(SpaceRoomListProxyMock(.init(spaceRoomProxy: SpaceRoomProxyMock(.init(id: invitedRoomIDs[0], isSpace: true)))))
|
||||
}
|
||||
context.send(viewAction: .acceptInvite(roomIdentifier: invitedRoomIDs[0]))
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(appSettings.seenInvites, [invitedRoomIDs[1]])
|
||||
XCTAssertFalse(notificationManager.removeDeliveredMessageNotificationsForCalled, "The notification will be dismissed when opening the room.")
|
||||
}
|
||||
|
||||
func testDeclineInvite() async throws {
|
||||
setupViewModel(withInvites: true)
|
||||
setupViewModel(invites: .rooms)
|
||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||
appSettings.seenInvites = Set(invitedRoomIDs)
|
||||
XCTAssertEqual(invitedRoomIDs.count, 2)
|
||||
@@ -331,7 +348,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDeclineAndBlockInvite() async throws {
|
||||
setupViewModel(withInvites: true)
|
||||
setupViewModel(invites: .rooms)
|
||||
let invitedRoomIDs = context.viewState.rooms.invites.compactMap(\.roomID)
|
||||
appSettings.seenInvites = Set(invitedRoomIDs)
|
||||
XCTAssertEqual(invitedRoomIDs.count, 2)
|
||||
@@ -347,20 +364,41 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func setupViewModel(securityStatePublisher: CurrentValuePublisher<SessionSecurityState, Never>? = nil, withInvites: Bool = false) {
|
||||
enum InviteType { case rooms, spaces }
|
||||
|
||||
private func setupViewModel(securityStatePublisher: CurrentValuePublisher<SessionSecurityState, Never>? = nil, invites: InviteType? = nil) {
|
||||
var rooms: [RoomSummary] = .mockRooms
|
||||
if withInvites {
|
||||
|
||||
switch invites {
|
||||
case .rooms:
|
||||
rooms += .mockInvites
|
||||
case .spaces:
|
||||
rooms += .mockSpaceInvites
|
||||
case nil:
|
||||
break
|
||||
}
|
||||
|
||||
roomSummaryProvider = RoomSummaryProviderMock(.init(state: .loaded(rooms)))
|
||||
|
||||
clientProxy = ClientProxyMock(.init(userID: "@mock:client.com",
|
||||
roomSummaryProvider: roomSummaryProvider))
|
||||
if withInvites {
|
||||
clientProxy.joinRoomViaReturnValue = .success(())
|
||||
clientProxy.joinRoomAliasReturnValue = .success(())
|
||||
clientProxy.roomForIdentifierClosure = { _ in .invited(InvitedRoomProxyMock(.init())) }
|
||||
|
||||
clientProxy.joinRoomViaReturnValue = .success(())
|
||||
clientProxy.joinRoomAliasReturnValue = .success(())
|
||||
|
||||
switch invites {
|
||||
case .rooms:
|
||||
clientProxy.roomForIdentifierClosure = { roomID in .invited(InvitedRoomProxyMock(.init(id: roomID))) }
|
||||
case .spaces:
|
||||
clientProxy.roomForIdentifierClosure = { spaceID in .invited(InvitedRoomProxyMock(.init(id: spaceID, isSpace: true))) }
|
||||
|
||||
let spaceServiceProxy = SpaceServiceProxyMock(.init())
|
||||
spaceServiceProxy.spaceRoomListSpaceIDParentClosure = { spaceID, _ in
|
||||
.success(SpaceRoomListProxyMock(.init(spaceRoomProxy: SpaceRoomProxyMock(.init(id: spaceID, isSpace: true)))))
|
||||
}
|
||||
clientProxy.underlyingSpaceService = spaceServiceProxy
|
||||
case nil:
|
||||
break
|
||||
}
|
||||
|
||||
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||
@@ -390,3 +428,42 @@ private extension [HomeScreenRoom] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension HomeScreenViewModelAction: @retroactive Equatable {
|
||||
public static func == (lhs: HomeScreenViewModelAction, rhs: HomeScreenViewModelAction) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.presentRoom(let lhsID), .presentRoom(let rhsID)):
|
||||
lhsID == rhsID
|
||||
case (.presentRoomDetails(let lhsID), .presentRoomDetails(let rhsID)):
|
||||
lhsID == rhsID
|
||||
case (.presentReportRoom(let lhsID), .presentReportRoom(let rhsID)):
|
||||
lhsID == rhsID
|
||||
case (.presentDeclineAndBlock(let lhsUserID, let lhsRoomID), .presentDeclineAndBlock(let rhsUserID, let rhsRoomID)):
|
||||
lhsUserID == rhsUserID && lhsRoomID == rhsRoomID
|
||||
case (.presentSpace(let lhsSpaceRoomListProxy), .presentSpace(let rhsSpaceRoomListProxy)):
|
||||
lhsSpaceRoomListProxy.spaceRoomProxy.id == rhsSpaceRoomListProxy.spaceRoomProxy.id
|
||||
case (.roomLeft(let lhsID), .roomLeft(let rhsID)):
|
||||
lhsID == rhsID
|
||||
case (.transferOwnership(let lhsID), .transferOwnership(let rhsID)):
|
||||
lhsID == rhsID
|
||||
case (.presentSecureBackupSettings, .presentSecureBackupSettings):
|
||||
true
|
||||
case (.presentRecoveryKeyScreen, .presentRecoveryKeyScreen):
|
||||
true
|
||||
case (.presentEncryptionResetScreen, .presentEncryptionResetScreen):
|
||||
true
|
||||
case (.presentSettingsScreen, .presentSettingsScreen):
|
||||
true
|
||||
case (.presentFeedbackScreen, .presentFeedbackScreen):
|
||||
true
|
||||
case (.presentStartChatScreen, .presentStartChatScreen):
|
||||
true
|
||||
case (.presentGlobalSearch, .presentGlobalSearch):
|
||||
true
|
||||
case (.logout, .logout):
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRoomPresentation() async throws {
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
}
|
||||
@@ -86,14 +86,14 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue((tabCoordinator?.sheetCoordinator as? NavigationStackCoordinator)?.rootCoordinator is SettingsScreenCoordinator)
|
||||
XCTAssertNil(detailCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertNil((tabCoordinator?.sheetCoordinator))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
}
|
||||
|
||||
func testChildRoomPresentation() async throws {
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .room(roomID: "1", via: []), expectedChatsState: .roomList(detailState: .room(roomID: "1")))
|
||||
let detailNavigationStack = try XCTUnwrap(detailNavigationStack, "There must be a navigation stack.")
|
||||
XCTAssertTrue(detailNavigationStack.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNotNil(detailCoordinator)
|
||||
@@ -121,14 +121,14 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testShareMediaRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .mediaFiles(roomID: "2", mediaFiles: [.init(url: .picturesDirectory, suggestedName: nil)])
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedChatsState: .roomList(roomListSelectedRoomID: "2"))
|
||||
expectedChatsState: .roomList(detailState: .room(roomID: "2")))
|
||||
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||
@@ -149,14 +149,14 @@ class UserSessionFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testShareTextRouteWithRoom() async throws {
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(roomListSelectedRoomID: "1"))
|
||||
try await process(route: .event(eventID: "1", roomID: "1", via: []), expectedChatsState: .roomList(detailState: .room(roomID: "1")))
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||
XCTAssertNil(chatsSplitCoordinator?.sheetCoordinator)
|
||||
|
||||
let sharePayload: ShareExtensionPayload = .text(roomID: "2", text: "Important text")
|
||||
try await process(route: .share(sharePayload),
|
||||
expectedChatsState: .roomList(roomListSelectedRoomID: "2"))
|
||||
expectedChatsState: .roomList(detailState: .room(roomID: "2")))
|
||||
|
||||
XCTAssertTrue(detailNavigationStack?.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertNil(tabCoordinator?.sheetCoordinator)
|
||||
|
||||
Reference in New Issue
Block a user