mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Add an empty state on the home screen. (#1450)
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
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, systemImage: "square.and.pencil")
|
||||
.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(where: { $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 {
|
||||
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 = MockUserSession(clientProxy: MockClientProxy(userID: "@user:example.com",
|
||||
roomSummaryProvider: MockRoomSummaryProvider(state: .loaded([]))),
|
||||
mediaProvider: MockMediaProvider())
|
||||
|
||||
return HomeScreenViewModel(userSession: userSession,
|
||||
attributedStringBuilder: AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL),
|
||||
selectedRoomPublisher: CurrentValueSubject<String?, Never>(nil).asCurrentValuePublisher(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user