Empty spaces list screen (#4936)

* Added an FF to create spaces + empty spaces list state

* removed learn more button and improved spacing

* updated toolbar for chats and spaces to match ios 26 designs
This commit is contained in:
Mauro
2026-01-09 15:17:43 +01:00
committed by GitHub
parent a894da0903
commit 34123a4567
79 changed files with 306 additions and 175 deletions
@@ -67,6 +67,7 @@
"action_copy_text" = "Copy text";
"action_create" = "Create";
"action_create_a_room" = "Create a room";
"action_create_space" = "Create space";
"action_deactivate" = "Deactivate";
"action_deactivate_account" = "Deactivate account";
"action_decline" = "Decline";
@@ -188,6 +189,7 @@
"common_copied_to_clipboard" = "Copied to clipboard";
"common_copyright" = "Copyright";
"common_creating_room" = "Creating room…";
"common_creating_space" = "Creating space…";
"common_current_user_canceled_knock" = "Request canceled";
"common_current_user_left_room" = "Left room";
"common_current_user_left_space" = "Left space";
@@ -307,6 +309,7 @@
"common_starting_chat" = "Starting chat…";
"common_sticker" = "Sticker";
"common_success" = "Success";
"common_suggested" = "Suggested";
"common_suggestions" = "Suggestions";
"common_syncing" = "Syncing";
"common_system" = "System";
@@ -526,14 +529,19 @@
"screen_create_poll_options_section_title" = "Options";
"screen_create_poll_remove_accessibility_label" = "Remove %1$@";
"screen_create_poll_settings_section_title" = "Settings";
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join this room";
"screen_create_room_error_creating_space" = "The space could not be created because of an unknown error. Try again later.";
"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 the room but an administrator or a moderator will have to accept the request";
"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_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_visibility_section_title" = "Room visibility";
"screen_create_room_title_new_space" = "New space";
"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.";
@@ -679,7 +687,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_footer" = "Choose which spaces’ members can join this room without an invitation. %1$@";
"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";
"screen_security_and_privacy_room_access_section_header" = "Access";
@@ -712,6 +720,7 @@
"screen_space_announcement_title" = "Introducing Spaces";
"screen_space_list_description" = "Spaces you have created or joined.";
"screen_space_list_details" = "%1$@ • %2$@";
"screen_space_list_empty_state_title" = "Create spaces to organize rooms";
"screen_space_list_parent_space" = "%1$@ space";
"screen_space_list_title" = "Spaces";
"screen_start_chat_join_room_by_address_action" = "Join room by address";
@@ -835,8 +844,10 @@
"screen_create_poll_title" = "Create Poll";
"screen_create_room_action_create_room" = "New room";
"screen_create_room_error_creating_room" = "An error occurred when creating the room";
"screen_create_room_private_option_description" = "Only people invited can access this room. All messages are end-to-end encrypted.";
"screen_create_room_private_option_description" = "Only people invited can join.";
"screen_create_room_private_option_title" = "Private";
"screen_create_room_public_option_description" = "Anyone can find this room.\nYou can change this anytime in room settings.";
"screen_create_room_public_option_title" = "Public";
"screen_create_room_topic_label" = "Topic (optional)";
"screen_deactivate_account_confirmation_dialog_content" = "Please confirm that you want to deactivate your account. This action cannot be undone.";
"screen_deactivate_account_delete_all_messages" = "Delete all my messages";
@@ -1434,8 +1445,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_private_option_title" = "Private room";
"screen_create_room_public_option_title" = "Public room";
"screen_create_room_room_name_label" = "Room name";
"screen_create_room_title" = "Create a room";
"screen_deactivate_account_title" = "Deactivate account";
@@ -67,6 +67,7 @@
"action_copy_text" = "Copy text";
"action_create" = "Create";
"action_create_a_room" = "Create a room";
"action_create_space" = "Create space";
"action_deactivate" = "Deactivate";
"action_deactivate_account" = "Deactivate account";
"action_decline" = "Decline";
@@ -188,6 +189,7 @@
"common_copied_to_clipboard" = "Copied to clipboard";
"common_copyright" = "Copyright";
"common_creating_room" = "Creating room…";
"common_creating_space" = "Creating space…";
"common_current_user_canceled_knock" = "Request canceled";
"common_current_user_left_room" = "Left room";
"common_current_user_left_space" = "Left space";
@@ -307,6 +309,7 @@
"common_starting_chat" = "Starting chat…";
"common_sticker" = "Sticker";
"common_success" = "Success";
"common_suggested" = "Suggested";
"common_suggestions" = "Suggestions";
"common_syncing" = "Syncing";
"common_system" = "System";
@@ -526,14 +529,19 @@
"screen_create_poll_options_section_title" = "Options";
"screen_create_poll_remove_accessibility_label" = "Remove %1$@";
"screen_create_poll_settings_section_title" = "Settings";
"screen_create_room_room_access_section_anyone_option_description" = "Anyone can join this room";
"screen_create_room_error_creating_space" = "The space could not be created because of an unknown error. Try again later.";
"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 the room but an administrator or a moderator will have to accept the request";
"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_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_visibility_section_title" = "Room visibility";
"screen_create_room_title_new_space" = "New space";
"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.";
@@ -679,7 +687,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_footer" = "Choose which spaces’ members can join this room without an invitation. %1$@";
"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";
"screen_security_and_privacy_room_access_section_header" = "Access";
@@ -712,6 +720,7 @@
"screen_space_announcement_title" = "Introducing Spaces";
"screen_space_list_description" = "Spaces you have created or joined.";
"screen_space_list_details" = "%1$@ • %2$@";
"screen_space_list_empty_state_title" = "Create spaces to organize rooms";
"screen_space_list_parent_space" = "%1$@ space";
"screen_space_list_title" = "Spaces";
"screen_start_chat_join_room_by_address_action" = "Join room by address";
@@ -835,8 +844,10 @@
"screen_create_poll_title" = "Create Poll";
"screen_create_room_action_create_room" = "New room";
"screen_create_room_error_creating_room" = "An error occurred when creating the room";
"screen_create_room_private_option_description" = "Only people invited can access this room. All messages are end-to-end encrypted.";
"screen_create_room_private_option_description" = "Only people invited can join.";
"screen_create_room_private_option_title" = "Private";
"screen_create_room_public_option_description" = "Anyone can find this room.\nYou can change this anytime in room settings.";
"screen_create_room_public_option_title" = "Public";
"screen_create_room_topic_label" = "Topic (optional)";
"screen_deactivate_account_confirmation_dialog_content" = "Please confirm that you want to deactivate your account. This action cannot be undone.";
"screen_deactivate_account_delete_all_messages" = "Delete all my messages";
@@ -1434,8 +1445,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_private_option_title" = "Private room";
"screen_create_room_public_option_title" = "Public room";
"screen_create_room_room_name_label" = "Room name";
"screen_create_room_title" = "Create a room";
"screen_deactivate_account_title" = "Deactivate account";
@@ -65,10 +65,13 @@ final class AppSettings {
case threadsEnabled
case developerOptionsEnabled
case linkPreviewsEnabled
case spaceSettingsEnabled
case focusEventOnNotificationTap
case linkNewDeviceEnabled
// Spaces
case spaceSettingsEnabled
case createSpaceEnabled
// Doug's tweaks 🔧
case hideUnreadMessagesBadge
case hideQuietNotificationAlerts
@@ -377,6 +380,14 @@ final class AppSettings {
// MARK: - Feature Flags
// Spaces
@UserPreference(key: UserDefaultsKeys.spaceSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
var spaceSettingsEnabled
@UserPreference(key: UserDefaultsKeys.createSpaceEnabled, defaultValue: false, storageType: .userDefaults(store))
var createSpaceEnabled
// Others
@UserPreference(key: UserDefaultsKeys.publicSearchEnabled, defaultValue: false, storageType: .userDefaults(store))
var publicSearchEnabled
@@ -402,9 +413,6 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.focusEventOnNotificationTap, defaultValue: false, storageType: .userDefaults(store))
var focusEventOnNotificationTap
@UserPreference(key: UserDefaultsKeys.spaceSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
var spaceSettingsEnabled
@UserPreference(key: UserDefaultsKeys.linkPreviewsEnabled, defaultValue: false, storageType: .userDefaults(store))
var linkPreviewsEnabled
@@ -288,7 +288,10 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
.store(in: &cancellables)
userSession.clientProxy.spaceService.topLevelSpacesPublisher
.map { $0.isEmpty ? .hidden : nil }
.combineLatest(flowParameters.appSettings.$createSpaceEnabled)
.map { topLevelSpaces, isCreateSpaceEnabled in
!isCreateSpaceEnabled && topLevelSpaces.isEmpty ? .hidden : nil
}
.weakAssign(to: \.chatsTabDetails.barVisibilityOverride, on: self)
.store(in: &cancellables)
}
+24 -6
View File
@@ -168,6 +168,8 @@ internal enum L10n {
internal static var actionCreate: String { return L10n.tr("Localizable", "action_create") }
/// Create a room
internal static var actionCreateARoom: String { return L10n.tr("Localizable", "action_create_a_room") }
/// Create space
internal static var actionCreateSpace: String { return L10n.tr("Localizable", "action_create_space") }
/// Deactivate
internal static var actionDeactivate: String { return L10n.tr("Localizable", "action_deactivate") }
/// Deactivate account
@@ -420,6 +422,8 @@ internal enum L10n {
internal static var commonCopyright: String { return L10n.tr("Localizable", "common_copyright") }
/// Creating room…
internal static var commonCreatingRoom: String { return L10n.tr("Localizable", "common_creating_room") }
/// Creating space…
internal static var commonCreatingSpace: String { return L10n.tr("Localizable", "common_creating_space") }
/// Request canceled
internal static var commonCurrentUserCanceledKnock: String { return L10n.tr("Localizable", "common_current_user_canceled_knock") }
/// Left room
@@ -696,6 +700,8 @@ internal enum L10n {
internal static var commonSticker: String { return L10n.tr("Localizable", "common_sticker") }
/// Success
internal static var commonSuccess: String { return L10n.tr("Localizable", "common_success") }
/// Suggested
internal static var commonSuggested: String { return L10n.tr("Localizable", "common_suggested") }
/// Suggestions
internal static var commonSuggestions: String { return L10n.tr("Localizable", "common_suggestions") }
/// Syncing
@@ -1559,25 +1565,33 @@ internal enum L10n {
internal static var screenCreateRoomAddPeopleTitle: String { return L10n.tr("Localizable", "screen_create_room_add_people_title") }
/// An error occurred when creating the room
internal static var screenCreateRoomErrorCreatingRoom: String { return L10n.tr("Localizable", "screen_create_room_error_creating_room") }
/// Only people invited can access this room. All messages are end-to-end encrypted.
/// 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") }
/// New space
internal static var screenCreateRoomNewSpaceTitle: String { return L10n.tr("Localizable", "screen_create_room_new_space_title") }
/// Only people invited can join.
internal static var screenCreateRoomPrivateOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_private_option_description") }
/// Private room
/// Private
internal static var screenCreateRoomPrivateOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_private_option_title") }
/// Anyone can find this room.
/// You can change this anytime in room settings.
internal static var screenCreateRoomPublicOptionDescription: String { return L10n.tr("Localizable", "screen_create_room_public_option_description") }
/// Public room
/// Anyone can join.
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 this room
/// 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 the room but an administrator or a moderator will have to accept the request
/// 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
internal static var screenCreateRoomRoomAccessSectionKnockingOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_room_access_section_knocking_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.
internal static var screenCreateRoomRoomAddressSectionFooter: String { return L10n.tr("Localizable", "screen_create_room_room_address_section_footer") }
/// Room address
@@ -1588,6 +1602,8 @@ internal enum L10n {
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") }
/// Please confirm that you want to deactivate your account. This action cannot be undone.
@@ -2882,7 +2898,7 @@ internal enum L10n {
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_description") }
/// Anyone
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_title") }
/// Choose which spaces’ members can join this room without an invitation. %1$@
/// Choose which spaces’ members can join this room without an invitation. %1$@
internal static func screenSecurityAndPrivacyRoomAccessFooter(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_security_and_privacy_room_access_footer", String(describing: p1))
}
@@ -3113,6 +3129,8 @@ internal enum L10n {
internal static func screenSpaceListDetails(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "screen_space_list_details", String(describing: p1), String(describing: p2))
}
/// Create spaces to organize rooms
internal static var screenSpaceListEmptyStateTitle: String { return L10n.tr("Localizable", "screen_space_list_empty_state_title") }
/// %1$@ space
internal static func screenSpaceListParentSpace(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_space_list_parent_space", String(describing: p1))
+5 -1
View File
@@ -87,7 +87,11 @@ enum UserAvatarSizeOnScreen {
var value: CGFloat {
switch self {
case .chats, .spaces:
return 32
if #available(iOS 26, *) {
return 40
} else {
return 32
}
case .timeline:
return 32
case .readReceipt:
@@ -40,9 +40,13 @@ struct HomeScreen: View {
.backportSharedBackgroundVisibility(.hidden)
ToolbarItem(placement: .primaryAction) {
newRoomButton
if #available(iOS 26, *) {
newRoomButton
} else {
newRoomButton
.buttonStyle(.compound(.super, size: .toolbarIcon))
}
}
.backportSharedBackgroundVisibility(.hidden)
}
private var settingsButton: some View {
@@ -71,7 +75,6 @@ struct HomeScreen: View {
} label: {
CompoundIcon(\.plus)
}
.buttonStyle(.compound(.super, size: .toolbarIcon))
.accessibilityLabel(L10n.actionStartChat)
.accessibilityIdentifier(A11yIdentifiers.homeScreen.startChat)
default:
@@ -66,8 +66,10 @@ protocol DeveloperOptionsProtocol: AnyObject {
var linkPreviewsEnabled: Bool { get set }
var spaceSettingsEnabled: Bool { get set }
var linkNewDeviceEnabled: Bool { get set }
var spaceSettingsEnabled: Bool { get set }
var createSpaceEnabled: Bool { get set }
}
extension AppSettings: DeveloperOptionsProtocol { }
@@ -45,15 +45,22 @@ struct DeveloperOptionsScreen: View {
Toggle(isOn: $context.linkNewDeviceEnabled) {
Text("Link new device with QR code")
}
Toggle(isOn: $context.spaceSettingsEnabled) {
Text("Space settings")
}
context.viewState.appHooks
.developerOptionsScreenHook
.generalSectionRows()
}
Section("Spaces") {
Toggle(isOn: $context.spaceSettingsEnabled) {
Text("Space settings")
}
Toggle(isOn: $context.createSpaceEnabled) {
Text("Create space")
}
}
Section("Room List") {
Toggle(isOn: $context.publicSearchEnabled) {
Text("Public search")
@@ -21,6 +21,8 @@ struct SpaceListScreenViewState: BindableState {
var topLevelSpaces: [SpaceRoomProxyProtocol]
var selectedSpaceID: String?
var isCreateSpaceEnabled: Bool
var bindings: SpaceListScreenViewStateBindings
}
@@ -33,4 +35,5 @@ enum SpaceListScreenViewAction {
case showSettings
case screenAppeared
case featureAnnouncementAppeared
case createSpace
}
@@ -31,6 +31,7 @@ class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenVie
super.init(initialViewState: SpaceListScreenViewState(userID: userSession.clientProxy.userID,
topLevelSpaces: spaceServiceProxy.topLevelSpacesPublisher.value,
isCreateSpaceEnabled: appSettings.createSpaceEnabled,
bindings: .init()),
mediaProvider: userSession.mediaProvider)
@@ -52,6 +53,10 @@ class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenVie
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.userDisplayName, on: self)
.store(in: &cancellables)
appSettings.$createSpaceEnabled
.weakAssign(to: \.state.isCreateSpaceEnabled, on: self)
.store(in: &cancellables)
}
// MARK: - Public
@@ -73,6 +78,9 @@ class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenVie
}
case .featureAnnouncementAppeared:
appSettings.hasSeenSpacesAnnouncement = true
case .createSpace:
// TODO: Implement
break
}
}
@@ -13,24 +13,46 @@ struct SpaceListScreen: View {
@Bindable var context: SpaceListScreenViewModel.Context
var body: some View {
ScrollView {
LazyVStack(spacing: 0) {
header
spaces
mainContent
.navigationTitle(L10n.screenSpaceListTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.toolbarBloom(hasSearchBar: false)
.onAppear { context.send(viewAction: .screenAppeared) }
.sheet(isPresented: $context.isPresentingFeatureAnnouncement) {
SpacesAnnouncementSheetView(context: context)
}
}
@ViewBuilder
private var mainContent: some View {
if context.viewState.isCreateSpaceEnabled, context.viewState.topLevelSpaces.isEmpty {
emptyState
} else {
ScrollView {
LazyVStack(spacing: 0) {
header
spaces
}
}
}
.navigationTitle(L10n.screenSpaceListTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.toolbarBloom(hasSearchBar: false)
.onAppear { context.send(viewAction: .screenAppeared) }
.sheet(isPresented: $context.isPresentingFeatureAnnouncement) {
SpacesAnnouncementSheetView(context: context)
}
}
var header: some View {
private var emptyState: some View {
FullscreenDialog(horizontalPadding: 24) {
TitleAndIcon(title: L10n.screenSpaceListEmptyStateTitle,
icon: \.spaceSolid,
iconStyle: .defaultSolid)
} bottomContent: {
Button(L10n.actionCreateSpace) {
context.send(viewAction: .createSpace)
}
.buttonStyle(.compound(.primary))
}
}
private var header: some View {
VStack(spacing: 16) {
BigIcon(icon: \.spaceSolid)
@@ -62,7 +84,7 @@ struct SpaceListScreen: View {
}
}
var spaces: some View {
private var spaces: some View {
ForEach(context.viewState.topLevelSpaces, id: \.id) { spaceRoomProxy in
SpaceRoomCell(spaceRoomProxy: spaceRoomProxy,
isSelected: spaceRoomProxy.id == context.viewState.selectedSpaceID,
@@ -73,7 +95,7 @@ struct SpaceListScreen: View {
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Button {
context.send(viewAction: .showSettings)
@@ -95,6 +117,18 @@ struct SpaceListScreen: View {
Text("").accessibilityHidden(true)
}
.backportSharedBackgroundVisibility(.hidden)
if context.viewState.isCreateSpaceEnabled {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
context.send(viewAction: .createSpace)
} label: {
CompoundIcon(\.plus)
.accessibilityHidden(true)
}
.accessibilityLabel(L10n.actionCreateSpace)
}
}
}
}
@@ -102,20 +136,31 @@ struct SpaceListScreen: View {
struct SpaceListScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = makeViewModel()
static let emptyViewModel = makeViewModel(isEmpty: true)
static var previews: some View {
NavigationStack {
SpaceListScreen(context: viewModel.context)
}
NavigationStack {
SpaceListScreen(context: emptyViewModel.context)
}
.previewDisplayName("Empty")
}
static func makeViewModel() -> SpaceListScreenViewModel {
static func makeViewModel(isEmpty: Bool = false) -> SpaceListScreenViewModel {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
appSettings.createSpaceEnabled = true
appSettings.hasSeenSpacesAnnouncement = true
let clientProxy = ClientProxyMock(.init())
clientProxy.spaceService = SpaceServiceProxyMock(.init(topLevelSpaces: .mockJoinedSpaces))
clientProxy.spaceService = SpaceServiceProxyMock(.init(topLevelSpaces: isEmpty ? [] : .mockJoinedSpaces))
let viewModel = SpaceListScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
selectedSpacePublisher: .init(nil),
appSettings: ServiceLocator.shared.settings,
appSettings: appSettings,
userIndicatorController: UserIndicatorControllerMock())
return viewModel