iOS 1.0 RC 2: Plex streams under upload caps, shuffle and album radio stations, Recently Added list, photo covers, crossfade

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-11-06 08:40:37 -08:00
1 parent c0e33b5e3e
commit 2fb8040388
17 files changed
+412 -74

No files matched your search

+25 -11
View File
@@ -48,9 +48,9 @@ struct LibraryHome: View {
}
case "Recently Added":
if !added.isEmpty {
SectionTitle(title: "Recently Added")
SectionTitle(title: "Recently Added", action: "See All") { ui.push(.RecentlyAdded) }
ForEach(Array(added.enumerated()), id: \.element.ratingKey) { i, t in
TrackRow(track: t, menu: ui.menu.track(t)) { g.playback.playTracks(added, start: i) }
TrackRow(track: t, menu: ui.menu.track(t)) { playRecent(from: t, at: i) }
.listRow(inset: MARGIN + 60, line: i < added.count - 1)
}
}
@@ -82,8 +82,7 @@ struct LibraryHome: View {
SectionTitle(title: "Stations")
let stations: [(String, String, () async throws -> Void)] = [
("dot.radiowaves.left.and.right", "Library Radio", {
let list = try await g.plex.allTracksShuffled()
if !list.isEmpty { g.playback.playTracks(list) }
try await g.playback.startSongRadio()
}),
("square.stack", "Random Album Radio", { try await g.playback.startAlbumRadio() }),
]
@@ -152,6 +151,16 @@ struct LibraryHome: View {
}
}
/// Plays the whole Recently Added list (up to 500 songs) from [t], not just the 10 shown here.
private func playRecent(from t: Track, at i: Int) {
let shown = added
Task {
let all = (try? await g.plex.recentlyAddedTracks(count: 500)) ?? []
if let j = all.firstIndex(where: { $0.ratingKey == t.ratingKey }) { g.playback.playTracks(all, start: j) }
else { g.playback.playTracks(shown, start: i) }
}
}
/// Starts a station: its songs play in the order given (albums stay whole).
private func play(_ start: @escaping () async throws -> Void) {
Task {
@@ -542,6 +551,8 @@ struct AlbumsScreen: View {
// ---- songs (paged) --------------------------------------------------------------------------
struct SongsScreen: View {
/// Recently Added: every song, newest first, with no sort menu.
var recent = false
@Environment(Ui.self) private var ui
@Environment(\.palette) private var p
@State private var tracks: [Track] = []
@@ -555,18 +566,21 @@ struct SongsScreen: View {
private let sort = SortState("songs", SONG_SORTS)
private var g: AppGraph { .shared }
/// Shuffles the whole library, not just the pages loaded so far.
/// Shuffles the whole library, not just the pages loaded so far: random songs from the server,
/// topped up as they play. Recently Added shuffles what it has loaded.
private func shuffleAll() {
if recent { g.playback.playTracks(tracks, shuffle: true); return }
Task {
do { g.playback.playTracks(try await g.plex.allTracksShuffled(), shuffle: true) }
catch { g.playback.report("Couldn't load songs: \(error.localizedDescription)") }
do { try await g.playback.startSongRadio() }
catch { ui.overlays.hud("Couldn't shuffle songs", "exclamationmark.triangle") }
}
}
private var order: String { recent ? "addedAt:desc" : sort.param }
var body: some View {
Page(title: "Songs") {
Page(title: recent ? "Recently Added" : "Songs") {
if !tracks.isEmpty {
if total > 0, !StemsOnly.on, !sort.option.random {
if total > 0, !StemsOnly.on, recent || !sort.option.random {
Text(songs(total)).font(.subheadline).foregroundStyle(p.label2).padding(.leading, MARGIN).padding(.bottom, 12)
}
PlayShuffle(play: { g.playback.playTracks(Array(tracks.prefix(1000))) },
@@ -584,7 +598,7 @@ struct SongsScreen: View {
if loading { LoadingRow() }
if let error { ErrorRow(message: error) { loadMore() } }
}
.toolbar { ToolbarItem(placement: .topBarTrailing) { SortMenu(sort: sort) } }
.toolbar { if !recent { ToolbarItem(placement: .topBarTrailing) { SortMenu(sort: sort) } } }
.task { if tracks.isEmpty { loadMore() } }
.onChange(of: "\(sort.token)|\(StemsOnly.on)|\(g.settings.libraryKey)") {
gen += 1; tracks = []; total = -1; offset = 0; error = nil; loading = false
@@ -595,7 +609,7 @@ struct SongsScreen: View {
private func loadMore() {
if loading || (total >= 0 && offset >= total) { return }
loading = true
let mine = gen, order = sort.param, random = sort.option.random
let mine = gen, order = self.order, random = !recent && sort.option.random
Task {
do {
// Random pages would repeat songs, so it's one shuffled batch.