| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- import Foundation
- /// Configuration for how the playlist view displays tracks.
- /// Persisted to UserDefaults so it remembers across sessions.
- /// Use `PlaylistViewConfig.shared` for a single app-wide instance.
- final class PlaylistViewConfig: ObservableObject {
- static let shared = PlaylistViewConfig()
- // MARK: - Column Visibility
- /// All possible metadata columns.
- enum Column: String, CaseIterable, Identifiable, Codable {
- case artwork = "Artwork"
- case trackNumber = "#"
- case title = "Title"
- case artist = "Artist"
- case album = "Album"
- case genre = "Genre"
- case bpm = "BPM"
- case key = "Key"
- case duration = "Duration"
- case format = "Format"
- case sampleRate = "Sample Rate"
- case bitDepth = "Bit Depth"
- case fileSize = "File Size"
- case rating = "Rating"
- case dateAdded = "Date Added"
- case playCount = "Play Count"
- case crossfade = "Crossfade"
- case gain = "Gain"
- var id: String { rawValue }
- /// Default width hint for each column.
- var defaultWidth: CGFloat {
- switch self {
- case .artwork: return 50
- case .trackNumber: return 30
- case .title: return 220
- case .artist: return 150
- case .album: return 150
- case .genre: return 100
- case .bpm: return 55
- case .key: return 55
- case .duration: return 55
- case .format: return 50
- case .sampleRate: return 70
- case .bitDepth: return 50
- case .fileSize: return 70
- case .rating: return 80
- case .dateAdded: return 90
- case .playCount: return 50
- case .crossfade: return 120
- case .gain: return 120
- }
- }
- }
- // MARK: - Artwork Size
- enum ArtworkSize: String, CaseIterable, Identifiable, Codable {
- case small = "Small" // 32pt
- case medium = "Medium" // 48pt
- case large = "Large" // 64pt
- var id: String { rawValue }
- var points: CGFloat {
- switch self {
- case .small: return 32
- case .medium: return 48
- case .large: return 64
- }
- }
- }
- // MARK: - Published State
- @Published var visibleColumns: [Column] {
- didSet { save() }
- }
- @Published var showArtwork: Bool {
- didSet { save() }
- }
- @Published var artworkSize: ArtworkSize {
- didSet { save() }
- }
- @Published var cursorFollowsPlayback: Bool {
- didSet { save() }
- }
- @Published var playbackFollowsCursor: Bool {
- didSet { save() }
- }
- // MARK: - Defaults
- static let defaultColumns: [Column] = [
- .artwork, .trackNumber, .title, .artist, .bpm, .key, .duration, .crossfade
- ]
- // MARK: - Init
- init() {
- let defaults = UserDefaults.standard
- if let data = defaults.data(forKey: "playlistVisibleColumns"),
- let decoded = try? JSONDecoder().decode([Column].self, from: data) {
- visibleColumns = decoded
- } else {
- visibleColumns = Self.defaultColumns
- }
- showArtwork = defaults.object(forKey: "playlistShowArtwork") as? Bool ?? true
- cursorFollowsPlayback = defaults.object(forKey: "playlistCursorFollowsPlayback") as? Bool ?? true
- playbackFollowsCursor = defaults.object(forKey: "playlistPlaybackFollowsCursor") as? Bool ?? false
- if let raw = defaults.string(forKey: "playlistArtworkSize"),
- let size = ArtworkSize(rawValue: raw) {
- artworkSize = size
- } else {
- artworkSize = .medium
- }
- }
- // MARK: - Persistence
- private func save() {
- let defaults = UserDefaults.standard
- if let data = try? JSONEncoder().encode(visibleColumns) {
- defaults.set(data, forKey: "playlistVisibleColumns")
- }
- defaults.set(showArtwork, forKey: "playlistShowArtwork")
- defaults.set(cursorFollowsPlayback, forKey: "playlistCursorFollowsPlayback")
- defaults.set(playbackFollowsCursor, forKey: "playlistPlaybackFollowsCursor")
- defaults.set(artworkSize.rawValue, forKey: "playlistArtworkSize")
- }
- // MARK: - Helpers
- func isColumnVisible(_ column: Column) -> Bool {
- visibleColumns.contains(column)
- }
- func toggleColumn(_ column: Column) {
- if let idx = visibleColumns.firstIndex(of: column) {
- visibleColumns.remove(at: idx)
- } else {
- visibleColumns.append(column)
- }
- }
- func resetToDefaults() {
- visibleColumns = Self.defaultColumns
- showArtwork = true
- artworkSize = .medium
- cursorFollowsPlayback = true
- playbackFollowsCursor = false
- }
- }
|