Files
letro-ios/ElementX/Sources/Screens/HomeScreen/View/HomeScreenEmptyStateView.swift
Mauro 6160c44d67 Update copyright holding and dates (#4640)
* Update copyright holding and dates

* compound IDE Macros updated

* update copyright

* update copyrights done

* update templates and README
2025-10-21 14:34:56 +02:00

155 lines
5.9 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-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 Compound
import SwiftUI
/// The view shown when the user isn't part of any rooms.
struct HomeScreenEmptyStateView: View {
let context: HomeScreenViewModel.Context
var body: some View {
VStack(spacing: 6) {
Text(L10n.screenRoomlistEmptyTitle)
.font(.compound.bodyLG)
.foregroundColor(.compound.textSecondary)
.multilineTextAlignment(.center)
Text(L10n.screenRoomlistEmptyMessage)
.font(.compound.bodyLG)
.foregroundColor(.compound.textSecondary)
.multilineTextAlignment(.center)
.padding(.bottom, 12)
Button { context.send(viewAction: .startChat) } label: {
Label(L10n.actionStartChat, icon: \.compose)
.font(.compound.bodyLGSemibold)
.foregroundColor(.compound.textOnSolidPrimary)
.padding(.vertical, 6)
.padding(.horizontal, 22)
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
}
.padding(16)
}
}
/// A custom layout for the empty state which will show it centrally with the
/// session verification banner and invites button stacked at the top.
struct HomeScreenEmptyStateLayout: Layout {
/// The vertical spacing between views in the layout.
var spacing: CGFloat = 8
/// The minimum height of the layout. This should be the height of the scroll view.
var minHeight: CGFloat = 0
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
// We keep the proposed width and replace the height with the minimum specified,
// or the total height of the subviews if it exceeds the minimum height.
let width = proposal.width ?? .greatestFiniteMagnitude
var height: CGFloat = spacing * CGFloat(max(0, subviews.count - 1))
for subview in subviews {
let size = subview.sizeThatFits(proposal)
height += size.height
}
return CGSize(width: width, height: max(minHeight, height))
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let mainView = subviews.first { $0.priority > 0 }
let topViews = subviews.filter { $0 != mainView }
var y: CGFloat = bounds.minY
// Place all the top views in a vertical stack, centering horizontally.
for view in topViews {
let size = view.sizeThatFits(proposal)
let x = (bounds.width - size.width) / 2
view.place(at: CGPoint(x: x, y: y), proposal: proposal)
y += size.height + spacing
}
// Place the main view in the center if there is space, otherwise add it to the stack.
guard let mainView else { return }
let mainViewSize = mainView.sizeThatFits(proposal)
if (y + mainViewSize.height / 2) < bounds.height / 2 {
let center = CGPoint(x: bounds.midX, y: bounds.midY)
mainView.place(at: center, anchor: .center, proposal: proposal)
} else {
let x = (bounds.width - mainViewSize.width) / 2
mainView.place(at: CGPoint(x: x, y: y), proposal: proposal)
}
}
}
// MARK: - Previews
struct HomeScreenEmptyStateView_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
HomeScreenEmptyStateView(context: viewModel.context)
.previewDisplayName("View")
GeometryReader { geometry in
ScrollView {
HomeScreenEmptyStateLayout(minHeight: geometry.size.height) {
banner
HomeScreenEmptyStateView(context: viewModel.context)
.layoutPriority(1)
}
}
}
.previewDisplayName("Normal Layout")
GeometryReader { geometry in
ScrollView {
HomeScreenEmptyStateLayout(minHeight: geometry.size.height) {
banner
banner
banner
HomeScreenEmptyStateView(context: viewModel.context)
.layoutPriority(1)
}
}
}
.previewDisplayName("Constrained layout")
}
// MARK: -
static var banner: some View {
Text("This is a title that is very long")
.font(.compound.headingXLBold)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding()
.background {
RoundedRectangle(cornerRadius: 20)
.fill(Color.compound.bgSubtleSecondary)
}
.padding()
}
static let viewModel = {
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@user:example.com",
roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded([])))))))
return HomeScreenViewModel(userSession: userSession,
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
appSettings: ServiceLocator.shared.settings,
analyticsService: ServiceLocator.shared.analytics,
notificationManager: NotificationManagerMock(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}()
}