import SwiftUI // MARK: - ChronoFeedRow /// A card component for the chronological event feed. /// Displays a timeline event with time badge, type icon, content, /// and a clean, wireframe bordered layout suitable for business scenarios. struct ChronoFeedRow: View { let event: CelestiaTimelineEvent var onTap: (() -> Void)? var body: some View { Button { HapticManager.trigger(.tapFeedback) onTap?() } label: { HStack(spacing: 12) { // Left: Time Badge (Minimalist outline) timeBadge // Center: Content eventContent Spacer(minLength: 4) // Right: Chevron Image(systemName: "chevron.right") .font(.system(size: 9, weight: .light)) .foregroundStyle(Color.secondary.opacity(0.5)) } .padding(12) .background(Color.cardBackground.opacity(0.15)) .businessBorder(cornerRadius: 8) } .buttonStyle(.plain) } // MARK: - Time Badge private var timeBadge: some View { Text(event.relativeTimeFormatted) .font(.system(size: 11, weight: .regular, design: .monospaced)) .foregroundStyle(Color.primary) .padding(.horizontal, 8) .padding(.vertical, 4) .businessBorder(cornerRadius: 4) } // MARK: - Event Content private var eventContent: some View { HStack(spacing: 10) { // Event icon (Clean monochrome outline) Image(systemName: eventIconName) .font(.system(size: 12, weight: .light)) .foregroundStyle(Color.primary) .frame(width: 24, height: 24) .businessBorder(cornerRadius: 12) VStack(alignment: .leading, spacing: 3) { // Type label Text(typeLabel) .font(.system(size: 9, weight: .medium)) .foregroundStyle(Color.secondary) // Content switch event.eventType { case "PHOTO": photoView case "NOTE", "MARKER": if let text = event.textContent { Text(text) .font(.system(size: 13, weight: .regular)) .foregroundStyle(Color.primary.opacity(0.9)) .lineLimit(2) .multilineTextAlignment(.leading) } default: Text("事件") .font(.system(size: 13)) .foregroundStyle(Color.secondary) } } } } // MARK: - Photo Placeholder private var photoPlaceholder: some View { HStack(spacing: 6) { Image(systemName: "photo") .font(.system(size: 10, weight: .light)) .foregroundStyle(Color.secondary) Text("照片片段") .font(.system(size: 11)) .foregroundStyle(Color.secondary) } .padding(.horizontal, 8) .padding(.vertical, 4) .businessBorder(cornerRadius: 4) } @ViewBuilder private var photoView: some View { if let path = event.localFilePath, let uiImage = UIImage(contentsOfFile: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(path).path) { HStack(spacing: 8) { Image(uiImage: uiImage) .resizable() .scaledToFill() .frame(width: 60, height: 45) .clipShape(RoundedRectangle(cornerRadius: 4)) .overlay( RoundedRectangle(cornerRadius: 4) .stroke(Color.lineBorder, lineWidth: 1) ) Text("照片记录") .font(.system(size: 11)) .foregroundStyle(Color.secondary) } } else { photoPlaceholder } } // MARK: - Computed Properties private var eventIconName: String { switch event.eventType { case "PHOTO": return "camera" case "NOTE": return "doc.text" case "MARKER": return "flag" case "VOICE": return "waveform" default: return "circle" } } private var typeLabel: String { switch event.eventType { case "PHOTO": return "照片" case "NOTE": return "笔记" case "MARKER": return "标记" case "VOICE": return "音频" default: return "事件" } } } // MARK: - Preview #Preview { VStack(spacing: 12) { let photoEvent: CelestiaTimelineEvent = { let e = CelestiaTimelineEvent(relativeTimeMs: 32000, eventType: "PHOTO") return e }() let noteEvent: CelestiaTimelineEvent = { let e = CelestiaTimelineEvent(relativeTimeMs: 65000, eventType: "NOTE") e.textContent = "看到了非常壮观的星空,银河清晰可见" return e }() let markerEvent: CelestiaTimelineEvent = { let e = CelestiaTimelineEvent(relativeTimeMs: 120000, eventType: "MARKER") e.textContent = "重要时刻标记" return e }() ChronoFeedRow(event: photoEvent) ChronoFeedRow(event: noteEvent) ChronoFeedRow(event: markerEvent) } .padding(16) .background(Color.spaceBlack) }