import SwiftUI import SwiftData // MARK: - HomeView /// The main launch screen of CelestiaTrace. /// Features a minimalist, line-drawn aesthetic with a central record button, /// bordered session cards, and a subtle technical grid background. struct HomeView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \CelestiaSession.startTime, order: .reverse) private var sessions: [CelestiaSession] @State private var navigateToRecording = false @State private var activeSession: CelestiaSession? @State private var selectedSessionForDetail: CelestiaSession? @State private var detector = RecordingEnvironmentDetector() @State private var dismissedWarnings: Set = [] /// Callback to switch to the History tab from the parent TabView. var onSwitchToHistory: (() -> Void)? var body: some View { NavigationStack { ZStack { // Dynamic clean background Color.spaceBlack .ignoresSafeArea() // Subtle technical grid background businessGrid // Main content VStack(spacing: 0) { topBar .padding(.top, 16) if let firstWarning = detector.activeWarnings.first(where: { !dismissedWarnings.contains($0) }) { DiscreetWarningBanner(warning: firstWarning) { withAnimation { _ = dismissedWarnings.insert(firstWarning) } } .padding(.top, 12) } Spacer() recordSection Spacer() recentSessionsSection .padding(.bottom, 16) historyLink .padding(.bottom, 24) } .padding(.horizontal, 24) } .navigationBarHidden(true) .onAppear { detector.checkEnvironment() } .fullScreenCover(isPresented: $navigateToRecording) { if let session = activeSession { ActiveRecordingView(session: session) { selectedSessionForDetail = session } } } .navigationDestination(item: $selectedSessionForDetail) { session in SessionDetailView(session: session) } } } // MARK: - Top Bar private var topBar: some View { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 4) { Text("星痕现场记录") .font(.system(size: 11, weight: .bold, design: .monospaced)) .foregroundStyle(Color.primary.opacity(0.8)) .tracking(3) Text(currentDateFormatted) .font(.system(size: 13, weight: .regular)) .foregroundStyle(Color.secondary) } Spacer() // Status indicator HStack(spacing: 6) { Circle() .fill(Color.primary.opacity(0.7)) .frame(width: 5, height: 5) Text("就绪") .font(.system(size: 9, weight: .semibold, design: .monospaced)) .foregroundStyle(Color.secondary) } .padding(.horizontal, 8) .padding(.vertical, 4) .businessBorder(cornerRadius: 4) } } // MARK: - Record Section private var recordSection: some View { VStack(spacing: 24) { PulsingRecordButton { createSessionAndNavigate() } VStack(spacing: 6) { Text("新建现场记录") .font(.system(size: 16, weight: .medium)) .foregroundStyle(Color.primary) Text("点击开启全新现场录音") .font(.system(size: 9, weight: .medium, design: .monospaced)) .foregroundStyle(Color.secondary.opacity(0.7)) .tracking(2) } } } // MARK: - Recent Sessions private var recentSessionsSection: some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: "list.dash") .font(.system(size: 12)) .foregroundStyle(Color.secondary) Text("最近记录") .font(.system(size: 13, weight: .semibold)) .foregroundStyle(Color.primary.opacity(0.8)) } if sessions.isEmpty { emptyRecentState } else { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 12) { ForEach(sessions.prefix(3)) { session in Button { selectedSessionForDetail = session } label: { RecentSessionCard(session: session) } .buttonStyle(.plain) } } .padding(.vertical, 2) } } } } private var emptyRecentState: some View { HStack { Spacer() VStack(spacing: 8) { Image(systemName: "plus.square.dashed") .font(.system(size: 20)) .foregroundStyle(Color.secondary.opacity(0.4)) Text("暂无记录") .font(.system(size: 12)) .foregroundStyle(Color.secondary.opacity(0.5)) } .padding(.vertical, 32) Spacer() } .background(Color.cardBackground.opacity(0.3)) .businessBorder(cornerRadius: 8) } // MARK: - History Link private var historyLink: some View { Button { onSwitchToHistory?() } label: { HStack(spacing: 6) { Text("查看历史列表") .font(.system(size: 13, weight: .regular)) Image(systemName: "chevron.right") .font(.system(size: 10)) } .foregroundStyle(Color.primary.opacity(0.7)) .padding(.horizontal, 14) .padding(.vertical, 8) .businessBorder(cornerRadius: 6) } } // MARK: - Technical Grid Background private var businessGrid: some View { Canvas { context, size in let step: CGFloat = 40 let cols = Int(size.width / step) let rows = Int(size.height / step) for col in 0...cols { let x = CGFloat(col) * step var path = Path() path.move(to: CGPoint(x: x, y: 0)) path.addLine(to: CGPoint(x: x, y: size.height)) context.stroke(path, with: .color(Color.primary.opacity(0.015)), lineWidth: 0.5) } for row in 0...rows { let y = CGFloat(row) * step var path = Path() path.move(to: CGPoint(x: 0, y: y)) path.addLine(to: CGPoint(x: size.width, y: y)) context.stroke(path, with: .color(Color.primary.opacity(0.015)), lineWidth: 0.5) } } .ignoresSafeArea() .allowsHitTesting(false) } // MARK: - Actions private func createSessionAndNavigate() { let formatter = DateFormatter() formatter.dateFormat = "yyyy.MM.dd HH:mm" let title = "现场记录 · \(formatter.string(from: Date()))" let session = CelestiaSession(title: title) modelContext.insert(session) try? modelContext.save() activeSession = session HapticManager.trigger(.recordStart) navigateToRecording = true } // MARK: - Helpers private var currentDateFormatted: String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "zh_Hans") formatter.dateFormat = "yyyy年M月d日 EEEE" return formatter.string(from: Date()) } } // MARK: - Recent Session Card private struct RecentSessionCard: View { let session: CelestiaSession var body: some View { VStack(alignment: .leading, spacing: 8) { Text(session.title) .font(.system(size: 13, weight: .medium)) .foregroundStyle(Color.primary) .lineLimit(1) HStack(spacing: 4) { Image(systemName: "waveform") .font(.system(size: 9)) .foregroundStyle(Color.secondary) Text(session.durationFormatted) .font(.system(size: 11, weight: .regular, design: .monospaced)) .foregroundStyle(Color.secondary) } HStack(spacing: 12) { HStack(spacing: 3) { Image(systemName: "camera") .font(.system(size: 9)) Text("\(session.photoCount)") .font(.system(size: 10, design: .monospaced)) } .foregroundStyle(Color.secondary) HStack(spacing: 3) { Image(systemName: "doc.text") .font(.system(size: 9)) Text("\(session.noteCount)") .font(.system(size: 10, design: .monospaced)) } .foregroundStyle(Color.secondary) } .padding(.top, 4) } .padding(12) .frame(width: 150, alignment: .leading) .background(Color.cardBackground.opacity(0.3)) .businessBorder(cornerRadius: 8) } } // MARK: - Preview #Preview { HomeView() .modelContainer(for: CelestiaSession.self, inMemory: true) }