import Foundation import SwiftData struct TimelineLocation: Hashable { let name: String let address: String let latitude: Double let longitude: Double var subtitle: String { let trimmedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines) return trimmedAddress == name ? "" : trimmedAddress } } @Model final class CelestiaTimelineEvent { var id: UUID var relativeTimeMs: Int64 var eventType: String // NOTE, PHOTO, MARKER, VOICE var textContent: String? var localFilePath: String? var voiceStartOffsetMs: Int64? var voiceEndOffsetMs: Int64? // Optional defaults keep existing SwiftData stores compatible. var locationName: String? = nil var locationAddress: String? = nil var latitude: Double? = nil var longitude: Double? = nil var createdAt: Date var session: CelestiaSession? var relativeTimeFormatted: String { let totalSeconds = Int(relativeTimeMs / 1000) let hours = totalSeconds / 3600 let minutes = (totalSeconds % 3600) / 60 let seconds = totalSeconds % 60 if hours > 0 { return String(format: "%d:%02d:%02d", hours, minutes, seconds) } return String(format: "%02d:%02d", minutes, seconds) } var isContinuationMarker: Bool { eventType == "MARKER" && textContent?.hasPrefix("续录时间:") == true } var location: TimelineLocation? { get { guard let latitude, let longitude else { return nil } let fallbackName = locationAddress? .trimmingCharacters(in: .whitespacesAndNewlines) let name = locationName? .trimmingCharacters(in: .whitespacesAndNewlines) let displayName = (name?.isEmpty == false ? name : fallbackName) .flatMap { $0.isEmpty ? nil : $0 } ?? "所在位置" return TimelineLocation( name: displayName, address: locationAddress ?? "", latitude: latitude, longitude: longitude ) } set { locationName = newValue?.name locationAddress = newValue?.address latitude = newValue?.latitude longitude = newValue?.longitude } } var eventIcon: String { if isContinuationMarker { return "record.circle" } switch eventType { case "PHOTO": return "camera.fill" case "NOTE": return "note.text" case "MARKER": return "exclamationmark.triangle.fill" case "VOICE": return "mic.fill" default: return "circle.fill" } } init(id: UUID = UUID(), relativeTimeMs: Int64, eventType: String) { self.id = id self.relativeTimeMs = relativeTimeMs self.eventType = eventType self.createdAt = Date() } }