Initial Commit
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// MAModels.swift
|
||||
// Mobile Music Assistant
|
||||
//
|
||||
// Created by Sven Hanold on 26.03.26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Player Models
|
||||
|
||||
struct MAPlayer: Codable, Identifiable, Hashable {
|
||||
let playerId: String
|
||||
let name: String
|
||||
let state: PlayerState
|
||||
let currentItem: MAQueueItem?
|
||||
let volume: Int
|
||||
let powered: Bool
|
||||
let available: Bool
|
||||
|
||||
var id: String { playerId }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case playerId = "player_id"
|
||||
case name
|
||||
case state
|
||||
case currentItem = "current_item"
|
||||
case volume = "volume_level"
|
||||
case powered
|
||||
case available
|
||||
}
|
||||
}
|
||||
|
||||
enum PlayerState: String, Codable {
|
||||
case playing
|
||||
case paused
|
||||
case idle
|
||||
case off
|
||||
}
|
||||
|
||||
// MARK: - Queue Models
|
||||
|
||||
struct MAQueueItem: Codable, Identifiable, Hashable {
|
||||
let queueItemId: String
|
||||
let mediaItem: MAMediaItem?
|
||||
let name: String
|
||||
let duration: Int?
|
||||
let streamDetails: MAStreamDetails?
|
||||
|
||||
var id: String { queueItemId }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case queueItemId = "queue_item_id"
|
||||
case mediaItem = "media_item"
|
||||
case name
|
||||
case duration
|
||||
case streamDetails = "stream_details"
|
||||
}
|
||||
}
|
||||
|
||||
struct MAStreamDetails: Codable, Hashable {
|
||||
let providerId: String
|
||||
let itemId: String
|
||||
let audioFormat: MAAudioFormat?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case providerId = "provider"
|
||||
case itemId = "item_id"
|
||||
case audioFormat = "audio_format"
|
||||
}
|
||||
}
|
||||
|
||||
struct MAAudioFormat: Codable, Hashable {
|
||||
let contentType: String
|
||||
let sampleRate: Int?
|
||||
let bitDepth: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case contentType = "content_type"
|
||||
case sampleRate = "sample_rate"
|
||||
case bitDepth = "bit_depth"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Media Models
|
||||
|
||||
struct MAMediaItem: Codable, Identifiable, Hashable {
|
||||
let uri: String
|
||||
let name: String
|
||||
let mediaType: MediaType
|
||||
let artists: [MAArtist]?
|
||||
let album: MAAlbum?
|
||||
let imageUrl: String?
|
||||
let duration: Int?
|
||||
|
||||
var id: String { uri }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case uri
|
||||
case name
|
||||
case mediaType = "media_type"
|
||||
case artists
|
||||
case album
|
||||
case imageUrl = "image"
|
||||
case duration
|
||||
}
|
||||
}
|
||||
|
||||
enum MediaType: String, Codable {
|
||||
case track
|
||||
case album
|
||||
case artist
|
||||
case playlist
|
||||
case radio
|
||||
}
|
||||
|
||||
struct MAArtist: Codable, Identifiable, Hashable {
|
||||
let uri: String
|
||||
let name: String
|
||||
let imageUrl: String?
|
||||
let sortName: String?
|
||||
let musicbrainzId: String?
|
||||
|
||||
var id: String { uri }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case uri
|
||||
case name
|
||||
case imageUrl = "image"
|
||||
case sortName = "sort_name"
|
||||
case musicbrainzId = "musicbrainz_id"
|
||||
}
|
||||
}
|
||||
|
||||
struct MAAlbum: Codable, Identifiable, Hashable {
|
||||
let uri: String
|
||||
let name: String
|
||||
let artists: [MAArtist]?
|
||||
let imageUrl: String?
|
||||
let year: Int?
|
||||
|
||||
var id: String { uri }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case uri
|
||||
case name
|
||||
case artists
|
||||
case imageUrl = "image"
|
||||
case year
|
||||
}
|
||||
}
|
||||
|
||||
struct MAPlaylist: Codable, Identifiable, Hashable {
|
||||
let uri: String
|
||||
let name: String
|
||||
let owner: String?
|
||||
let imageUrl: String?
|
||||
let isEditable: Bool
|
||||
|
||||
var id: String { uri }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case uri
|
||||
case name
|
||||
case owner
|
||||
case imageUrl = "image"
|
||||
case isEditable = "is_editable"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - WebSocket Protocol Models
|
||||
|
||||
struct MACommand: Encodable {
|
||||
let messageId: String
|
||||
let command: String
|
||||
let args: [String: AnyCodable]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case messageId = "message_id"
|
||||
case command
|
||||
case args
|
||||
}
|
||||
}
|
||||
|
||||
struct MAResponse: Decodable {
|
||||
let messageId: String?
|
||||
let result: AnyCodable?
|
||||
let errorCode: String?
|
||||
let errorMessage: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case messageId = "message_id"
|
||||
case result
|
||||
case errorCode = "error_code"
|
||||
case errorMessage = "error"
|
||||
}
|
||||
}
|
||||
|
||||
struct MAEvent: Decodable {
|
||||
let event: String
|
||||
let data: AnyCodable?
|
||||
}
|
||||
|
||||
// MARK: - Auth Models
|
||||
|
||||
struct MALoginRequest: Encodable {
|
||||
let username: String
|
||||
let password: String
|
||||
}
|
||||
|
||||
struct MALoginResponse: Decodable {
|
||||
let accessToken: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case accessToken = "access_token"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AnyCodable Helper
|
||||
|
||||
/// Helper to handle dynamic JSON values
|
||||
struct AnyCodable: Codable, Hashable {
|
||||
let value: Any
|
||||
|
||||
init(_ value: Any) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
if container.decodeNil() {
|
||||
value = NSNull()
|
||||
} else if let bool = try? container.decode(Bool.self) {
|
||||
value = bool
|
||||
} else if let int = try? container.decode(Int.self) {
|
||||
value = int
|
||||
} else if let double = try? container.decode(Double.self) {
|
||||
value = double
|
||||
} else if let string = try? container.decode(String.self) {
|
||||
value = string
|
||||
} else if let array = try? container.decode([AnyCodable].self) {
|
||||
value = array.map { $0.value }
|
||||
} else if let dict = try? container.decode([String: AnyCodable].self) {
|
||||
value = dict.mapValues { $0.value }
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Unable to decode value"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
switch value {
|
||||
case is NSNull:
|
||||
try container.encodeNil()
|
||||
case let bool as Bool:
|
||||
try container.encode(bool)
|
||||
case let int as Int:
|
||||
try container.encode(int)
|
||||
case let double as Double:
|
||||
try container.encode(double)
|
||||
case let string as String:
|
||||
try container.encode(string)
|
||||
case let array as [Any]:
|
||||
try container.encode(array.map { AnyCodable($0) })
|
||||
case let dict as [String: Any]:
|
||||
try container.encode(dict.mapValues { AnyCodable($0) })
|
||||
default:
|
||||
throw EncodingError.invalidValue(
|
||||
value,
|
||||
EncodingError.Context(
|
||||
codingPath: container.codingPath,
|
||||
debugDescription: "Unable to encode value"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool {
|
||||
// Simple comparison - extend as needed
|
||||
return String(describing: lhs.value) == String(describing: rhs.value)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(String(describing: value))
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyCodable {
|
||||
/// Decode the wrapped value to a specific type
|
||||
func decode<T: Decodable>(as type: T.Type) throws -> T {
|
||||
let data = try JSONEncoder().encode(self)
|
||||
return try JSONDecoder().decode(T.self, from: data)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user