Add the empty state to SpaceScreen. (#4985)

* Rename PaginationState.timelineEndReached to PaginationState.endReached.

* Rename PaginationState to TimelinePaginationState.

Also renames PaginationStatus to PaginationState so that a TimelinePaginationState consists of the forward and backward pagination states.

* Add the empty state to SpaceScreen.

Only has 1 of the 2 buttons for now.
This commit is contained in:
Doug
2026-01-22 12:37:34 +00:00
committed by GitHub
parent b4b9f6affa
commit cea365ac97
30 changed files with 164 additions and 94 deletions
@@ -25,7 +25,7 @@ struct SpaceScreenViewState: BindableState {
var permalink: URL?
var roomProxy: JoinedRoomProxyProtocol?
var isPaginating = false
var paginationState: PaginationState = .idle
var rooms: [SpaceServiceRoomProtocol]
var selectedSpaceRoomID: String?
var joiningRoomIDs: Set<String> = []
@@ -40,6 +40,10 @@ struct SpaceScreenViewState: BindableState {
var bindings = SpaceScreenViewStateBindings()
var shouldShowEmptyState: Bool {
rooms.isEmpty && paginationState == .endReached && canEditChildren
}
var visibleRooms: [SpaceServiceRoomProtocol] {
if editMode == .inactive {
rooms
@@ -57,13 +57,16 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc
spaceRoomListProxy.paginationStatePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] paginationState in
guard let self else { return }
switch paginationState {
case .idle(let endReached):
self?.state.isPaginating = false
guard !endReached else { return }
case .idle(endReached: false):
state.paginationState = .idle
Task { await spaceRoomListProxy.paginate() }
case .idle(endReached: true):
state.paginationState = .endReached
case .loading:
self?.state.isPaginating = true
state.paginationState = .paginating
}
}
.store(in: &cancellables)
@@ -22,7 +22,11 @@ struct SpaceScreen: View {
mediaProvider: context.mediaProvider)
}
rooms
if context.viewState.shouldShowEmptyState {
emptyState
} else {
rooms
}
}
}
.environment(\.editMode, .constant(context.viewState.editMode))
@@ -53,12 +57,32 @@ struct SpaceScreen: View {
}
}
if context.viewState.isPaginating {
if context.viewState.paginationState == .paginating {
ProgressView()
.padding()
}
}
var emptyState: some View {
VStack(spacing: 24) {
TitleAndIcon(title: L10n.screenSpaceEmptyStateTitle,
icon: \.room,
iconStyle: .defaultSolid)
.padding(.horizontal, 24)
VStack(spacing: 16) {
Button { context.send(viewAction: .addExistingRooms) } label: {
Label(L10n.actionAddExistingRooms, icon: \.plus)
}
.buttonStyle(.compound(.primary))
// @Velin92 Create Room button goes here.
}
.padding(.horizontal, 16)
}
.padding(.top, 40)
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
if isEditModeActive {
@@ -150,6 +174,7 @@ struct SpaceScreen: View {
struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = makeViewModel()
static let managingViewModel = makeViewModel(isManagingRooms: true)
static let newSpaceViewModel = makeViewModel(isNewSpace: true)
static var previews: some View {
NavigationStack {
@@ -160,9 +185,19 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
SpaceScreen(context: managingViewModel.context)
}
.previewDisplayName("Managing")
NavigationStack {
SpaceScreen(context: newSpaceViewModel.context)
}
.previewDisplayName("New Space")
.snapshotPreferences(expect: newSpaceViewModel.context.observe(\.viewState.shouldShowEmptyState))
}
static func makeViewModel(isManagingRooms: Bool = false) -> SpaceScreenViewModel {
static func makeViewModel(isManagingRooms: Bool = false, isNewSpace: Bool = false) -> SpaceScreenViewModel {
let appSettings = AppSettings()
appSettings.spaceSettingsEnabled = true
appSettings.createSpaceEnabled = true
let spaceServiceRoom = SpaceServiceRoomMock(.init(id: "!eng-space:matrix.org",
name: "Engineering Team",
isSpace: true,
@@ -173,11 +208,11 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
canonicalAlias: "#engineering-team:element.io",
joinRule: .knockRestricted(rules: [.roomMembership(roomId: "")])))
let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceServiceRoom: spaceServiceRoom,
initialSpaceRooms: .mockSpaceList))
initialSpaceRooms: isNewSpace ? [] : .mockSpaceList))
let clientProxy = ClientProxyMock(.init())
clientProxy.roomForIdentifierClosure = { _ in
.joined(JoinedRoomProxyMock(.init()))
.joined(JoinedRoomProxyMock(.init(members: .allMembersAsAdmin)))
}
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
@@ -185,7 +220,7 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview {
spaceServiceProxy: SpaceServiceProxyMock(.init()),
selectedSpaceRoomPublisher: .init(nil),
userSession: userSession,
appSettings: AppSettings(),
appSettings: appSettings,
userIndicatorController: UserIndicatorControllerMock())
if isManagingRooms {