Create room redesign and refactor (#4942)

This commit is contained in:
Mauro
2026-01-12 18:49:31 +01:00
committed by GitHub
parent f42c063e50
commit 4cfedefcca
77 changed files with 463 additions and 348 deletions
@@ -532,18 +532,21 @@
"screen_create_poll_remove_accessibility_label" = "Remove %1$@";
"screen_create_poll_settings_section_title" = "Settings";
"screen_create_room_error_creating_space" = "The space could not be created because of an unknown error. Try again later.";
"screen_create_room_name_placeholder" = "Add name…";
"screen_create_room_new_room_title" = "New room";
"screen_create_room_new_space_title" = "New space";
"screen_create_room_public_option_short_description" = "Anyone can join.";
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join.";
"screen_create_room_room_access_section_anyone_option_title" = "Anyone";
"screen_create_room_room_access_section_header" = "Room Access";
"screen_create_room_room_access_section_knocking_option_description" = "Anyone can ask to join but an administrator or a moderator must accept the request.";
"screen_create_room_room_access_section_knocking_option_title" = "Ask to join";
"screen_create_room_room_access_section_knocking_option_title" = "Allow ask to join";
"screen_create_room_room_access_section_private_option_description" = "Only people invited can join.";
"screen_create_room_room_access_section_private_option_title" = "Private";
"screen_create_room_room_access_section_public_option_description" = "Anyone can join.";
"screen_create_room_room_access_section_public_option_title" = "Public";
"screen_create_room_room_access_section_title" = "Who has access";
"screen_create_room_room_address_section_footer" = "In order for this room to be visible in the public room directory, you will need a room address.";
"screen_create_room_room_address_section_title" = "Room address";
"screen_create_room_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_create_room_room_address_section_title" = "Address";
"screen_create_room_room_visibility_section_title" = "Room visibility";
"screen_create_room_title_new_space" = "New space";
"screen_create_room_topic_placeholder" = "Add description…";
"screen_decline_and_block_block_user_option_description" = "You will not see any messages or room invites from this user";
"screen_decline_and_block_report_user_option_description" = "Report this room to your account provider.";
"screen_edit_room_address_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
@@ -681,6 +684,7 @@
"screen_security_and_privacy_add_room_address_action" = "Add address";
"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorized spaces can join, but everyone else must request access.";
"screen_security_and_privacy_ask_to_join_option_description" = "Everyone must request access.";
"screen_security_and_privacy_ask_to_join_option_title" = "Ask to join";
"screen_security_and_privacy_ask_to_join_single_space_members_option_description" = "Anyone in %1$@ can join, but everyone else must request access.";
"screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption";
"screen_security_and_privacy_enable_encryption_alert_description" = "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.\nNo one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.\nWe do not recommend enabling encryption for rooms that anyone can find and join.";
@@ -688,6 +692,7 @@
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
"screen_security_and_privacy_encryption_toggle_title" = "Enable end-to-end encryption";
"screen_security_and_privacy_room_access_anyone_option_description" = "Anyone can join.";
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer" = "Choose which spaces’ members can join this room without an invitation. %1$@";
"screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join.";
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
@@ -1424,9 +1429,7 @@
"screen_room_details_profile_row_title" = "Profile";
"screen_room_details_security_and_privacy_title" = "Security & privacy";
"screen_roomlist_knock_event_sent_description" = "Request to join sent";
"screen_security_and_privacy_ask_to_join_option_title" = "Ask to join";
"screen_security_and_privacy_encryption_section_header" = "Encryption";
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_space_settings_leave_space" = "Leave space";
@@ -1447,8 +1450,6 @@
"screen_create_account_title" = "Create account";
"screen_create_poll_cancel_confirmation_content_ios" = "Your changes won’t be saved";
"screen_create_room_add_people_title" = "Invite people";
"screen_create_room_room_name_label" = "Room name";
"screen_create_room_title" = "Create a room";
"screen_deactivate_account_title" = "Deactivate account";
"screen_dm_details_block_alert_action" = "Block";
"screen_dm_details_block_alert_description" = "Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime.";
@@ -532,18 +532,21 @@
"screen_create_poll_remove_accessibility_label" = "Remove %1$@";
"screen_create_poll_settings_section_title" = "Settings";
"screen_create_room_error_creating_space" = "The space could not be created because of an unknown error. Try again later.";
"screen_create_room_name_placeholder" = "Add name…";
"screen_create_room_new_room_title" = "New room";
"screen_create_room_new_space_title" = "New space";
"screen_create_room_public_option_short_description" = "Anyone can join.";
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join.";
"screen_create_room_room_access_section_anyone_option_title" = "Anyone";
"screen_create_room_room_access_section_header" = "Room Access";
"screen_create_room_room_access_section_knocking_option_description" = "Anyone can ask to join but an administrator or a moderator must accept the request.";
"screen_create_room_room_access_section_knocking_option_title" = "Ask to join";
"screen_create_room_room_access_section_knocking_option_title" = "Allow ask to join";
"screen_create_room_room_access_section_private_option_description" = "Only people invited can join.";
"screen_create_room_room_access_section_private_option_title" = "Private";
"screen_create_room_room_access_section_public_option_description" = "Anyone can join.";
"screen_create_room_room_access_section_public_option_title" = "Public";
"screen_create_room_room_access_section_title" = "Who has access";
"screen_create_room_room_address_section_footer" = "In order for this room to be visible in the public room directory, you will need a room address.";
"screen_create_room_room_address_section_title" = "Room address";
"screen_create_room_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_create_room_room_address_section_title" = "Address";
"screen_create_room_room_visibility_section_title" = "Room visibility";
"screen_create_room_title_new_space" = "New space";
"screen_create_room_topic_placeholder" = "Add description…";
"screen_decline_and_block_block_user_option_description" = "You will not see any messages or room invites from this user";
"screen_decline_and_block_report_user_option_description" = "Report this room to your account provider.";
"screen_edit_room_address_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
@@ -681,6 +684,7 @@
"screen_security_and_privacy_add_room_address_action" = "Add address";
"screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description" = "Anyone in authorised spaces can join, but everyone else must request access.";
"screen_security_and_privacy_ask_to_join_option_description" = "Everyone must request access.";
"screen_security_and_privacy_ask_to_join_option_title" = "Ask to join";
"screen_security_and_privacy_ask_to_join_single_space_members_option_description" = "Anyone in %1$@ can join, but everyone else must request access.";
"screen_security_and_privacy_enable_encryption_alert_confirm_button_title" = "Yes, enable encryption";
"screen_security_and_privacy_enable_encryption_alert_description" = "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.\nNo one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.\nWe do not recommend enabling encryption for rooms that anyone can find and join.";
@@ -688,6 +692,7 @@
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
"screen_security_and_privacy_encryption_toggle_title" = "Enable end-to-end encryption";
"screen_security_and_privacy_room_access_anyone_option_description" = "Anyone can join.";
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer" = "Choose which spaces’ members can join this room without an invitation. %1$@";
"screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join.";
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
@@ -1424,9 +1429,7 @@
"screen_room_details_profile_row_title" = "Profile";
"screen_room_details_security_and_privacy_title" = "Security & privacy";
"screen_roomlist_knock_event_sent_description" = "Request to join sent";
"screen_security_and_privacy_ask_to_join_option_title" = "Ask to join";
"screen_security_and_privacy_encryption_section_header" = "Encryption";
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_space_settings_leave_space" = "Leave space";
@@ -1447,8 +1450,6 @@
"screen_create_account_title" = "Create account";
"screen_create_poll_cancel_confirmation_content_ios" = "Your changes won’t be saved";
"screen_create_room_add_people_title" = "Invite people";
"screen_create_room_room_name_label" = "Room name";
"screen_create_room_title" = "Create a room";
"screen_deactivate_account_title" = "Deactivate account";
"screen_dm_details_block_alert_action" = "Block";
"screen_dm_details_block_alert_description" = "Blocked users won't be able to send you messages and all their messages will be hidden. You can unblock them anytime.";
@@ -567,7 +567,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
private func startStartChatFlow(animated: Bool) {
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = StartChatFlowCoordinator(userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
let coordinator = StartChatFlowCoordinator(isSpace: false,
userDiscoveryService: UserDiscoveryService(clientProxy: userSession.clientProxy),
navigationStackCoordinator: navigationStackCoordinator,
flowParameters: flowParameters)
@@ -16,6 +16,7 @@ enum StartChatFlowCoordinatorAction {
}
class StartChatFlowCoordinator: FlowCoordinatorProtocol {
private let isSpace: Bool
private let userDiscoveryService: UserDiscoveryServiceProtocol
private let navigationStackCoordinator: NavigationStackCoordinator
@@ -63,9 +64,11 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
actionsSubject.eraseToAnyPublisher()
}
init(userDiscoveryService: UserDiscoveryServiceProtocol,
init(isSpace: Bool,
userDiscoveryService: UserDiscoveryServiceProtocol,
navigationStackCoordinator: NavigationStackCoordinator,
flowParameters: CommonFlowParameters) {
self.isSpace = isSpace
self.userDiscoveryService = userDiscoveryService
self.navigationStackCoordinator = navigationStackCoordinator
@@ -168,7 +171,8 @@ class StartChatFlowCoordinator: FlowCoordinatorProtocol {
}
private func presentCreateRoomScreen() {
let createParameters = CreateRoomScreenCoordinatorParameters(userSession: flowParameters.userSession,
let createParameters = CreateRoomScreenCoordinatorParameters(isSpace: isSpace,
userSession: flowParameters.userSession,
userIndicatorController: flowParameters.userIndicatorController,
appSettings: flowParameters.appSettings,
analytics: flowParameters.analytics)
+17 -15
View File
@@ -1571,6 +1571,10 @@ internal enum L10n {
internal static var screenCreateRoomErrorCreatingRoom: String { return L10n.tr("Localizable", "screen_create_room_error_creating_room") }
/// The space could not be created because of an unknown error. Try again later.
internal static var screenCreateRoomErrorCreatingSpace: String { return L10n.tr("Localizable", "screen_create_room_error_creating_space") }
/// Add name…
internal static var screenCreateRoomNamePlaceholder: String { return L10n.tr("Localizable", "screen_create_room_name_placeholder") }
/// New room
internal static var screenCreateRoomNewRoomTitle: String { return L10n.tr("Localizable", "screen_create_room_new_room_title") }
/// New space
internal static var screenCreateRoomNewSpaceTitle: String { return L10n.tr("Localizable", "screen_create_room_new_space_title") }
/// Only people invited can join.
@@ -1584,32 +1588,30 @@ internal enum L10n {
internal static var screenCreateRoomPublicOptionShortDescription: String { return L10n.tr("Localizable", "screen_create_room_public_option_short_description") }
/// Public
internal static var screenCreateRoomPublicOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_public_option_title") }
/// Anyone can join.
internal static var screenCreateRoomRoomAccessSectionAnyoneOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_anyone_option_description") }
/// Anyone
internal static var screenCreateRoomRoomAccessSectionAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_anyone_option_title") }
/// Room Access
internal static var screenCreateRoomRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_header") }
/// Anyone can ask to join but an administrator or a moderator must accept the request.
internal static var screenCreateRoomRoomAccessSectionKnockingOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_knocking_option_description") }
/// Ask to join
/// Allow ask to join
internal static var screenCreateRoomRoomAccessSectionKnockingOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_knocking_option_title") }
/// Only people invited can join.
internal static var screenCreateRoomRoomAccessSectionPrivateOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_private_option_description") }
/// Private
internal static var screenCreateRoomRoomAccessSectionPrivateOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_private_option_title") }
/// Anyone can join.
internal static var screenCreateRoomRoomAccessSectionPublicOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_public_option_description") }
/// Public
internal static var screenCreateRoomRoomAccessSectionPublicOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_public_option_title") }
/// Who has access
internal static var screenCreateRoomRoomAccessSectionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_title") }
/// In order for this room to be visible in the public room directory, you will need a room address.
/// You’ll need an address in order to make it visible in the public directory.
internal static var screenCreateRoomRoomAddressSectionFooter: String { return L10n.tr("Localizable", "screen_create_room_room_address_section_footer") }
/// Room address
/// Address
internal static var screenCreateRoomRoomAddressSectionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_address_section_title") }
/// Room name
internal static var screenCreateRoomRoomNameLabel: String { return L10n.tr("Localizable", "screen_create_room_room_name_label") }
/// Room visibility
internal static var screenCreateRoomRoomVisibilitySectionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_visibility_section_title") }
/// Create a room
internal static var screenCreateRoomTitle: String { return L10n.tr("Localizable", "screen_create_room_title") }
/// New space
internal static var screenCreateRoomTitleNewSpace: String { return L10n.tr("Localizable", "screen_create_room_title_new_space") }
/// Topic (optional)
internal static var screenCreateRoomTopicLabel: String { return L10n.tr("Localizable", "screen_create_room_topic_label") }
/// Add description…
internal static var screenCreateRoomTopicPlaceholder: String { return L10n.tr("Localizable", "screen_create_room_topic_placeholder") }
/// Please confirm that you want to deactivate your account. This action cannot be undone.
internal static var screenDeactivateAccountConfirmationDialogContent: String { return L10n.tr("Localizable", "screen_deactivate_account_confirmation_dialog_content") }
/// Delete all my messages
+1 -1
View File
@@ -73,7 +73,7 @@ extension ClientProxyMock {
canDeactivateAccount = false
directRoomForUserIDReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
createDirectRoomWithExpectedRoomNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
canJoinRoomWithReturnValue = true
joinRoomViaClosure = { roomID, _ in
configuration.overrides.joinedRoomIDs.insert(roomID)
@@ -2847,15 +2847,15 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
}
//MARK: - createRoom
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = 0
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartCallsCount: Int {
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = 0
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount: Int {
get {
if Thread.isMainThread {
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount
returnValue = createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount
}
return returnValue!
@@ -2863,29 +2863,29 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
}
set {
if Thread.isMainThread {
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = newValue
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = newValue
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingCallsCount = newValue
}
}
}
}
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartCalled: Bool {
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartCallsCount > 0
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCalled: Bool {
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount > 0
}
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReceivedArguments: (name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?)?
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReceivedInvocations: [(name: String, topic: String?, isRoomPrivate: Bool, isKnockingOnly: 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 createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue: Result<String, ClientProxyError>!
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue: Result<String, ClientProxyError>! {
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue: Result<String, ClientProxyError>!
var createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue: Result<String, ClientProxyError>! {
get {
if Thread.isMainThread {
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue
} else {
var returnValue: Result<String, ClientProxyError>? = nil
DispatchQueue.main.sync {
returnValue = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue
returnValue = createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue
}
return returnValue!
@@ -2893,26 +2893,26 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable {
}
set {
if Thread.isMainThread {
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue = newValue
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue = newValue
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartUnderlyingReturnValue = newValue
}
}
}
}
var createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartClosure: ((String, String?, Bool, 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?, isRoomPrivate: Bool, isKnockingOnly: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?) async -> Result<String, ClientProxyError> {
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartCallsCount += 1
createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReceivedArguments = (name: name, topic: topic, isRoomPrivate: isRoomPrivate, isKnockingOnly: isKnockingOnly, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart)
func createRoom(name: String, topic: String?, accessType: CreateRoomAccessType, isSpace: Bool, userIDs: [String], avatarURL: URL?, aliasLocalPart: String?) async -> Result<String, ClientProxyError> {
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartCallsCount += 1
createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedArguments = (name: name, topic: topic, accessType: accessType, isSpace: isSpace, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart)
DispatchQueue.main.async {
self.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReceivedInvocations.append((name: name, topic: topic, isRoomPrivate: isRoomPrivate, isKnockingOnly: isKnockingOnly, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart))
self.createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReceivedInvocations.append((name: name, topic: topic, accessType: accessType, isSpace: isSpace, userIDs: userIDs, avatarURL: avatarURL, aliasLocalPart: aliasLocalPart))
}
if let createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartClosure = createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartClosure {
return await createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartClosure(name, topic, isRoomPrivate, isKnockingOnly, userIDs, avatarURL, aliasLocalPart)
if let createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartClosure = createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartClosure {
return await createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartClosure(name, topic, accessType, isSpace, userIDs, avatarURL, aliasLocalPart)
} else {
return createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue
return createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue
}
}
//MARK: - joinRoom
@@ -10,6 +10,7 @@ import Combine
import SwiftUI
struct CreateRoomScreenCoordinatorParameters {
let isSpace: Bool
let userSession: UserSessionProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSettings: AppSettings
@@ -31,7 +32,8 @@ final class CreateRoomScreenCoordinator: CoordinatorProtocol {
}
init(parameters: CreateRoomScreenCoordinatorParameters) {
viewModel = CreateRoomScreenViewModel(userSession: parameters.userSession,
viewModel = CreateRoomScreenViewModel(isSpace: parameters.isSpace,
userSession: parameters.userSession,
analytics: parameters.analytics,
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSettings)
@@ -23,12 +23,13 @@ enum CreateRoomScreenViewModelAction {
}
struct CreateRoomScreenViewState: BindableState {
let isSpace: Bool
var roomName: String
let serverName: String
let isKnockingFeatureEnabled: Bool
var aliasLocalPart: String
var bindings: CreateRoomScreenViewStateBindings
var avatarURL: URL?
var avatarMediaInfo: MediaInfo?
var canCreateRoom: Bool {
!roomName.isEmpty && aliasErrors.isEmpty
}
@@ -43,12 +44,19 @@ struct CreateRoomScreenViewState: BindableState {
nil
}
}
var availableAccessTypes: [CreateRoomAccessType] {
var availableTypes = CreateRoomAccessType.allCases
if !isKnockingFeatureEnabled {
availableTypes.removeAll { $0 == .askToJoin }
}
return availableTypes
}
}
struct CreateRoomScreenViewStateBindings {
var roomTopic: String
var isRoomPrivate: Bool
var isKnockingOnly: Bool
var selectedAccessType: CreateRoomAccessType
var showAttachmentConfirmationDialog = false
/// Information describing the currently displayed alert.
@@ -13,17 +13,7 @@ import SwiftUI
typealias CreateRoomScreenViewModelType = StateStoreViewModel<CreateRoomScreenViewState, CreateRoomScreenViewAction>
class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreenViewModelProtocol {
struct Parameters {
var name = ""
var topic = ""
var isRoomPrivate = true
var isKnockingOnly = false
var avatarImageMedia: MediaInfo?
var aliasLocalPart: String?
}
private let userSession: UserSessionProtocol
private var parameters: Parameters
private let mediaUploadingPreprocessor: MediaUploadingPreprocessor
private let analytics: AnalyticsService
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -36,25 +26,24 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
actionsSubject.eraseToAnyPublisher()
}
init(userSession: UserSessionProtocol,
initialParameters: Parameters = .init(),
init(isSpace: Bool,
userSession: UserSessionProtocol,
analytics: AnalyticsService,
userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) {
self.userSession = userSession
parameters = initialParameters
mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings)
self.analytics = analytics
self.userIndicatorController = userIndicatorController
let bindings = CreateRoomScreenViewStateBindings(roomTopic: parameters.topic,
isRoomPrivate: parameters.isRoomPrivate,
isKnockingOnly: appSettings.knockingEnabled ? parameters.isKnockingOnly : false)
let bindings = CreateRoomScreenViewStateBindings(roomTopic: "",
selectedAccessType: .private)
super.init(initialViewState: CreateRoomScreenViewState(roomName: parameters.name,
super.init(initialViewState: CreateRoomScreenViewState(isSpace: isSpace,
roomName: "",
serverName: userSession.clientProxy.userIDServerName ?? "",
isKnockingFeatureEnabled: appSettings.knockingEnabled,
aliasLocalPart: parameters.aliasLocalPart ?? roomAliasNameFromRoomDisplayName(roomName: parameters.name),
aliasLocalPart: roomAliasNameFromRoomDisplayName(roomName: ""),
bindings: bindings),
mediaProvider: userSession.mediaProvider)
@@ -72,8 +61,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
case .displayMediaPicker:
actionsSubject.send(.displayMediaPicker)
case .removeImage:
parameters.avatarImageMedia = nil
state.avatarURL = nil
state.avatarMediaInfo = nil
case .updateAliasLocalPart(let aliasLocalPart):
state.aliasLocalPart = aliasLocalPart.lowercased()
// If this has been called this means that the user wants a custom address not necessarily reflecting the name
@@ -104,9 +92,8 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
let mediaInfo = try await mediaUploadingPreprocessor.processMedia(at: fileURL, maxUploadSize: maxUploadSize).get()
switch mediaInfo {
case .image(_, let thumbnailURL, _):
parameters.avatarImageMedia = mediaInfo
state.avatarURL = thumbnailURL
case .image:
state.avatarMediaInfo = mediaInfo
default:
break
}
@@ -123,34 +110,18 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
// Reset the state related to public rooms if the user choses the room to be empty
context.$viewState
.dropFirst()
.map(\.bindings.isRoomPrivate)
.map(\.bindings.selectedAccessType)
.removeDuplicates()
.filter { $0 }
.filter(\.isPrivate)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let self else { return }
state.bindings.isKnockingOnly = false
state.aliasErrors = []
state.aliasLocalPart = roomAliasNameFromRoomDisplayName(roomName: state.roomName)
syncNameAndAlias = true
}
.store(in: &cancellables)
context.$viewState
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
.removeDuplicates { old, new in
old.roomName == new.roomName &&
old.bindings.roomTopic == new.bindings.roomTopic &&
old.bindings.isRoomPrivate == new.bindings.isRoomPrivate &&
old.bindings.isKnockingOnly == new.bindings.isKnockingOnly &&
old.aliasLocalPart == new.aliasLocalPart
}
.sink { [weak self] state in
guard let self else { return }
updateParameters(state: state)
}
.store(in: &cancellables)
context.$viewState
.map(\.aliasLocalPart)
.removeDuplicates()
@@ -160,8 +131,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
return
}
guard state.isKnockingFeatureEnabled,
!state.bindings.isRoomPrivate,
guard !state.bindings.selectedAccessType.isPrivate,
let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: aliasLocalPart,
serverName: state.serverName) else {
// While is empty or private room we don't change or display the error
@@ -195,30 +165,15 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
.store(in: &cancellables)
}
private func updateParameters(state: CreateRoomScreenViewState) {
parameters.name = state.roomName
parameters.topic = state.bindings.roomTopic
parameters.isRoomPrivate = state.bindings.isRoomPrivate
parameters.isKnockingOnly = state.bindings.isKnockingOnly
if state.isKnockingFeatureEnabled, !state.aliasLocalPart.isEmpty {
parameters.aliasLocalPart = state.aliasLocalPart
} else {
parameters.aliasLocalPart = nil
}
}
private func createRoom() async {
defer {
hideLoadingIndicator()
}
showLoadingIndicator()
// Since the parameters are throttled, we need to make sure that the latest values are used
updateParameters(state: state)
// Better to double check the errors also when trying to create the room
if state.isKnockingFeatureEnabled, !parameters.isRoomPrivate {
guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: parameters.aliasLocalPart,
if !state.bindings.selectedAccessType.isPrivate {
guard let canonicalAlias = String.makeCanonicalAlias(aliasLocalPart: state.aliasLocalPart,
serverName: state.serverName),
isRoomAliasFormatValid(alias: canonicalAlias) else {
state.aliasErrors = [.invalidSymbols]
@@ -238,7 +193,7 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
}
let avatarURL: URL?
if let media = parameters.avatarImageMedia {
if let media = state.avatarMediaInfo {
switch await userSession.clientProxy.uploadMedia(media) {
case .success(let url):
avatarURL = URL(string: url)
@@ -263,14 +218,13 @@ class CreateRoomScreenViewModel: CreateRoomScreenViewModelType, CreateRoomScreen
avatarURL = nil
}
switch await userSession.clientProxy.createRoom(name: parameters.name,
topic: parameters.topic.isBlank ? nil : parameters.topic,
isRoomPrivate: parameters.isRoomPrivate,
// As of right now we don't want to make private rooms with the knock rule
isKnockingOnly: parameters.isRoomPrivate ? false : parameters.isKnockingOnly,
switch await userSession.clientProxy.createRoom(name: state.roomName,
topic: state.bindings.roomTopic.isBlank ? nil : state.bindings.roomTopic,
accessType: state.bindings.selectedAccessType,
isSpace: state.isSpace,
userIDs: [], // The invite users screen is shown next so we don't need to invite anyone right now.
avatarURL: avatarURL,
aliasLocalPart: parameters.isRoomPrivate ? nil : parameters.aliasLocalPart) {
aliasLocalPart: state.bindings.selectedAccessType.isPrivate ? nil : state.aliasLocalPart) {
case .success(let roomID):
guard case let .joined(roomProxy) = await userSession.clientProxy.roomForIdentifier(roomID) else {
state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom,
@@ -39,17 +39,15 @@ struct CreateRoomScreen: View {
Form {
roomSection
topicSection
securitySection
if context.viewState.isKnockingFeatureEnabled,
!context.isRoomPrivate {
roomAccessSection
roomAccessSection
if !context.selectedAccessType.isPrivate {
roomAliasSection
}
}
.compoundList()
.track(screen: .CreateRoom)
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenCreateRoomTitle)
.navigationTitle(context.viewState.isSpace ? L10n.screenCreateRoomNewSpaceTitle : L10n.screenCreateRoomNewRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.alert(item: $context.alertInfo)
@@ -60,15 +58,20 @@ struct CreateRoomScreen: View {
Section {
HStack(alignment: .center, spacing: 16) {
roomAvatarButton
let nameLabel = if #available(iOS 26, *) {
L10n.commonName
} else {
L10n.commonName.uppercased()
}
VStack(alignment: .leading, spacing: 8) {
Text(L10n.screenCreateRoomRoomNameLabel.uppercased())
Text(nameLabel)
.padding(.leading, ListRowPadding.horizontal)
.compoundListSectionHeader()
TextField(L10n.screenCreateRoomRoomNameLabel,
TextField(L10n.commonName,
text: roomNameBinding,
prompt: Text(L10n.commonRoomNamePlaceholder).foregroundColor(.compound.textSecondary),
prompt: Text(L10n.screenCreateRoomNamePlaceholder).foregroundColor(.compound.textSecondary),
axis: .horizontal)
.font(.compound.bodyLG)
.foregroundStyle(.compound.textPrimary)
@@ -90,7 +93,7 @@ struct CreateRoomScreen: View {
focus = nil
context.showAttachmentConfirmationDialog = true
} label: {
if let url = context.viewState.avatarURL {
if let url = context.viewState.avatarMediaInfo?.thumbnailURL {
AsyncImage(url: url) { image in
image
.resizable()
@@ -118,7 +121,7 @@ struct CreateRoomScreen: View {
}
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.mediaPicker)
if context.viewState.avatarURL != nil {
if context.viewState.avatarMediaInfo?.thumbnailURL != nil {
Button(L10n.actionRemove, role: .destructive) {
context.send(viewAction: .removeImage)
}
@@ -128,7 +131,7 @@ struct CreateRoomScreen: View {
private var topicSection: some View {
Section {
ListRow(label: .plain(title: L10n.commonTopicPlaceholder),
ListRow(label: .plain(title: L10n.screenCreateRoomTopicPlaceholder),
kind: .textField(text: $context.roomTopic, axis: .vertical))
.lineLimit(3, reservesSpace: false)
.focused($focus, equals: .topic)
@@ -139,34 +142,16 @@ struct CreateRoomScreen: View {
}
}
private var securitySection: some View {
Section {
ListRow(label: .default(title: L10n.screenCreateRoomPrivateOptionTitle,
description: L10n.screenCreateRoomPrivateOptionDescription,
icon: \.lock,
iconAlignment: .top),
kind: .selection(isSelected: context.isRoomPrivate) { context.isRoomPrivate = true })
ListRow(label: .default(title: L10n.screenCreateRoomPublicOptionTitle,
description: L10n.screenCreateRoomPublicOptionDescription,
icon: \.public,
iconAlignment: .top),
kind: .selection(isSelected: !context.isRoomPrivate) { context.isRoomPrivate = false })
} header: {
Text(L10n.screenCreateRoomRoomVisibilitySectionTitle)
.compoundListSectionHeader()
}
}
private var roomAccessSection: some View {
Section {
ListRow(label: .plain(title: L10n.screenCreateRoomRoomAccessSectionAnyoneOptionTitle,
description: L10n.screenCreateRoomRoomAccessSectionAnyoneOptionDescription),
kind: .selection(isSelected: !context.isKnockingOnly) { context.isKnockingOnly = false })
ListRow(label: .plain(title: L10n.screenCreateRoomRoomAccessSectionKnockingOptionTitle,
description: L10n.screenCreateRoomRoomAccessSectionKnockingOptionDescription),
kind: .selection(isSelected: context.isKnockingOnly) { context.isKnockingOnly = true })
ForEach(context.viewState.availableAccessTypes, id: \.self) { accessType in
CreateRoomAccessRow(access: accessType,
isSelected: context.selectedAccessType == accessType) {
context.selectedAccessType = accessType
}
}
} header: {
Text(L10n.screenCreateRoomRoomAccessSectionHeader)
Text(L10n.screenCreateRoomRoomAccessSectionTitle)
.compoundListSectionHeader()
}
}
@@ -207,48 +192,129 @@ struct CreateRoomScreen: View {
}
}
private struct CreateRoomAccessRow: View {
let access: CreateRoomAccessType
let isSelected: Bool
let onSelection: () -> Void
var title: String {
switch access {
case .public:
L10n.screenCreateRoomRoomAccessSectionPublicOptionTitle
case .askToJoin:
L10n.screenCreateRoomRoomAccessSectionKnockingOptionTitle
case .private:
L10n.screenCreateRoomRoomAccessSectionPrivateOptionTitle
}
}
var description: String {
switch access {
case .public:
L10n.screenCreateRoomRoomAccessSectionPublicOptionDescription
case .askToJoin:
L10n.screenCreateRoomRoomAccessSectionKnockingOptionDescription
case .private:
L10n.screenCreateRoomRoomAccessSectionPrivateOptionDescription
}
}
var icon: KeyPath<CompoundIcons, Image> {
switch access {
case .public:
\.public
case .askToJoin:
\.userAdd
case .private:
\.lock
}
}
var body: some View {
ListRow(label: .default(title: title,
description: description,
icon: icon,
iconAlignment: .top),
kind: .selection(isSelected: isSelected,
action: onSelection))
}
}
// MARK: - Previews
struct CreateRoom_Previews: PreviewProvider, TestablePreview {
static let viewModel = {
AppSettings.resetAllSettings()
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com"))))
return CreateRoomScreenViewModel(userSession: userSession,
initialParameters: .init(),
return CreateRoomScreenViewModel(isSpace: false,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
}()
static let spaceViewModel = {
AppSettings.resetAllSettings()
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@userid:example.com"))))
return CreateRoomScreenViewModel(isSpace: true,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
}()
static let publicRoomViewModel = {
AppSettings.resetAllSettings()
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))))
ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomScreenViewModel(userSession: userSession,
initialParameters: .init(isRoomPrivate: false),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
let viewModel = CreateRoomScreenViewModel(isSpace: false,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
viewModel.context.selectedAccessType = .public
return viewModel
}()
static let askToJoinViewModel = {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
appSettings.knockingEnabled = true
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))))
let viewModel = CreateRoomScreenViewModel(isSpace: false,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: appSettings)
viewModel.context.selectedAccessType = .askToJoin
return viewModel
}()
static let publicRoomInvalidAliasViewModel = {
AppSettings.resetAllSettings()
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))))
ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomScreenViewModel(userSession: userSession,
initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "#:"),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
let viewModel = CreateRoomScreenViewModel(isSpace: false,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
viewModel.context.selectedAccessType = .public
viewModel.context.send(viewAction: .updateAliasLocalPart("#:"))
return viewModel
}()
static let publicRoomExistingAliasViewModel = {
AppSettings.resetAllSettings()
let clientProxy = ClientProxyMock(.init(userIDServerName: "example.org", userID: "@userid:example.com"))
clientProxy.isAliasAvailableReturnValue = .success(false)
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
ServiceLocator.shared.settings.knockingEnabled = true
return CreateRoomScreenViewModel(userSession: userSession,
initialParameters: .init(isRoomPrivate: false, aliasLocalPart: "existing"),
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
let viewModel = CreateRoomScreenViewModel(isSpace: false,
userSession: userSession,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: UserIndicatorControllerMock(),
appSettings: ServiceLocator.shared.settings)
viewModel.context.selectedAccessType = .public
viewModel.context.send(viewAction: .updateAliasLocalPart("existing"))
return viewModel
}()
static var previews: some View {
@@ -257,11 +323,21 @@ struct CreateRoom_Previews: PreviewProvider, TestablePreview {
}
.previewDisplayName("Create Room")
NavigationStack {
CreateRoomScreen(context: spaceViewModel.context)
}
.previewDisplayName("Create Space")
NavigationStack {
CreateRoomScreen(context: publicRoomViewModel.context)
}
.previewDisplayName("Create Public Room")
NavigationStack {
CreateRoomScreen(context: askToJoinViewModel.context)
}
.previewDisplayName("Create Knockable Room")
NavigationStack {
CreateRoomScreen(context: publicRoomInvalidAliasViewModel.context)
}
@@ -48,13 +48,15 @@ struct SecurityAndPrivacyScreen: View {
Section {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle,
description: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription,
icon: \.public),
icon: \.public,
iconAlignment: .top),
kind: .selection(isSelected: context.desiredSettings.accessType == .anyone) { context.desiredSettings.accessType = .anyone })
if context.viewState.isSpaceMembersOptionAvailable {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
description: context.viewState.spaceMembersDescription,
icon: \.space),
icon: \.space,
iconAlignment: .top),
kind: .selection(isSelected: context.desiredSettings.accessType.isSpaceMembers) {
context.send(viewAction: .selectedSpaceMembersAccess)
})
@@ -74,7 +76,8 @@ struct SecurityAndPrivacyScreen: View {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
description: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription,
icon: \.lock),
icon: \.lock,
iconAlignment: .top),
kind: .selection(isSelected: context.desiredSettings.accessType == .inviteOnly) { context.desiredSettings.accessType = .inviteOnly })
} header: {
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
@@ -94,7 +97,8 @@ struct SecurityAndPrivacyScreen: View {
private var askToJoinOption: some View {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
description: L10n.screenSecurityAndPrivacyAskToJoinOptionDescription,
icon: \.userAdd),
icon: \.userAdd,
iconAlignment: .top),
kind: .selection(isSelected: context.desiredSettings.accessType == .askToJoin) { context.desiredSettings.accessType = .askToJoin })
.disabled(!context.viewState.isKnockingEnabled)
}
@@ -102,7 +106,8 @@ struct SecurityAndPrivacyScreen: View {
private var askToJoinWithSpaceMembersOption: some View {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
description: context.viewState.askToJoinWithSpaceMembersDescription,
icon: \.userAdd),
icon: \.userAdd,
iconAlignment: .top),
kind: .selection(isSelected: context.desiredSettings.accessType.isAskToJoinWithSpaceMembers) { context.send(viewAction: .selectedAskToJoinWithSpaceMembersAccess) })
.disabled(!context.viewState.isAskToJoinWithSpaceMembersOptionSelectable)
}
@@ -474,25 +474,26 @@ class ClientProxy: ClientProxyProtocol {
func createRoom(name: String,
topic: String?,
isRoomPrivate: Bool,
isKnockingOnly: Bool,
accessType: CreateRoomAccessType,
isSpace: Bool,
userIDs: [String],
avatarURL: URL?,
aliasLocalPart: String?) async -> Result<String, ClientProxyError> {
do {
let parameters = CreateRoomParameters(name: name,
topic: topic,
isEncrypted: isRoomPrivate,
isEncrypted: accessType.isEncrypted,
isDirect: false,
visibility: isRoomPrivate ? .private : .public,
preset: isRoomPrivate ? .privateChat : .publicChat,
visibility: accessType.visibility,
preset: accessType.preset,
invite: userIDs,
avatar: avatarURL?.absoluteString,
powerLevelContentOverride: isKnockingOnly ? Self.knockingRoomCreationPowerLevelOverrides : Self.roomCreationPowerLevelOverrides,
joinRuleOverride: isKnockingOnly ? .knock : nil,
historyVisibilityOverride: isRoomPrivate ? .invited : nil,
powerLevelContentOverride: accessType == .askToJoin ? Self.knockingRoomCreationPowerLevelOverrides : Self.roomCreationPowerLevelOverrides,
joinRuleOverride: accessType.joinRuleOverride,
historyVisibilityOverride: accessType.historyVisibilityOverride,
// This is an FFI naming mistake, what is required is the `aliasLocalPart` not the whole alias
canonicalAlias: aliasLocalPart)
canonicalAlias: aliasLocalPart,
isSpace: isSpace)
let roomID = try await client.createRoom(request: parameters)
await waitForRoomToSync(roomID: roomID)
@@ -1300,3 +1301,35 @@ private extension TimelineMediaVisibility {
}
}
}
private extension CreateRoomAccessType {
var isEncrypted: Bool {
switch self {
case .public:
false
case .askToJoin, .private:
true
}
}
var visibility: RoomVisibility {
isPrivate ? .private : .public
}
var preset: RoomPreset {
isPrivate ? .privateChat : .publicChat
}
var historyVisibilityOverride: RoomHistoryVisibility? {
isPrivate ? .invited : nil
}
var joinRuleOverride: JoinRule? {
switch self {
case .askToJoin:
.knock
case .private, .public:
nil
}
}
}
@@ -48,6 +48,21 @@ enum SlidingSyncConstants {
static let maximumVisibleRangeSize = 30
}
enum CreateRoomAccessType: CaseIterable {
case `public`
case askToJoin
case `private`
var isPrivate: Bool {
switch self {
case .private:
true
case .public, .askToJoin:
false
}
}
}
/// This struct represents the configuration that we are using to register the application through Pusher to Sygnal
/// using the Matrix Rust SDK, more info here:
/// https://github.com/matrix-org/sygnal
@@ -156,8 +171,8 @@ protocol ClientProxyProtocol: AnyObject {
func createRoom(name: String,
topic: String?,
isRoomPrivate: Bool,
isKnockingOnly: Bool,
accessType: CreateRoomAccessType,
isSpace: Bool,
userIDs: [String],
avatarURL: URL?,
aliasLocalPart: String?) async -> Result<String, ClientProxyError>
@@ -651,14 +651,16 @@ class MockScreen: Identifiable {
return navigationStackCoordinator
case .startChatFlow:
let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com"))
clientProxy.createRoomNameTopicIsRoomPrivateIsKnockingOnlyUserIDsAvatarURLAliasLocalPartReturnValue = .success("!new-room:client.com")
clientProxy.createRoomNameTopicAccessTypeIsSpaceUserIDsAvatarURLAliasLocalPartReturnValue =
.success("!new-room:client.com")
clientProxy.roomForIdentifierClosure = { roomID in .joined(JoinedRoomProxyMock(.init(id: roomID, members: []))) }
let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby])
let navigationStackCoordinator = NavigationStackCoordinator()
let flowCoordinator = StartChatFlowCoordinator(userDiscoveryService: userDiscoveryService,
let flowCoordinator = StartChatFlowCoordinator(isSpace: false,
userDiscoveryService: userDiscoveryService,
navigationStackCoordinator: navigationStackCoordinator,
flowParameters: CommonFlowParameters(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
bugReportService: BugReportServiceMock(.init()),