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
@@ -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