| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- 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 {
- if event.isContinuationMarker {
- return "record.circle"
- }
- 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 {
- if event.isContinuationMarker {
- return "续录"
- }
- 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)
- }
|