ChadMusic.swift 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import CoreTransferable
  2. import Foundation
  3. import UniformTypeIdentifiers
  4. // MARK: - Custom UTTypes for drag-and-drop
  5. extension UTType {
  6. static let chadTrack = UTType(exportedAs: "com.mixboard.chad-track")
  7. static let chadAlbum = UTType(exportedAs: "com.mixboard.chad-album")
  8. }
  9. // MARK: - Chad Music API Response Models
  10. /// Navigation value for drilling into a category (e.g., artist "Pink Floyd" → show their albums).
  11. struct CategoryFilter: Hashable {
  12. let category: ChadCategoryType
  13. let value: String
  14. }
  15. /// A category entry from GET /api/cat/:category (e.g., album, artist, genre).
  16. /// Server returns: {"item": "Rock", "count": 42}
  17. struct ChadCategory: Codable, Identifiable, Hashable {
  18. let item: String
  19. let count: Int?
  20. // No server-side ID — use item name as identity
  21. var id: String { item }
  22. var name: String { item }
  23. }
  24. /// An album from the Chad Music API.
  25. /// Server keys: id, artist, year, album (title), publisher, country, genre, type, status, mb_id, track_count, total_duration, cover
  26. struct ChadAlbum: Codable, Identifiable, Hashable {
  27. let id: String
  28. let album: String? // server can return null for some releases
  29. let artist: String?
  30. let year: Int?
  31. let genre: String?
  32. let trackCount: Int?
  33. let cover: String?
  34. let publisher: String?
  35. let country: String?
  36. let type: String?
  37. let status: String?
  38. let totalDuration: Double?
  39. let originalDate: String?
  40. let mbId: String?
  41. /// Display-friendly title.
  42. var title: String { album ?? "Untitled" }
  43. enum CodingKeys: String, CodingKey {
  44. case id, album, artist, year, genre, cover, publisher, country
  45. case trackCount = "track_count"
  46. case totalDuration = "total_duration"
  47. case originalDate = "original_date"
  48. case mbId = "mb_id"
  49. case type, status
  50. }
  51. }
  52. /// A track from GET /api/album/:id/tracks.
  53. /// Server keys: id, artist, album_artist, album, year, no (track#), title, bit_rate, duration, url, cover
  54. struct ChadTrack: Codable, Identifiable, Hashable {
  55. let id: String
  56. let title: String
  57. let artist: String?
  58. let albumArtist: String?
  59. let album: String?
  60. let duration: Double?
  61. let no: Int? // server calls track number "no"
  62. let url: String // relative path for streaming
  63. let bitRate: Int?
  64. let year: Int?
  65. let cover: String?
  66. /// Track number for display.
  67. var trackNumber: Int? { no }
  68. enum CodingKeys: String, CodingKey {
  69. case id, title, artist, album, duration, no, url, year, cover
  70. case albumArtist = "album_artist"
  71. case bitRate = "bit_rate"
  72. }
  73. /// Human-readable duration string.
  74. var formattedDuration: String {
  75. guard let duration else { return "—" }
  76. let total = Int(duration)
  77. let minutes = total / 60
  78. let seconds = total % 60
  79. return String(format: "%d:%02d", minutes, seconds)
  80. }
  81. }
  82. extension ChadTrack: Transferable {
  83. static var transferRepresentation: some TransferRepresentation {
  84. CodableRepresentation(contentType: .chadTrack)
  85. }
  86. }
  87. extension ChadAlbum: Transferable {
  88. static var transferRepresentation: some TransferRepresentation {
  89. CodableRepresentation(contentType: .chadAlbum)
  90. }
  91. }
  92. /// The album-tracks endpoint returns a plain array of tracks (not a wrapper object).
  93. /// We decode as [ChadTrack] directly in the API client.
  94. /// Library statistics from GET /api/stats.
  95. /// Server keys: tracks, albums, artists, duration (formatted string), rescans
  96. struct ChadStats: Codable {
  97. let tracks: Int?
  98. let albums: Int?
  99. let artists: Int?
  100. let duration: String? // server returns pre-formatted string like "3d 14h 22m"
  101. }
  102. /// The browsable category types exposed by the API.
  103. enum ChadCategoryType: String, CaseIterable, Identifiable {
  104. case album
  105. case artist
  106. case genre
  107. case year
  108. case publisher
  109. case country
  110. case type
  111. case status
  112. var id: String { rawValue }
  113. var displayName: String {
  114. switch self {
  115. case .album: "Albums"
  116. case .artist: "Artists"
  117. case .genre: "Genres"
  118. case .year: "Years"
  119. case .publisher: "Publishers"
  120. case .country: "Countries"
  121. case .type: "Types"
  122. case .status: "Status"
  123. }
  124. }
  125. var icon: String {
  126. switch self {
  127. case .album: "square.stack"
  128. case .artist: "person.2"
  129. case .genre: "guitars"
  130. case .year: "calendar"
  131. case .publisher: "building.2"
  132. case .country: "globe"
  133. case .type: "tag"
  134. case .status: "checkmark.circle"
  135. }
  136. }
  137. }