CelestiaTimelineEvent.swift 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import Foundation
  2. import SwiftData
  3. struct TimelineLocation: Hashable {
  4. let name: String
  5. let address: String
  6. let latitude: Double
  7. let longitude: Double
  8. var subtitle: String {
  9. let trimmedAddress = address.trimmingCharacters(in: .whitespacesAndNewlines)
  10. return trimmedAddress == name ? "" : trimmedAddress
  11. }
  12. }
  13. @Model
  14. final class CelestiaTimelineEvent {
  15. var id: UUID
  16. var relativeTimeMs: Int64
  17. var eventType: String // NOTE, PHOTO, MARKER, VOICE
  18. var textContent: String?
  19. var localFilePath: String?
  20. var voiceStartOffsetMs: Int64?
  21. var voiceEndOffsetMs: Int64?
  22. // Optional defaults keep existing SwiftData stores compatible.
  23. var locationName: String? = nil
  24. var locationAddress: String? = nil
  25. var latitude: Double? = nil
  26. var longitude: Double? = nil
  27. var createdAt: Date
  28. var session: CelestiaSession?
  29. var relativeTimeFormatted: String {
  30. let totalSeconds = Int(relativeTimeMs / 1000)
  31. let hours = totalSeconds / 3600
  32. let minutes = (totalSeconds % 3600) / 60
  33. let seconds = totalSeconds % 60
  34. if hours > 0 {
  35. return String(format: "%d:%02d:%02d", hours, minutes, seconds)
  36. }
  37. return String(format: "%02d:%02d", minutes, seconds)
  38. }
  39. var isContinuationMarker: Bool {
  40. eventType == "MARKER" && textContent?.hasPrefix("续录时间:") == true
  41. }
  42. var location: TimelineLocation? {
  43. get {
  44. guard let latitude, let longitude else { return nil }
  45. let fallbackName = locationAddress?
  46. .trimmingCharacters(in: .whitespacesAndNewlines)
  47. let name = locationName?
  48. .trimmingCharacters(in: .whitespacesAndNewlines)
  49. let displayName = (name?.isEmpty == false ? name : fallbackName)
  50. .flatMap { $0.isEmpty ? nil : $0 }
  51. ?? "所在位置"
  52. return TimelineLocation(
  53. name: displayName,
  54. address: locationAddress ?? "",
  55. latitude: latitude,
  56. longitude: longitude
  57. )
  58. }
  59. set {
  60. locationName = newValue?.name
  61. locationAddress = newValue?.address
  62. latitude = newValue?.latitude
  63. longitude = newValue?.longitude
  64. }
  65. }
  66. var eventIcon: String {
  67. if isContinuationMarker {
  68. return "record.circle"
  69. }
  70. switch eventType {
  71. case "PHOTO": return "camera.fill"
  72. case "NOTE": return "note.text"
  73. case "MARKER": return "exclamationmark.triangle.fill"
  74. case "VOICE": return "mic.fill"
  75. default: return "circle.fill"
  76. }
  77. }
  78. init(id: UUID = UUID(), relativeTimeMs: Int64, eventType: String) {
  79. self.id = id
  80. self.relativeTimeMs = relativeTimeMs
  81. self.eventType = eventType
  82. self.createdAt = Date()
  83. }
  84. }