Stemify iOS 1.1 and WebUI updates, repo restructure into desktop/ios/server/web

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Sonnet 5.5 committed 2026-10-24 17:08:51 -07:00
1 parent 894dd5a695
commit df5d847169
157 files changed
+3935 -742

No files matched your search

+9 -2
View File
@@ -76,7 +76,7 @@ final class CarLibrary: @unchecked Sendable {
default:
guard let (kind, key) = CarIds.split(id) else { return nil }
switch kind {
case "playlist": return await songs(id, try tracksOf(id), shuffleRow: true, showArtist: true)
case "playlist", "stemplaylist": return await songs(id, try tracksOf(id), shuffleRow: true, showArtist: true)
case "album": return await songs(id, try tracksOf(id), shuffleRow: false, showArtist: false)
case "artist": return try await artistPage(key, away)
case "albums": return try await albumsFor(away).filter { CarTree.letterOf($0.title) == key }.map { albumItem($0) }
@@ -95,7 +95,13 @@ final class CarLibrary: @unchecked Sendable {
} else {
shown = all
}
return shown.map { playlistItem($0) }
let stem = await MainActor.run { AppGraph.shared.stemPlaylists.lists.filter { !$0.items.isEmpty } }
let stemItems = stem.map { l in
CarItem(id: "stemplaylist:\(l.id)", title: l.title, subtitle: CarTree.countLabel(l.items.count, "song", "songs"),
art: artOr(l.thumb, l.title), playable: false, group: "Stem Playlists")
}
let plexItems = shown.map { playlistItem($0, group: stemItems.isEmpty ? nil : "Plex Playlists") }
return stemItems + plexItems
}
private func albumsTab(_ away: Bool) async throws -> [CarItem] {
@@ -168,6 +174,7 @@ final class CarLibrary: @unchecked Sendable {
guard let (kind, key) = CarIds.split(container) else { return [] }
switch kind {
case "playlist": all = try await plex.playlistTracks(key)
case "stemplaylist": all = await MainActor.run { AppGraph.shared.stemPlaylists.list(key)?.tracks ?? [] }
case "album": all = try await plex.albumTracks(key)
case "artist": all = away ? downloadedTracks().filter { $0.artistKey == key } : try await plex.artistTracks(key)
case "search": all = try await searchTracks(key, away)
+1 -1
View File
@@ -172,7 +172,7 @@ final class CarPlayScene: UIResponder, CPTemplateApplicationSceneDelegate, CPNow
guard let self else { return }
let label = self.g.nowPlaying.nextMix.label
self.g.nowPlaying.cycleMix()
self.alert("Mix: \(label)")
self.alert(label)
})
}
let shuffle = CPNowPlayingShuffleButton { [weak self] _ in self?.g.playback.toggleShuffle() }
+7 -6
View File
@@ -49,12 +49,13 @@ final class HomeReach: @unchecked Sendable {
let now = DispatchTime.now().uptimeNanoseconds
let (at, last) = stamp.with { (checkedAt, _last) }
if at != 0 && now - at < Self.ttl { return last }
let plex = settings.plexUrl.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + "/identity"
let stems = settings.stemUrl.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + "/api/info"
async let a = answers(plex)
async let b = answers(stems)
let (ra, rb) = await (a, b)
let ok = ra || rb
let urls = settings.servers.map { $0.base + ($0.kind == .plex ? "/identity" : "/System/Info/Public") }
+ [settings.stemUrl.trimmingCharacters(in: CharacterSet(charactersIn: "/")) + "/api/info"]
let ok = await withTaskGroup(of: Bool.self) { g in
for u in urls { g.addTask { await self.answers(u) } }
for await r in g where r { g.cancelAll(); return true }
return false
}
stamp.with { _last = ok; checkedAt = DispatchTime.now().uptimeNanoseconds }
await MainActor.run {
if home != ok {