Andromida/Andromida/App/Views/Today/Components/TodayEmptyStateView.swift

175 lines
6.7 KiB
Swift

import SwiftUI
import Bedrock
struct TodayEmptyStateView: View {
@Bindable var store: RitualStore
@Bindable var categoryStore: CategoryStore
@State private var showingPresetLibrary = false
@State private var showingCreateRitual = false
var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.large) {
SectionHeaderView(
title: String(localized: "No Active Rituals"),
subtitle: String(localized: "Start building better habits")
)
VStack(spacing: Design.Spacing.large) {
// Icon
SymbolIcon("sparkles", size: .hero, color: AppAccent.primary)
.padding(.top, Design.Spacing.large)
Text(String(localized: "Rituals help you build consistent habits through focused, time-bound journeys."))
.typography(.subheading)
.foregroundStyle(AppTextColors.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, Design.Spacing.large)
// Quick start goal cards
quickStartSection
// Divider
HStack {
Rectangle()
.fill(AppBorder.subtle)
.frame(height: 1)
Text(String(localized: "or"))
.typography(.caption)
.foregroundStyle(AppTextColors.tertiary)
Rectangle()
.fill(AppBorder.subtle)
.frame(height: 1)
}
.padding(.horizontal, Design.Spacing.medium)
// Action buttons
VStack(spacing: Design.Spacing.medium) {
Button {
showingCreateRitual = true
} label: {
HStack {
Image(systemName: "plus.circle.fill")
Text(String(localized: "Create Custom Ritual"))
}
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
Button {
showingPresetLibrary = true
} label: {
HStack {
Image(systemName: "sparkles.rectangle.stack")
Text(String(localized: "Browse All Presets"))
}
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
}
.padding(.horizontal, Design.Spacing.medium)
// Past rituals hint
if !store.pastRituals.isEmpty {
Text(String(localized: "You can also restart a past ritual from the Rituals tab."))
.typography(.caption)
.foregroundStyle(AppTextColors.tertiary)
.multilineTextAlignment(.center)
.padding(.top, Design.Spacing.small)
}
}
.padding(Design.Spacing.large)
.frame(maxWidth: .infinity)
.background(AppSurface.card)
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
}
.sheet(isPresented: $showingPresetLibrary) {
PresetLibrarySheet(store: store)
}
.sheet(isPresented: $showingCreateRitual) {
RitualEditSheet(store: store, categoryStore: categoryStore, ritual: nil)
}
}
// MARK: - Quick Start Section
@State private var quickStartButtonHeight: CGFloat?
private var quickStartSection: some View {
VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text(String(localized: "Quick Start"))
.typography(.captionEmphasis)
.foregroundStyle(AppTextColors.tertiary)
.padding(.horizontal, Design.Spacing.medium)
LazyVGrid(
columns: [
GridItem(.flexible(), spacing: Design.Spacing.small),
GridItem(.flexible(), spacing: Design.Spacing.small)
],
spacing: Design.Spacing.small
) {
ForEach(OnboardingGoal.allCases) { goal in
QuickStartButton(goal: goal, uniformHeight: quickStartButtonHeight) {
startQuickRitual(for: goal)
}
.onGeometryChange(for: CGFloat.self) { proxy in
proxy.size.height
} action: { height in
if let current = quickStartButtonHeight {
if height > current {
quickStartButtonHeight = height
}
} else {
quickStartButtonHeight = height
}
}
}
}
.padding(.horizontal, Design.Spacing.small)
}
}
private func startQuickRitual(for goal: OnboardingGoal) {
// Get the first morning preset for this goal, or any preset
if let preset = OnboardingPresetRecommender.recommendedPreset(for: goal, time: .morning) {
store.createRitual(from: preset)
}
}
}
/// A compact button for quick-starting a ritual from a goal category.
private struct QuickStartButton: View {
let goal: OnboardingGoal
let uniformHeight: CGFloat?
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: Design.Spacing.small) {
SymbolIcon(goal.symbolName, size: .row, color: AppAccent.primary)
Text(goal.displayName)
.typography(.subheading)
.foregroundStyle(AppTextColors.primary)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
Spacer()
}
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.frame(height: uniformHeight)
.background(AppSurface.tertiary)
.clipShape(.rect(cornerRadius: Design.CornerRadius.medium))
}
.buttonStyle(.plain)
.accessibilityLabel(String(localized: "Start \(goal.displayName) ritual"))
}
}
#Preview {
TodayEmptyStateView(store: RitualStore.preview, categoryStore: CategoryStore.preview)
.padding()
.background(AppSurface.primary)
}