iOS: stem playlist refresh and covers, download queue page, reorderable Library, whole-library radio

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Sonnet 5.5 committed 2026-10-28 15:49:09 -07:00
1 parent 7c8dfb54ca
commit a4c870251c
18 files changed
+514 -30

No files matched your search

+29 -1
View File
@@ -15,10 +15,12 @@ struct StemPlaylist: Codable, Hashable, Identifiable {
var items: [StemPlaylistItem] = []
var created = Date()
var updated = Date()
/// Cover picked by the user; nil follows the first song.
var artThumb: String?
var tracks: [Track] { items.map(\.track) }
var mixes: [[String: StemSetting]] { items.map(\.mix) }
var thumb: String? { items.lazy.compactMap(\.track.thumb).first }
var thumb: String? { artThumb ?? items.lazy.compactMap(\.track.thumb).first }
var durationMs: Int64 { items.reduce(0) { $0 + $1.track.durationMs } }
}
@@ -51,6 +53,32 @@ final class StemPlaylists {
edit(id) { $0.items += tracks.map { StemPlaylistItem(track: $0, mix: MixPresets.normalized(mix)) } }
}
func setArt(_ id: String, _ thumb: String?) { edit(id) { $0.artThumb = thumb } }
/// Re-reads every song from the library so renamed, retagged or re-added songs match again.
/// Keeps each item's uid and mix, and leaves songs the library can't find as they are.
/// Returns true when anything changed.
@discardableResult
func refresh(albumTracks: (String) async throws -> [Track]) async -> Bool {
var fresh: [String: [Track]] = [:]
for key in Set(lists.flatMap { $0.items.compactMap(\.track.albumKey) }) {
if let t = try? await albumTracks(key) { fresh[key] = t }
}
var changed = false
for li in lists.indices {
for ii in lists[li].items.indices {
let old = lists[li].items[ii].track
guard let key = old.albumKey, let pool = fresh[key] else { continue }
guard var new = pool.first(where: { $0.ratingKey == old.ratingKey })
?? pool.first(where: { $0.title == old.title && $0.artist == old.artist }) else { continue }
if new.stemKey == nil { new.stemKey = old.stemKey }
if new != old { lists[li].items[ii].track = new; changed = true }
}
}
if changed { save() }
return changed
}
func setItems(_ id: String, _ items: [StemPlaylistItem]) { edit(id) { $0.items = items } }
func remove(_ id: String, item: String) { edit(id) { $0.items.removeAll { $0.uid == item } } }
func setMix(_ id: String, item: String, _ mix: [String: StemSetting]) {