Navigation: Glas, oben oder seitlich, und mehr Reihen auf der Startseite
Drei Dinge aus dem letzten Durchgang. **Die Startseite hatte zu wenig anzubieten.** Es gab eine Reihe „Zuletzt hinzugefügt" für alles zusammen. Jetzt gibt es eine je Mediathek — „Neu in Filme" und „Neu in Serien" sind zwei verschiedene Fragen, und wer nach einem Film sucht, blättert in einer gemeinsamen Reihe an Folgen vorbei. Dazu Favoriten, die es bei Jellyfin ohnehin gibt. Die drei Abfragen laufen nebeneinander; nur die Mediatheken werden streng geholt, weil sie die Probe darauf sind, ob der Server überhaupt antwortet. **Die Seitenleiste ist jetzt echtes Glas**, nicht das alte Material: im ausfahrbaren Blatt über `glassEffect`, in der geteilten Ansicht auf dem iPad vom System selbst — dort lag mein eigener Hintergrund darüber und machte ihn stumpf. Die zum Inhalt zeigende Kante ist gerundet, damit das Blatt sichtbar über der Seite liegt statt daneben zu stehen. **Die Navigation kann nach oben.** Eine Einstellung unter Erscheinungsbild, seitlich oder oben. Oben stehen alle Einträge gleichzeitig da, als Glaskapseln in einer waagerecht scrollenden Reihe — die Zahl der Mediatheken kommt vom Server, und zusammengequetschte Beschriftungen wären die schlechtere Antwort als eine Reihe, die weiterläuft. Angesehen auf iPhone und iPad: beide Formen, das Glas in beiden Leisten, die neuen Reihen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5763d866bf
commit
5cebfed36b
@@ -54,40 +54,66 @@ public struct JellyfinBackend: MediaBackend {
|
||||
// wird aus einzelnen Abfragen zusammengesetzt. Das ist kein Mangel:
|
||||
// so steht hier, was die Reihen bedeuten, statt dass es der Server
|
||||
// entscheidet.
|
||||
// „Zuletzt hinzugefügt" wird streng geholt: scheitert es, ist der
|
||||
// Server nicht erreichbar, und dann gehört eine Fehlermeldung hin.
|
||||
let latest = try await latestItems(limit: 20)
|
||||
//
|
||||
// Die Mediatheken werden streng geholt: sie sind die Probe darauf, ob
|
||||
// der Server überhaupt antwortet. Alles Weitere ist nachsichtig —
|
||||
// fällt „Als Nächstes" aus, soll deshalb nicht die ganze Startseite
|
||||
// leer bleiben.
|
||||
let libraries = try await libraries().filter(\.isSupported)
|
||||
|
||||
// Die beiden anderen nachsichtig: fällt „Als Nächstes" aus, soll
|
||||
// deshalb nicht die ganze Startseite leer bleiben.
|
||||
let resuming = try? await rowItems(path: "/Users/\(account.userID)/Items/Resume", query: [
|
||||
async let resuming = try? rowItems(path: "/Users/\(account.userID)/Items/Resume", query: [
|
||||
URLQueryItem(name: "Limit", value: "20"),
|
||||
URLQueryItem(name: "MediaTypes", value: "Video"),
|
||||
URLQueryItem(name: "Fields", value: Self.fields),
|
||||
])
|
||||
let nextUp = try? await rowItems(path: "/Shows/NextUp", query: [
|
||||
async let nextUp = try? rowItems(path: "/Shows/NextUp", query: [
|
||||
URLQueryItem(name: "userId", value: account.userID),
|
||||
URLQueryItem(name: "Limit", value: "20"),
|
||||
URLQueryItem(name: "Fields", value: Self.fields),
|
||||
])
|
||||
async let favourites = try? rowItems(path: "/Users/\(account.userID)/Items", query: [
|
||||
URLQueryItem(name: "Filters", value: "IsFavorite"),
|
||||
URLQueryItem(name: "Recursive", value: "true"),
|
||||
URLQueryItem(name: "IncludeItemTypes", value: "Movie,Series"),
|
||||
URLQueryItem(name: "Limit", value: "20"),
|
||||
URLQueryItem(name: "Fields", value: Self.fields),
|
||||
])
|
||||
|
||||
let rows = [
|
||||
var rows = [
|
||||
MediaRowGroup(
|
||||
id: "resume",
|
||||
title: String(localized: "row.continueWatching", bundle: .module),
|
||||
items: resuming ?? []
|
||||
items: await resuming ?? []
|
||||
),
|
||||
MediaRowGroup(
|
||||
id: "nextup",
|
||||
title: String(localized: "row.nextUp", bundle: .module),
|
||||
items: nextUp ?? []
|
||||
),
|
||||
MediaRowGroup(
|
||||
id: "latest",
|
||||
title: String(localized: "row.recentlyAdded", bundle: .module),
|
||||
items: latest
|
||||
items: await nextUp ?? []
|
||||
),
|
||||
]
|
||||
|
||||
// Je Mediathek eine eigene Reihe. „Neu in Filme" und „Neu in Serien"
|
||||
// sind zwei verschiedene Fragen — in einer gemeinsamen Reihe steht am
|
||||
// Ende beides durcheinander, und wer nach einem Film sucht, blättert
|
||||
// an Folgen vorbei.
|
||||
for library in libraries {
|
||||
let items = try? await latestItems(limit: 20, inLibrary: library.id)
|
||||
rows.append(MediaRowGroup(
|
||||
id: "latest.\(library.id)",
|
||||
title: String(
|
||||
localized: "row.latestIn \(library.title)",
|
||||
bundle: .module
|
||||
),
|
||||
items: items ?? []
|
||||
))
|
||||
}
|
||||
|
||||
rows.append(MediaRowGroup(
|
||||
id: "favourites",
|
||||
title: String(localized: "row.favourites", bundle: .module),
|
||||
items: await favourites ?? []
|
||||
))
|
||||
|
||||
// Leere Reihen wären auf der Startseite nur Überschriften ohne Inhalt.
|
||||
return rows.filter { !$0.items.isEmpty }
|
||||
}
|
||||
@@ -330,15 +356,21 @@ public struct JellyfinBackend: MediaBackend {
|
||||
///
|
||||
/// Die Antwort ist hier ausnahmsweise ein nacktes Feld, kein Objekt mit
|
||||
/// `Items` darin.
|
||||
private func latestItems(limit: Int) async throws(MediaBackendError) -> [MediaItem] {
|
||||
private func latestItems(
|
||||
limit: Int,
|
||||
inLibrary libraryID: String? = nil
|
||||
) async throws(MediaBackendError) -> [MediaItem] {
|
||||
var query = [
|
||||
URLQueryItem(name: "Limit", value: String(limit)),
|
||||
URLQueryItem(name: "Fields", value: Self.fields),
|
||||
// Neue Folgen erscheinen als ihre Serie statt als zwölf
|
||||
// Einzelkacheln, die alle dasselbe Plakat tragen.
|
||||
URLQueryItem(name: "GroupItems", value: "true"),
|
||||
]
|
||||
if let libraryID { query.append(URLQueryItem(name: "ParentId", value: libraryID)) }
|
||||
|
||||
let items = try await client.send(
|
||||
JellyfinRequest(path: "/Users/\(account.userID)/Items/Latest", query: [
|
||||
URLQueryItem(name: "Limit", value: String(limit)),
|
||||
URLQueryItem(name: "Fields", value: Self.fields),
|
||||
// Neue Folgen erscheinen als ihre Serie statt als zwölf
|
||||
// Einzelkacheln, die alle dasselbe Plakat tragen.
|
||||
URLQueryItem(name: "GroupItems", value: "true"),
|
||||
]),
|
||||
JellyfinRequest(path: "/Users/\(account.userID)/Items/Latest", query: query),
|
||||
as: [JellyfinItem].self
|
||||
)
|
||||
return items.map { $0.asMediaItem(imageBase: images) }
|
||||
|
||||
Reference in New Issue
Block a user