mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Initial setup for the SpaceListScreen. (#4380)
This commit is contained in:
@@ -74,6 +74,11 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
isNewLogin: isNewLogin)
|
||||
chatsTabDetails = .init(tag: HomeTab.chats, title: L10n.screenHomeTabChats, icon: \.chat, selectedIcon: \.chatSolid)
|
||||
chatsTabDetails.barVisibility = .hidden
|
||||
|
||||
// This is just temporary, it needs a flow coordinator to properly handle (amongst other things) navigation/split views.
|
||||
let spaceListScreenCoordinator = SpaceListScreenCoordinator(parameters: .init(userSession: userSession))
|
||||
let spacesNavigationCoordinator = NavigationStackCoordinator()
|
||||
spacesNavigationCoordinator.setRootCoordinator(spaceListScreenCoordinator)
|
||||
spacesTabDetails = .init(tag: HomeTab.spaces, title: L10n.screenHomeTabSpaces, icon: \.space, selectedIcon: \.spaceSolid)
|
||||
|
||||
onboardingStackCoordinator = NavigationStackCoordinator()
|
||||
@@ -89,7 +94,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
|
||||
navigationTabCoordinator.setTabs([
|
||||
.init(coordinator: chatsSplitCoordinator, details: chatsTabDetails),
|
||||
.init(coordinator: BlankFormCoordinator(), details: spacesTabDetails)
|
||||
.init(coordinator: spacesNavigationCoordinator, details: spacesTabDetails)
|
||||
])
|
||||
|
||||
setupObservers()
|
||||
|
||||
@@ -153,6 +153,7 @@ enum TestablePreviewsDictionary {
|
||||
"SettingsScreen_Previews" : SettingsScreen_Previews.self,
|
||||
"ShimmerOverlay_Previews" : ShimmerOverlay_Previews.self,
|
||||
"SoftLogoutScreen_Previews" : SoftLogoutScreen_Previews.self,
|
||||
"SpaceListScreen_Previews" : SpaceListScreen_Previews.self,
|
||||
"SplashScreen_Previews" : SplashScreen_Previews.self,
|
||||
"StackedAvatarsView_Previews" : StackedAvatarsView_Previews.self,
|
||||
"StartChatScreen_Previews" : StartChatScreen_Previews.self,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
// periphery:ignore:all - this is just a spaceList remove this comment once generating the final file
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct SpaceListScreenCoordinatorParameters {
|
||||
let userSession: UserSessionProtocol
|
||||
}
|
||||
|
||||
enum SpaceListScreenCoordinatorAction {
|
||||
case showSettings
|
||||
}
|
||||
|
||||
final class SpaceListScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: SpaceListScreenCoordinatorParameters
|
||||
private let viewModel: SpaceListScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<SpaceListScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<SpaceListScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: SpaceListScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = SpaceListScreenViewModel(userSession: parameters.userSession)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received view model action: \(action)")
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .showSettings:
|
||||
actionsSubject.send(.showSettings)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(SpaceListScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum SpaceListScreenViewModelAction {
|
||||
case showSettings
|
||||
}
|
||||
|
||||
struct SpaceListScreenViewState: BindableState {
|
||||
let userID: String
|
||||
var userDisplayName: String?
|
||||
var userAvatarURL: URL?
|
||||
|
||||
var rooms: [HomeScreenRoom]
|
||||
var joinedRoomsCount: Int
|
||||
|
||||
var bindings: SpaceListScreenViewStateBindings
|
||||
|
||||
var subtitle: String {
|
||||
L10n.screenSpaceListDetails(L10n.commonSpaces(rooms.count), L10n.commonRooms(joinedRoomsCount))
|
||||
}
|
||||
}
|
||||
|
||||
struct SpaceListScreenViewStateBindings { }
|
||||
|
||||
enum SpaceListScreenViewAction {
|
||||
case showSettings
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias SpaceListScreenViewModelType = StateStoreViewModelV2<SpaceListScreenViewState, SpaceListScreenViewAction>
|
||||
|
||||
class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<SpaceListScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<SpaceListScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(userSession: UserSessionProtocol) {
|
||||
super.init(initialViewState: SpaceListScreenViewState(userID: userSession.clientProxy.userID,
|
||||
rooms: [],
|
||||
joinedRoomsCount: 0,
|
||||
bindings: .init()),
|
||||
mediaProvider: userSession.mediaProvider)
|
||||
|
||||
userSession.clientProxy.userAvatarURLPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSession.clientProxy.userDisplayNamePublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.weakAssign(to: \.state.userDisplayName, on: self)
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: SpaceListScreenViewAction) {
|
||||
MXLog.info("View model: received view action: \(viewAction)")
|
||||
|
||||
switch viewAction {
|
||||
case .showSettings:
|
||||
actionsSubject.send(.showSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol SpaceListScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<SpaceListScreenViewModelAction, Never> { get }
|
||||
var context: SpaceListScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct SpaceListScreen: View {
|
||||
@Bindable var context: SpaceListScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
header
|
||||
}
|
||||
}
|
||||
.navigationTitle(L10n.screenSpaceListTitle)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { toolbar }
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.bloom()
|
||||
}
|
||||
|
||||
var header: some View {
|
||||
VStack(spacing: 16) {
|
||||
BigIcon(icon: \.spaceSolid)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text(L10n.screenSpaceListTitle)
|
||||
.font(.compound.headingLGBold)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(context.viewState.subtitle)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundStyle(.compound.textSecondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Text(L10n.screenSpaceListDescription)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundStyle(.compound.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 32)
|
||||
.padding(.bottom, 24)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle()
|
||||
.fill(Color.compound.borderDisabled)
|
||||
.frame(height: 1 / UIScreen.main.scale)
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
context.send(viewAction: .showSettings)
|
||||
} label: {
|
||||
LoadableAvatarImage(url: context.viewState.userAvatarURL,
|
||||
name: context.viewState.userDisplayName,
|
||||
contentID: context.viewState.userID,
|
||||
avatarSize: .user(on: .home),
|
||||
mediaProvider: context.mediaProvider)
|
||||
.accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar)
|
||||
.compositingGroup()
|
||||
}
|
||||
.accessibilityLabel(L10n.commonSettings)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .principal) {
|
||||
// Hides the navigationTitle (which is set for the navigation stack label).
|
||||
Text("").accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct SpaceListScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = makeViewModel()
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
SpaceListScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
|
||||
static func makeViewModel(counterValue: Int = 0) -> SpaceListScreenViewModel {
|
||||
let clientProxy = ClientProxyMock(.init())
|
||||
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||
let viewModel = SpaceListScreenViewModel(userSession: userSession)
|
||||
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user