mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Invite the DM recipient to a new room (#5588)
* handle the details to invite users flow. * implemented the room creation in the invite screen, and its dismissal + navigaton to the newly created room * updated preview and unit tests * pr suggestions
This commit is contained in:
@@ -60,6 +60,13 @@ struct FocusEvent: Hashable {
|
|||||||
let shouldSetPin: Bool
|
let shouldSetPin: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum InviteUsersFlow: Hashable {
|
||||||
|
/// Invite people into the current room.
|
||||||
|
case existingRoom
|
||||||
|
/// Start a new room with the given invitee pre-selected and mandatory.
|
||||||
|
case newRoom(mandatoryInvitee: UserProfileProxy)
|
||||||
|
}
|
||||||
|
|
||||||
// swiftlint:disable:next type_body_length
|
// swiftlint:disable:next type_body_length
|
||||||
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
private let roomID: String
|
private let roomID: String
|
||||||
@@ -537,8 +544,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
presentMediaUploadPreviewScreen(for: mediaURLs, timelineController: timelineController, animated: animated)
|
presentMediaUploadPreviewScreen(for: mediaURLs, timelineController: timelineController, animated: animated)
|
||||||
|
|
||||||
case (_, .presentInviteUsersScreen, .inviteUsersScreen):
|
case (_, .presentInviteUsersScreen(let flow), .inviteUsersScreen):
|
||||||
presentInviteUsersScreen()
|
presentInviteUsersScreen(flow: flow)
|
||||||
|
|
||||||
case (_, .presentTransferOwnershipScreen, .transferOwnershipScreen):
|
case (_, .presentTransferOwnershipScreen, .transferOwnershipScreen):
|
||||||
presentTransferOwnershipScreen()
|
presentTransferOwnershipScreen()
|
||||||
@@ -955,7 +962,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .presentNotificationSettingsScreen:
|
case .presentNotificationSettingsScreen:
|
||||||
stateMachine.tryEvent(.presentNotificationSettingsScreen)
|
stateMachine.tryEvent(.presentNotificationSettingsScreen)
|
||||||
case .presentInviteUsersScreen:
|
case .presentInviteUsersScreen:
|
||||||
stateMachine.tryEvent(.presentInviteUsersScreen)
|
stateMachine.tryEvent(.presentInviteUsersScreen(flow: .existingRoom))
|
||||||
|
case .presentInviteToNewRoom(let mandatoryInvitee):
|
||||||
|
stateMachine.tryEvent(.presentInviteUsersScreen(flow: .newRoom(mandatoryInvitee: mandatoryInvitee)))
|
||||||
case .presentPollsHistory:
|
case .presentPollsHistory:
|
||||||
stateMachine.tryEvent(.presentPollsHistory)
|
stateMachine.tryEvent(.presentPollsHistory)
|
||||||
case .presentRolesAndPermissionsScreen:
|
case .presentRolesAndPermissionsScreen:
|
||||||
@@ -1326,10 +1335,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentInviteUsersScreen() {
|
private func presentInviteUsersScreen(flow: InviteUsersFlow = .existingRoom) {
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
|
let roomType: InviteUsersScreenRoomType = switch flow {
|
||||||
|
case .existingRoom: .existingRoom(roomProxy: roomProxy)
|
||||||
|
case .newRoom(let mandatoryInvitee): .draft(mandatoryInvitees: [mandatoryInvitee])
|
||||||
|
}
|
||||||
|
|
||||||
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: userSession,
|
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: userSession,
|
||||||
roomProxy: roomProxy,
|
roomType: roomType,
|
||||||
isSkippable: false,
|
isSkippable: false,
|
||||||
userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
|
userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController,
|
||||||
@@ -1344,6 +1359,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .openRoom(let roomID):
|
||||||
|
guard case .newRoom = flow else { return }
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
stateMachine.tryEvent(.startChildFlow(roomID: roomID, via: [], entryPoint: .room))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ extension RoomFlowCoordinator {
|
|||||||
case roomDetailsEditScreen
|
case roomDetailsEditScreen
|
||||||
case notificationSettings
|
case notificationSettings
|
||||||
case globalNotificationSettings
|
case globalNotificationSettings
|
||||||
case inviteUsersScreen(previousState: State)
|
case inviteUsersScreen(flow: InviteUsersFlow, previousState: State)
|
||||||
case mediaUploadPicker(mode: MediaPickerScreenMode, previousState: State)
|
case mediaUploadPicker(mode: MediaPickerScreenMode, previousState: State)
|
||||||
case mediaUploadPreview(mediaURLs: [URL], previousState: State)
|
case mediaUploadPreview(mediaURLs: [URL], previousState: State)
|
||||||
case emojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>, previousState: State)
|
case emojiPicker(itemID: TimelineItemIdentifier, selectedEmojis: Set<String>, previousState: State)
|
||||||
@@ -136,7 +136,7 @@ extension RoomFlowCoordinator {
|
|||||||
case presentGlobalNotificationSettingsScreen
|
case presentGlobalNotificationSettingsScreen
|
||||||
case dismissGlobalNotificationSettingsScreen
|
case dismissGlobalNotificationSettingsScreen
|
||||||
|
|
||||||
case presentInviteUsersScreen
|
case presentInviteUsersScreen(flow: InviteUsersFlow)
|
||||||
case dismissInviteUsersScreen
|
case dismissInviteUsersScreen
|
||||||
|
|
||||||
case presentMediaUploadPicker(mode: MediaPickerScreenMode)
|
case presentMediaUploadPicker(mode: MediaPickerScreenMode)
|
||||||
@@ -404,9 +404,9 @@ extension RoomFlowCoordinator {
|
|||||||
case (.mediaUploadPicker(_, let previousMediaUploadPickerState), .presentMediaUploadPreview(let mediaURLs)):
|
case (.mediaUploadPicker(_, let previousMediaUploadPickerState), .presentMediaUploadPreview(let mediaURLs)):
|
||||||
return .mediaUploadPreview(mediaURLs: mediaURLs, previousState: previousMediaUploadPickerState)
|
return .mediaUploadPreview(mediaURLs: mediaURLs, previousState: previousMediaUploadPickerState)
|
||||||
|
|
||||||
case (_, .presentInviteUsersScreen):
|
case (_, .presentInviteUsersScreen(let flow)):
|
||||||
return .inviteUsersScreen(previousState: fromState)
|
return .inviteUsersScreen(flow: flow, previousState: fromState)
|
||||||
case (.inviteUsersScreen(let previousState), .dismissInviteUsersScreen):
|
case (.inviteUsersScreen(_, let previousState), .dismissInviteUsersScreen):
|
||||||
return previousState
|
return previousState
|
||||||
|
|
||||||
case (_, .presentTransferOwnershipScreen):
|
case (_, .presentTransferOwnershipScreen):
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private func presentInviteUsersScreen() {
|
private func presentInviteUsersScreen() {
|
||||||
let stackCoordinator = NavigationStackCoordinator()
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
|
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
|
||||||
roomProxy: roomProxy,
|
roomType: .existingRoom(roomProxy: roomProxy),
|
||||||
isSkippable: false,
|
isSkippable: false,
|
||||||
userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy),
|
userDiscoveryService: UserDiscoveryService(clientProxy: flowParameters.userSession.clientProxy),
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController,
|
||||||
@@ -276,6 +276,8 @@ final class RoomMembersFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .openRoom:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .transferOwnership:
|
case .transferOwnership:
|
||||||
stateMachine.tryEvent(.presentTransferOwnership)
|
stateMachine.tryEvent(.presentTransferOwnership)
|
||||||
case .presentRecipientDetails, .presentNotificationSettingsScreen, .presentReportRoomScreen,
|
case .presentRecipientDetails, .presentNotificationSettingsScreen, .presentReportRoomScreen,
|
||||||
.presentInviteUsersScreen, .presentPollsHistory, .presentCall,
|
.presentInviteUsersScreen, .presentInviteToNewRoom, .presentPollsHistory, .presentCall,
|
||||||
.presentPinnedEventsTimeline, .presentMediaEventsTimeline, .presentKnockingRequestsListScreen:
|
.presentPinnedEventsTimeline, .presentMediaEventsTimeline, .presentKnockingRequestsListScreen:
|
||||||
fatalError("Not handled in the space context")
|
fatalError("Not handled in the space context")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
private func presentInviteUsersScreen(roomProxy: JoinedRoomProxyProtocol, spaceRoomListProxy: SpaceRoomListProxyProtocol?) {
|
private func presentInviteUsersScreen(roomProxy: JoinedRoomProxyProtocol, spaceRoomListProxy: SpaceRoomListProxyProtocol?) {
|
||||||
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
|
let inviteParameters = InviteUsersScreenCoordinatorParameters(userSession: flowParameters.userSession,
|
||||||
roomProxy: roomProxy,
|
roomType: .existingRoom(roomProxy: roomProxy),
|
||||||
isSkippable: true,
|
isSkippable: true,
|
||||||
userDiscoveryService: userDiscoveryService,
|
userDiscoveryService: userDiscoveryService,
|
||||||
userIndicatorController: flowParameters.userIndicatorController,
|
userIndicatorController: flowParameters.userIndicatorController,
|
||||||
@@ -285,6 +285,8 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
} else {
|
} else {
|
||||||
actionsSubject.send(.finished(.room(id: roomProxy.id)))
|
actionsSubject.send(.finished(.room(id: roomProxy.id)))
|
||||||
}
|
}
|
||||||
|
case .openRoom:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -3367,8 +3367,8 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
|||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCalled: Bool {
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCalled: Bool {
|
||||||
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount > 0
|
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount > 0
|
||||||
}
|
}
|
||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments: (name: String, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?)?
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments: (name: String?, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?)?
|
||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedInvocations: [(name: String, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?)] = []
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedInvocations: [(name: String?, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?)] = []
|
||||||
|
|
||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue: Result<String, ClientProxyError>!
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue: Result<String, ClientProxyError>!
|
||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue: Result<String, ClientProxyError>! {
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue: Result<String, ClientProxyError>! {
|
||||||
@@ -3394,9 +3394,9 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartClosure: ((String, String?, CreateRoomAccessType, Bool, [String], URL?, String?) async -> Result<String, ClientProxyError>)?
|
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartClosure: ((String?, String?, CreateRoomAccessType, Bool, [String], URL?, String?) async -> Result<String, ClientProxyError>)?
|
||||||
|
|
||||||
func createRoom(name: String, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?) async -> Result<String, ClientProxyError> {
|
func createRoom(name: String?, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?) async -> Result<String, ClientProxyError> {
|
||||||
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount += 1
|
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount += 1
|
||||||
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments = (name: name, topic: topic, accessType: accessType, isSpace: isSpace, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart)
|
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments = (name: name, topic: topic, accessType: accessType, isSpace: isSpace, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct InviteUsersScreenCoordinatorParameters {
|
struct InviteUsersScreenCoordinatorParameters {
|
||||||
let userSession: UserSessionProtocol
|
let userSession: UserSessionProtocol
|
||||||
let roomProxy: JoinedRoomProxyProtocol
|
let roomType: InviteUsersScreenRoomType
|
||||||
let isSkippable: Bool
|
let isSkippable: Bool
|
||||||
let userDiscoveryService: UserDiscoveryServiceProtocol
|
let userDiscoveryService: UserDiscoveryServiceProtocol
|
||||||
let userIndicatorController: UserIndicatorControllerProtocol
|
let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
@@ -20,6 +20,7 @@ struct InviteUsersScreenCoordinatorParameters {
|
|||||||
|
|
||||||
enum InviteUsersScreenCoordinatorAction {
|
enum InviteUsersScreenCoordinatorAction {
|
||||||
case dismiss
|
case dismiss
|
||||||
|
case openRoom(roomID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class InviteUsersScreenCoordinator: CoordinatorProtocol {
|
final class InviteUsersScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -33,7 +34,7 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol {
|
|||||||
|
|
||||||
init(parameters: InviteUsersScreenCoordinatorParameters) {
|
init(parameters: InviteUsersScreenCoordinatorParameters) {
|
||||||
viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession,
|
viewModel = InviteUsersScreenViewModel(userSession: parameters.userSession,
|
||||||
roomProxy: parameters.roomProxy,
|
roomType: parameters.roomType,
|
||||||
isSkippable: parameters.isSkippable,
|
isSkippable: parameters.isSkippable,
|
||||||
userDiscoveryService: parameters.userDiscoveryService,
|
userDiscoveryService: parameters.userDiscoveryService,
|
||||||
userIndicatorController: parameters.userIndicatorController,
|
userIndicatorController: parameters.userIndicatorController,
|
||||||
@@ -46,6 +47,8 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
|
case .openRoom(let roomID):
|
||||||
|
actionsSubject.send(.openRoom(roomID: roomID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ enum InviteUsersScreenErrorType: Error {
|
|||||||
|
|
||||||
enum InviteUsersScreenViewModelAction {
|
enum InviteUsersScreenViewModelAction {
|
||||||
case dismiss
|
case dismiss
|
||||||
|
case openRoom(roomID: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InviteUsersScreenRoomType {
|
enum InviteUsersScreenRoomType {
|
||||||
case draft
|
case draft(mandatoryInvitees: [UserProfileProxy])
|
||||||
case room(roomProxy: JoinedRoomProxyProtocol)
|
case existingRoom(roomProxy: JoinedRoomProxyProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InviteUsersScreenViewState: BindableState {
|
struct InviteUsersScreenViewState: BindableState {
|
||||||
@@ -29,6 +30,7 @@ struct InviteUsersScreenViewState: BindableState {
|
|||||||
var usersSection: UserDiscoverySection = .init(type: .suggestions, users: [])
|
var usersSection: UserDiscoverySection = .init(type: .suggestions, users: [])
|
||||||
|
|
||||||
var selectedUsers: [UserProfileProxy] = []
|
var selectedUsers: [UserProfileProxy] = []
|
||||||
|
var mandatoryInvitees: [UserProfileProxy] = []
|
||||||
var membershipState: [String: MembershipState] = .init()
|
var membershipState: [String: MembershipState] = .init()
|
||||||
var usersToConfirm: [UserProfileProxy] = []
|
var usersToConfirm: [UserProfileProxy] = []
|
||||||
|
|
||||||
@@ -38,32 +40,29 @@ struct InviteUsersScreenViewState: BindableState {
|
|||||||
!isSearching && usersSection.type == .searchResult && usersSection.users.isEmpty
|
!isSearching && usersSection.type == .searchResult && usersSection.users.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasInvitableSelectedUsers: Bool {
|
||||||
|
selectedUsers.contains { !isInviteeMandatory($0) }
|
||||||
|
}
|
||||||
|
|
||||||
func isUserSelected(_ user: UserProfileProxy) -> Bool {
|
func isUserSelected(_ user: UserProfileProxy) -> Bool {
|
||||||
isUserDisabled(user) || selectedUsers.contains { $0.userID == user.userID }
|
isUserDisabled(user) || selectedUsers.contains { $0.userID == user.userID }
|
||||||
}
|
}
|
||||||
|
|
||||||
func isUserDisabled(_ user: UserProfileProxy) -> Bool {
|
func isUserDisabled(_ user: UserProfileProxy) -> Bool {
|
||||||
|
if isInviteeMandatory(user) { return true }
|
||||||
let membershipState = membershipState(user)
|
let membershipState = membershipState(user)
|
||||||
return membershipState == .invite || membershipState == .join
|
return membershipState == .invite || membershipState == .join
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInviteeMandatory(_ user: UserProfileProxy) -> Bool {
|
||||||
|
mandatoryInvitees.contains { $0.userID == user.userID }
|
||||||
|
}
|
||||||
|
|
||||||
func membershipState(_ user: UserProfileProxy) -> MembershipState? {
|
func membershipState(_ user: UserProfileProxy) -> MembershipState? {
|
||||||
membershipState[user.userID]
|
membershipState[user.userID]
|
||||||
}
|
}
|
||||||
|
|
||||||
let isSkippable: Bool
|
let isSkippable: Bool
|
||||||
|
|
||||||
var actionText: String {
|
|
||||||
if isSkippable, selectedUsers.isEmpty {
|
|
||||||
L10n.actionSkip
|
|
||||||
} else {
|
|
||||||
L10n.actionInvite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isActionDisabled: Bool {
|
|
||||||
isSkippable ? false : selectedUsers.isEmpty
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InviteUsersScreenViewStateBindings {
|
struct InviteUsersScreenViewStateBindings {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ typealias InviteUsersScreenViewModelType = StateStoreViewModel<InviteUsersScreen
|
|||||||
|
|
||||||
class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol {
|
class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScreenViewModelProtocol {
|
||||||
private let clientProxy: ClientProxyProtocol
|
private let clientProxy: ClientProxyProtocol
|
||||||
private let roomProxy: JoinedRoomProxyProtocol
|
private let roomType: InviteUsersScreenRoomType
|
||||||
private let userDiscoveryService: UserDiscoveryServiceProtocol
|
private let userDiscoveryService: UserDiscoveryServiceProtocol
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
@@ -27,18 +27,21 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol,
|
init(userSession: UserSessionProtocol,
|
||||||
roomProxy: JoinedRoomProxyProtocol,
|
roomType: InviteUsersScreenRoomType,
|
||||||
isSkippable: Bool,
|
isSkippable: Bool,
|
||||||
userDiscoveryService: UserDiscoveryServiceProtocol,
|
userDiscoveryService: UserDiscoveryServiceProtocol,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol,
|
userIndicatorController: UserIndicatorControllerProtocol,
|
||||||
appSettings: AppSettings) {
|
appSettings: AppSettings) {
|
||||||
clientProxy = userSession.clientProxy
|
clientProxy = userSession.clientProxy
|
||||||
self.roomProxy = roomProxy
|
self.roomType = roomType
|
||||||
self.userDiscoveryService = userDiscoveryService
|
self.userDiscoveryService = userDiscoveryService
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
|
||||||
super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: [],
|
let mandatoryInvitees: [UserProfileProxy] = if case .draft(let invitees) = roomType { invitees } else { [] }
|
||||||
|
|
||||||
|
super.init(initialViewState: InviteUsersScreenViewState(selectedUsers: mandatoryInvitees,
|
||||||
|
mandatoryInvitees: mandatoryInvitees,
|
||||||
isSkippable: isSkippable),
|
isSkippable: isSkippable),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
@@ -61,13 +64,18 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
case .cancel:
|
case .cancel:
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
case .proceed:
|
case .proceed:
|
||||||
guard roomProxy.details.historySharingState != RoomHistorySharingState.hidden,
|
switch roomType {
|
||||||
!state.usersToConfirm.isEmpty,
|
case .draft:
|
||||||
!state.isSkippable else {
|
createDraftRoom(mandatoryUserIDs: state.selectedUsers.map(\.userID))
|
||||||
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
|
case .existingRoom(let roomProxy):
|
||||||
return
|
guard roomProxy.details.historySharingState != RoomHistorySharingState.hidden,
|
||||||
|
!state.usersToConfirm.isEmpty,
|
||||||
|
!state.isSkippable else {
|
||||||
|
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.bindings.presentConfirmationDialog = true
|
||||||
}
|
}
|
||||||
state.bindings.presentConfirmationDialog = true
|
|
||||||
case .removeUnknownUsers:
|
case .removeUnknownUsers:
|
||||||
state.bindings.presentConfirmationDialog = false
|
state.bindings.presentConfirmationDialog = false
|
||||||
state.selectedUsers.removeAll { user in
|
state.selectedUsers.removeAll { user in
|
||||||
@@ -77,7 +85,9 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
case .confirmUnknownUsers:
|
case .confirmUnknownUsers:
|
||||||
state.bindings.presentConfirmationDialog = false
|
state.bindings.presentConfirmationDialog = false
|
||||||
state.usersToConfirm = []
|
state.usersToConfirm = []
|
||||||
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
|
if case .existingRoom(let roomProxy) = roomType {
|
||||||
|
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
|
||||||
|
}
|
||||||
case .toggleUser(let user):
|
case .toggleUser(let user):
|
||||||
toggleUser(user)
|
toggleUser(user)
|
||||||
}
|
}
|
||||||
@@ -86,6 +96,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func toggleUser(_ user: UserProfileProxy) {
|
private func toggleUser(_ user: UserProfileProxy) {
|
||||||
|
guard !state.isInviteeMandatory(user) else { return }
|
||||||
|
|
||||||
if state.selectedUsers.contains(user) {
|
if state.selectedUsers.contains(user) {
|
||||||
state.selectedUsers.removeAll { $0.userID == user.userID }
|
state.selectedUsers.removeAll { $0.userID == user.userID }
|
||||||
} else {
|
} else {
|
||||||
@@ -105,6 +117,29 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func createDraftRoom(mandatoryUserIDs: [String]) {
|
||||||
|
showLoadingIndicator(title: L10n.commonCreatingRoom)
|
||||||
|
|
||||||
|
Task {
|
||||||
|
defer { hideLoadingIndicator() }
|
||||||
|
|
||||||
|
switch await clientProxy.createRoom(name: nil,
|
||||||
|
topic: nil,
|
||||||
|
accessType: .private,
|
||||||
|
isSpace: false,
|
||||||
|
userIDs: mandatoryUserIDs,
|
||||||
|
avatarURL: nil,
|
||||||
|
aliasLocalPart: nil) {
|
||||||
|
case .success(let roomID):
|
||||||
|
actionsSubject.send(.openRoom(roomID: roomID))
|
||||||
|
case .failure:
|
||||||
|
state.bindings.alertInfo = .init(id: .unknown,
|
||||||
|
title: L10n.commonError,
|
||||||
|
message: L10n.screenStartChatErrorStartingChat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) {
|
private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) {
|
||||||
showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose)
|
showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose)
|
||||||
|
|
||||||
@@ -168,6 +203,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func fetchMembersIfNeeded() {
|
private func fetchMembersIfNeeded() {
|
||||||
|
guard case .existingRoom(let roomProxy) = roomType else { return }
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
showLoadingIndicator()
|
showLoadingIndicator()
|
||||||
await roomProxy.updateMembers()
|
await roomProxy.updateMembers()
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ struct InviteUsersConfirmationSheetView_Previews: PreviewProvider, TestablePrevi
|
|||||||
|
|
||||||
static func makeViewModel() -> InviteUsersScreenViewModel {
|
static func makeViewModel() -> InviteUsersScreenViewModel {
|
||||||
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init()))),
|
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: ClientProxyMock(.init()))),
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: [])),
|
roomType: .existingRoom(roomProxy: JoinedRoomProxyMock(.init(members: []))),
|
||||||
isSkippable: true,
|
isSkippable: true,
|
||||||
userDiscoveryService: UserDiscoveryServiceMock(),
|
userDiscoveryService: UserDiscoveryServiceMock(),
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ struct InviteUsersScreen: View {
|
|||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
|
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
|
||||||
InviteUsersScreenSelectedItem(user: user, mediaProvider: context.mediaProvider) {
|
InviteUsersScreenSelectedItem(user: user,
|
||||||
|
mediaProvider: context.mediaProvider,
|
||||||
|
isLocked: context.viewState.isInviteeMandatory(user)) {
|
||||||
deselect(user)
|
deselect(user)
|
||||||
}
|
}
|
||||||
.frame(width: selectedUserCellWidth)
|
.frame(width: selectedUserCellWidth)
|
||||||
@@ -133,11 +135,18 @@ struct InviteUsersScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
Button(context.viewState.actionText) {
|
if context.viewState.isSkippable, context.viewState.selectedUsers.isEmpty {
|
||||||
context.send(viewAction: .proceed)
|
Button(L10n.actionSkip) {
|
||||||
|
context.send(viewAction: .proceed)
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.inviteUsersScreen.proceed)
|
||||||
|
} else {
|
||||||
|
ToolbarButton(role: .confirm(title: L10n.actionInvite)) {
|
||||||
|
context.send(viewAction: .proceed)
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.inviteUsersScreen.proceed)
|
||||||
|
.disabled(!context.viewState.hasInvitableSelectedUsers)
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier(A11yIdentifiers.inviteUsersScreen.proceed)
|
|
||||||
.disabled(context.viewState.isActionDisabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +162,7 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
static let searchingViewModel = makeViewModel(searchQuery: "Alice")
|
static let searchingViewModel = makeViewModel(searchQuery: "Alice")
|
||||||
static let selectedViewModel = makeViewModel(hasSelection: true)
|
static let selectedViewModel = makeViewModel(hasSelection: true)
|
||||||
static let confirmSelectedViewModel = makeViewModel(shouldConfirm: true)
|
static let confirmSelectedViewModel = makeViewModel(shouldConfirm: true)
|
||||||
|
static let draftViewModel = makeViewModel(roomType: .draft(mandatoryInvitees: [.mockAlice]), isSkippable: false)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ElementNavigationStack {
|
ElementNavigationStack {
|
||||||
@@ -179,9 +189,19 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
InviteUsersScreen(context: confirmSelectedViewModel.context)
|
InviteUsersScreen(context: confirmSelectedViewModel.context)
|
||||||
}
|
}
|
||||||
.previewDisplayName("Confirm Selected")
|
.previewDisplayName("Confirm Selected")
|
||||||
|
|
||||||
|
ElementNavigationStack {
|
||||||
|
InviteUsersScreen(context: draftViewModel.context)
|
||||||
|
}
|
||||||
|
.previewDisplayName("Draft (locked invitee)")
|
||||||
|
.snapshotPreferences(expect: draftViewModel.context.$viewState.map { !$0.mandatoryInvitees.isEmpty })
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeViewModel(searchQuery: String? = nil, hasSelection: Bool = false, shouldConfirm: Bool = false) -> InviteUsersScreenViewModel {
|
static func makeViewModel(searchQuery: String? = nil,
|
||||||
|
hasSelection: Bool = false,
|
||||||
|
shouldConfirm: Bool = false,
|
||||||
|
roomType: InviteUsersScreenRoomType? = nil,
|
||||||
|
isSkippable: Bool = true) -> InviteUsersScreenViewModel {
|
||||||
let clientProxy = ClientProxyMock(.init())
|
let clientProxy = ClientProxyMock(.init())
|
||||||
clientProxy.recentConversationCounterpartsReturnValue = [.mockAlice, .mockBob, .mockCharlie, .mockDan, .mockVerbose]
|
clientProxy.recentConversationCounterpartsReturnValue = [.mockAlice, .mockBob, .mockCharlie, .mockDan, .mockVerbose]
|
||||||
|
|
||||||
@@ -189,8 +209,8 @@ struct InviteUsersScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
|
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
|
||||||
|
|
||||||
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
roomProxy: JoinedRoomProxyMock(.init(members: [])),
|
roomType: roomType ?? .existingRoom(roomProxy: JoinedRoomProxyMock(.init(members: []))),
|
||||||
isSkippable: true,
|
isSkippable: isSkippable,
|
||||||
userDiscoveryService: userDiscoveryService,
|
userDiscoveryService: userDiscoveryService,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
appSettings: AppSettings())
|
appSettings: AppSettings())
|
||||||
|
|||||||
+17
-7
@@ -12,6 +12,7 @@ import SwiftUI
|
|||||||
struct InviteUsersScreenSelectedItem: View {
|
struct InviteUsersScreenSelectedItem: View {
|
||||||
let user: UserProfileProxy
|
let user: UserProfileProxy
|
||||||
let mediaProvider: MediaProviderProtocol?
|
let mediaProvider: MediaProviderProtocol?
|
||||||
|
var isLocked = false
|
||||||
let dismissAction: () -> Void
|
let dismissAction: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -25,18 +26,27 @@ struct InviteUsersScreenSelectedItem: View {
|
|||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
.accessibilityAction(named: L10n.actionRemove, dismissAction)
|
.accessibilityActions {
|
||||||
|
if !isLocked {
|
||||||
|
Button(L10n.actionRemove, action: dismissAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
var avatar: some View {
|
var avatar: some View {
|
||||||
LoadableAvatarImage(url: user.avatarURL,
|
let avatarImage = LoadableAvatarImage(url: user.avatarURL,
|
||||||
name: user.displayName,
|
name: user.displayName,
|
||||||
contentID: user.userID,
|
contentID: user.userID,
|
||||||
avatarSize: .user(on: .inviteUsers),
|
avatarSize: .user(on: .inviteUsers),
|
||||||
mediaProvider: mediaProvider)
|
mediaProvider: mediaProvider)
|
||||||
.overlayRemoveItemButton(action: dismissAction)
|
if isLocked {
|
||||||
|
avatarImage
|
||||||
|
} else {
|
||||||
|
avatarImage.overlayRemoveItemButton(action: dismissAction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var closeButtonLabel: some View {
|
var closeButtonLabel: some View {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ enum RoomDetailsScreenCoordinatorAction {
|
|||||||
case presentRoomDetailsEditScreen
|
case presentRoomDetailsEditScreen
|
||||||
case presentNotificationSettingsScreen
|
case presentNotificationSettingsScreen
|
||||||
case presentInviteUsersScreen
|
case presentInviteUsersScreen
|
||||||
|
case presentInviteToNewRoom(invitee: UserProfileProxy)
|
||||||
case presentPollsHistory
|
case presentPollsHistory
|
||||||
case presentRolesAndPermissionsScreen
|
case presentRolesAndPermissionsScreen
|
||||||
case presentCall(isVoiceCall: Bool)
|
case presentCall(isVoiceCall: Bool)
|
||||||
@@ -71,6 +72,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.presentRoomMembersList)
|
actionsSubject.send(.presentRoomMembersList)
|
||||||
case .requestInvitePeoplePresentation:
|
case .requestInvitePeoplePresentation:
|
||||||
actionsSubject.send(.presentInviteUsersScreen)
|
actionsSubject.send(.presentInviteUsersScreen)
|
||||||
|
case .requestInviteToNewRoomPresentation(let invitee):
|
||||||
|
actionsSubject.send(.presentInviteToNewRoom(invitee: invitee))
|
||||||
case .leftRoom:
|
case .leftRoom:
|
||||||
actionsSubject.send(.leftRoom)
|
actionsSubject.send(.leftRoom)
|
||||||
case .requestEditDetailsPresentation:
|
case .requestEditDetailsPresentation:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ enum RoomDetailsScreenViewModelAction: Equatable {
|
|||||||
case requestMemberDetailsPresentation
|
case requestMemberDetailsPresentation
|
||||||
case requestRecipientDetailsPresentation(userID: String)
|
case requestRecipientDetailsPresentation(userID: String)
|
||||||
case requestInvitePeoplePresentation
|
case requestInvitePeoplePresentation
|
||||||
|
case requestInviteToNewRoomPresentation(selectedInvitee: UserProfileProxy)
|
||||||
case leftRoom
|
case leftRoom
|
||||||
case requestEditDetailsPresentation
|
case requestEditDetailsPresentation
|
||||||
case requestPollsHistoryPresentation
|
case requestPollsHistoryPresentation
|
||||||
@@ -98,6 +99,7 @@ struct RoomDetailsScreenViewState: BindableState {
|
|||||||
}
|
}
|
||||||
shortcuts.append(.videoCall)
|
shortcuts.append(.videoCall)
|
||||||
}
|
}
|
||||||
|
// The invite flow is different for DMs
|
||||||
if dmRecipientInfo == nil, canInviteUsers {
|
if dmRecipientInfo == nil, canInviteUsers {
|
||||||
shortcuts.append(.invite)
|
shortcuts.append(.invite)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,11 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
|||||||
case .processTapPeople:
|
case .processTapPeople:
|
||||||
actionsSubject.send(.requestMemberDetailsPresentation)
|
actionsSubject.send(.requestMemberDetailsPresentation)
|
||||||
case .processTapInvite:
|
case .processTapInvite:
|
||||||
actionsSubject.send(.requestInvitePeoplePresentation)
|
if let dmRecipient = state.dmRecipientInfo {
|
||||||
|
actionsSubject.send(.requestInviteToNewRoomPresentation(selectedInvitee: .init(member: dmRecipient.member)))
|
||||||
|
} else {
|
||||||
|
actionsSubject.send(.requestInvitePeoplePresentation)
|
||||||
|
}
|
||||||
case .processTapLeave:
|
case .processTapLeave:
|
||||||
processTapToLeave()
|
processTapToLeave()
|
||||||
case .confirmLeave:
|
case .confirmLeave:
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ struct RoomDetailsScreen: View {
|
|||||||
|
|
||||||
aboutSection
|
aboutSection
|
||||||
|
|
||||||
|
// The invitation flow is different for DMs
|
||||||
|
if context.viewState.dmRecipientInfo != nil {
|
||||||
|
inviteToNewRoomSection
|
||||||
|
}
|
||||||
|
|
||||||
configurationSection
|
configurationSection
|
||||||
|
|
||||||
if context.viewState.dmRecipientInfo == nil {
|
if context.viewState.dmRecipientInfo == nil {
|
||||||
@@ -165,6 +170,13 @@ struct RoomDetailsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var inviteToNewRoomSection: some View {
|
||||||
|
ListRow(label: .default(title: L10n.actionInvite, icon: \.userAdd),
|
||||||
|
kind: .navigationLink {
|
||||||
|
context.send(viewAction: .processTapInvite)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private var configurationSection: some View {
|
private var configurationSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ListRow(label: .default(title: L10n.screenRoomDetailsNotificationTitle, icon: \.notifications),
|
ListRow(label: .default(title: L10n.screenRoomDetailsNotificationTitle, icon: \.notifications),
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRoom(name: String,
|
func createRoom(name: String?,
|
||||||
topic: String?,
|
topic: String?,
|
||||||
accessType: CreateRoomAccessType,
|
accessType: CreateRoomAccessType,
|
||||||
isSpace: Bool,
|
isSpace: Bool,
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ protocol ClientProxyProtocol: AnyObject {
|
|||||||
|
|
||||||
func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result<String, ClientProxyError>
|
func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result<String, ClientProxyError>
|
||||||
|
|
||||||
func createRoom(name: String,
|
func createRoom(name: String?,
|
||||||
topic: String?,
|
topic: String?,
|
||||||
accessType: CreateRoomAccessType,
|
accessType: CreateRoomAccessType,
|
||||||
isSpace: Bool,
|
isSpace: Bool,
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f9413c3be57bf3882bff847d80d76616413d2c16a63ba316a760548fff2b6386
|
||||||
|
size 151997
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5e70eaef9bc01eccd462be75ac69e1d556784596a0287da9a8f3f7e10e4e58b0
|
||||||
|
size 156413
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5338f0606064bc4c3c098c2458f1923db59f206c059cfa11bdbc387d42b3af3a
|
||||||
|
size 99061
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4c9456d7cf947a55063eae44ea955c34fd3af41663ffffbe1f72ba47f791cab0
|
||||||
|
size 102079
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:f2efff4b459ba93bc6c8290a3ff46a26a684b2c6abe2812c8a4667a86b060711
|
oid sha256:5a87b4c36df926a3cde0d0a15022e5b5c0c0e545c97974f916b506b4862fc5ab
|
||||||
size 151210
|
size 149551
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1a6fa5cea94e92da8768cb87d2d157b9552ec3624fbc4f1241c0c212e93f2d98
|
oid sha256:d450d451e9ca7779f4f186f2eabf8a23cef9b2dd861e581ed1c61d0dbbb06499
|
||||||
size 155938
|
size 153569
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:34bbaf989ac4635edb87a1e2855c45af93eba45aae198a56173186014b79ddb3
|
oid sha256:c793431f951a5ad023ab9b358658a5d75244e292cf56c0fc4a808b84df654e7a
|
||||||
size 98426
|
size 97084
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9cebab4fae25f0ea54e0583563719429d06d52949d7847458a98dec6c0ce746d
|
oid sha256:68c222b7a50badb09b9f2cbdcc6010edac0a7a2727ed9f2f98713e2874ffa214
|
||||||
size 101873
|
size 100304
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c993321bdec5ce94cdd241664bce503fb1c65dc5341355c593f4eeae7f6c222f
|
oid sha256:6bcc46e5933065a1b02dff5c2bf8abc09c626cf6adb6b91acca6c0d27bf8d967
|
||||||
size 244787
|
size 236706
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3a0c1fd96fe4299e6075b6ebadc330a5943fe5393e3589a93288dc5d87f73ac8
|
oid sha256:f892f94486579fcebfdb3bd5723c233130b10fdabbadac17f687fcedab365085
|
||||||
size 253574
|
size 244369
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:68b3531545e4cecc6b6be0bb009e448cae7bc7ed2cee4686b48a20a780c82bb3
|
oid sha256:ebb51488b9ce71449d0f8ee04bba5f260f924c61186b41c0d6fe8903b06de6b9
|
||||||
size 181065
|
size 174363
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:040b3df78b47409a450c6ffec62fb3ff72cbfe74e15f3e85dfbc046a5f2695a7
|
oid sha256:f87dbe6bdd53d79ef618cb86707360df944aeb0ad5916f64a9826eaae400e2b0
|
||||||
size 185625
|
size 185369
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c993321bdec5ce94cdd241664bce503fb1c65dc5341355c593f4eeae7f6c222f
|
oid sha256:6bcc46e5933065a1b02dff5c2bf8abc09c626cf6adb6b91acca6c0d27bf8d967
|
||||||
size 244787
|
size 236706
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3a0c1fd96fe4299e6075b6ebadc330a5943fe5393e3589a93288dc5d87f73ac8
|
oid sha256:f892f94486579fcebfdb3bd5723c233130b10fdabbadac17f687fcedab365085
|
||||||
size 253574
|
size 244369
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:68b3531545e4cecc6b6be0bb009e448cae7bc7ed2cee4686b48a20a780c82bb3
|
oid sha256:ebb51488b9ce71449d0f8ee04bba5f260f924c61186b41c0d6fe8903b06de6b9
|
||||||
size 181065
|
size 174363
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:040b3df78b47409a450c6ffec62fb3ff72cbfe74e15f3e85dfbc046a5f2695a7
|
oid sha256:f87dbe6bdd53d79ef618cb86707360df944aeb0ad5916f64a9826eaae400e2b0
|
||||||
size 185625
|
size 185369
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:c993321bdec5ce94cdd241664bce503fb1c65dc5341355c593f4eeae7f6c222f
|
oid sha256:6bcc46e5933065a1b02dff5c2bf8abc09c626cf6adb6b91acca6c0d27bf8d967
|
||||||
size 244787
|
size 236706
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3a0c1fd96fe4299e6075b6ebadc330a5943fe5393e3589a93288dc5d87f73ac8
|
oid sha256:f892f94486579fcebfdb3bd5723c233130b10fdabbadac17f687fcedab365085
|
||||||
size 253574
|
size 244369
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:68b3531545e4cecc6b6be0bb009e448cae7bc7ed2cee4686b48a20a780c82bb3
|
oid sha256:ebb51488b9ce71449d0f8ee04bba5f260f924c61186b41c0d6fe8903b06de6b9
|
||||||
size 181065
|
size 174363
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:040b3df78b47409a450c6ffec62fb3ff72cbfe74e15f3e85dfbc046a5f2695a7
|
oid sha256:f87dbe6bdd53d79ef618cb86707360df944aeb0ad5916f64a9826eaae400e2b0
|
||||||
size 185625
|
size 185369
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ final class InviteUsersScreenViewModelTests {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return true
|
return true
|
||||||
|
case .openRoom:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +124,8 @@ final class InviteUsersScreenViewModelTests {
|
|||||||
switch action {
|
switch action {
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
return true
|
return true
|
||||||
|
case .openRoom:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +163,74 @@ final class InviteUsersScreenViewModelTests {
|
|||||||
try await deferredState.fulfill()
|
try await deferredState.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Draft (new room)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func createsNewRoomInDraftMode() async throws {
|
||||||
|
userDiscoveryService = UserDiscoveryServiceMock()
|
||||||
|
userDiscoveryService.searchProfilesWithReturnValue = .success([])
|
||||||
|
|
||||||
|
clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
|
||||||
|
let newRoomID = "!newroom:example.com"
|
||||||
|
clientProxy.createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue = .success(newRoomID)
|
||||||
|
|
||||||
|
viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
|
roomType: .draft(mandatoryInvitees: [.mockAlice]),
|
||||||
|
isSkippable: false,
|
||||||
|
userDiscoveryService: userDiscoveryService,
|
||||||
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
|
appSettings: AppSettings())
|
||||||
|
|
||||||
|
// The locked invitee starts pre-selected and locked.
|
||||||
|
#expect(context.viewState.selectedUsers.map(\.userID) == [UserProfileProxy.mockAlice.userID])
|
||||||
|
#expect(context.viewState.isInviteeMandatory(.mockAlice))
|
||||||
|
// The proceed button is disabled while the only selected user is the locked invitee.
|
||||||
|
#expect(!context.viewState.hasInvitableSelectedUsers)
|
||||||
|
|
||||||
|
// Selecting a non-locked user enables the proceed button.
|
||||||
|
var deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||||
|
state.isUserSelected(.mockBob)
|
||||||
|
}
|
||||||
|
context.send(viewAction: .toggleUser(.mockBob))
|
||||||
|
try await deferredState.fulfill()
|
||||||
|
#expect(context.viewState.hasInvitableSelectedUsers)
|
||||||
|
|
||||||
|
// Deselecting the non-locked user disables the proceed button again.
|
||||||
|
deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||||
|
!state.isUserSelected(.mockBob)
|
||||||
|
}
|
||||||
|
context.send(viewAction: .toggleUser(.mockBob))
|
||||||
|
try await deferredState.fulfill()
|
||||||
|
#expect(!context.viewState.hasInvitableSelectedUsers)
|
||||||
|
|
||||||
|
// Re-select the non-locked user before proceeding.
|
||||||
|
deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||||
|
state.isUserSelected(.mockBob)
|
||||||
|
}
|
||||||
|
context.send(viewAction: .toggleUser(.mockBob))
|
||||||
|
try await deferredState.fulfill()
|
||||||
|
|
||||||
|
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||||
|
if case .openRoom(let roomID) = action, roomID == newRoomID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
context.send(viewAction: .proceed)
|
||||||
|
|
||||||
|
try await deferredAction.fulfill()
|
||||||
|
|
||||||
|
let args = try #require(clientProxy.createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments)
|
||||||
|
#expect(args.name == nil)
|
||||||
|
#expect(args.topic == nil)
|
||||||
|
#expect(args.accessType == .private)
|
||||||
|
#expect(args.isSpace == false)
|
||||||
|
#expect(args.userIDs == [UserProfileProxy.mockAlice.userID, UserProfileProxy.mockBob.userID])
|
||||||
|
#expect(args.avatarURL == nil)
|
||||||
|
#expect(args.aliasLocalPart == nil)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func setupViewModel(roomProxy: JoinedRoomProxyProtocol, isSkippable: Bool) {
|
private func setupViewModel(roomProxy: JoinedRoomProxyProtocol, isSkippable: Bool) {
|
||||||
@@ -168,7 +240,7 @@ final class InviteUsersScreenViewModelTests {
|
|||||||
clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
|
clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
|
||||||
|
|
||||||
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
let viewModel = InviteUsersScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
|
||||||
roomProxy: roomProxy,
|
roomType: .existingRoom(roomProxy: roomProxy),
|
||||||
isSkippable: isSkippable,
|
isSkippable: isSkippable,
|
||||||
userDiscoveryService: userDiscoveryService,
|
userDiscoveryService: userDiscoveryService,
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
|
|||||||
Reference in New Issue
Block a user