Eine Sekunde heisst nicht „Weiterschauen"
Die Schwelle aus den Einstellungen wirkt serverseitig, also sofort überall: `/home/continue` liest die gespeicherte Spalte. Eine Stelle blieb trotzdem widersprüchlich — die **Beschriftung**. Web und Apps entschieden „Weiterschauen" gegen „Abspielen" selbst, allein daran, ob eine Stelle über null steht. Nach einer Sekunde stand der Titel damit nicht mehr in der Reihe, hiess aber weiter „Weiterschauen", trug einen Fortschrittsbalken unter der Kachel und bot „Aus Weiterschauen entfernen" an. Der Server hatte diese Frage längst beantwortet. `ItemStateResponse` bringt sie jetzt mit; `MediaItem.isResumable` und `.resumeProgress` sind die einzigen Stellen, die sie noch stellen. Im Simulator nachgeprüft: nach einer Sekunde heisst der Knopf „Abspielen", „Von vorn" fehlt, und der Balken unter der Kachel ist weg. Kennt ein Server das Feld nicht — Jellyfin, oder ein älterer Flaro —, gilt wie früher die blosse Stelle. **Und das Profilbild steht in den Apps.** `FlaroAccount` führt es mit, `/auth/me` bringt es bei jedem Start frisch, und der Buchstabenkreis tritt zurück, sobald eines da ist. Jellyfin bleibt beim Buchstaben — dort gibt es kein Profilbild, und das ist keine Lücke, sondern der Stand der Dinge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
579a49b73b
commit
e30158b84c
@@ -443,17 +443,26 @@ public struct FlaroAccount: Sendable, Equatable {
|
||||
public let userName: String
|
||||
public let isAdministrator: Bool
|
||||
|
||||
/// Das eigene Bild, als Schlüssel in die Bildablage — `nil`: der Buchstabe.
|
||||
///
|
||||
/// **Mit Vorgabewert, damit Jellyfin unberührt bleibt.** Dort gibt es
|
||||
/// kein Profilbild, und ein Pflichtfeld hier zwänge jeden Aufrufer, sich
|
||||
/// dazu zu äussern.
|
||||
public let avatarImage: String?
|
||||
|
||||
public init(
|
||||
baseURL: URL,
|
||||
serverName: String,
|
||||
userID: String,
|
||||
userName: String,
|
||||
isAdministrator: Bool
|
||||
isAdministrator: Bool,
|
||||
avatarImage: String? = nil
|
||||
) {
|
||||
self.baseURL = baseURL
|
||||
self.serverName = serverName
|
||||
self.userID = userID
|
||||
self.userName = userName
|
||||
self.isAdministrator = isAdministrator
|
||||
self.avatarImage = avatarImage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ enum Wire {
|
||||
let id: String
|
||||
let userName: String
|
||||
let isAdministrator: Bool
|
||||
/// Das Profilbild, als Schlüssel in die Bildablage des Servers.
|
||||
let avatarImage: String?
|
||||
}
|
||||
|
||||
struct Library: Decodable, Sendable {
|
||||
@@ -78,6 +80,12 @@ enum Wire {
|
||||
let rating: Int?
|
||||
let dismissed: Bool
|
||||
let lastPlayedAt: Date?
|
||||
/// Ob der Server den Titel in „Weiterschauen" führt.
|
||||
///
|
||||
/// **Nicht dasselbe wie „Stelle über null".** Seit es eine
|
||||
/// einstellbare Untergrenze gibt, sind das zwei Fragen: eine Sekunde
|
||||
/// wird gemerkt, zählt aber nicht als angefangen.
|
||||
let inProgress: Bool?
|
||||
}
|
||||
|
||||
struct Movie: Decodable, Sendable {
|
||||
|
||||
@@ -20,6 +20,7 @@ extension Wire.Movie {
|
||||
resumeMilliseconds: state.map { Int($0.positionSeconds * 1000) },
|
||||
lastPlayedAt: state?.lastPlayedAt,
|
||||
isFavourite: state?.favourite ?? false,
|
||||
isInProgress: state?.inProgress,
|
||||
availableFrom: publishAt,
|
||||
versions: [Wire.fassung(id: id, files: files)]
|
||||
)
|
||||
@@ -69,6 +70,7 @@ extension Wire.Episode {
|
||||
resumeMilliseconds: state.map { Int($0.positionSeconds * 1000) },
|
||||
lastPlayedAt: state?.lastPlayedAt,
|
||||
isFavourite: state?.favourite ?? false,
|
||||
isInProgress: state?.inProgress,
|
||||
availableFrom: publishAt,
|
||||
versions: [Wire.fassung(id: id, files: files)]
|
||||
)
|
||||
@@ -104,6 +106,7 @@ extension Wire.Started {
|
||||
resumeMilliseconds: Int(state.positionSeconds * 1000),
|
||||
lastPlayedAt: state.lastPlayedAt,
|
||||
isFavourite: state.favourite,
|
||||
isInProgress: state.inProgress,
|
||||
availableFrom: publishAt
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,8 @@ public enum FlaroSignIn {
|
||||
serverName: gruss.serverName,
|
||||
userID: antwort.user.id,
|
||||
userName: antwort.user.userName,
|
||||
isAdministrator: antwort.user.isAdministrator), client)
|
||||
isAdministrator: antwort.user.isAdministrator,
|
||||
avatarImage: antwort.user.avatarImage), client)
|
||||
}
|
||||
|
||||
/// Nimmt ein gespeichertes Token wieder auf.
|
||||
@@ -111,7 +112,8 @@ public enum FlaroSignIn {
|
||||
serverName: gruss?.serverName ?? baseURL.host() ?? "Flaro",
|
||||
userID: wer.id,
|
||||
userName: wer.userName,
|
||||
isAdministrator: wer.isAdministrator), client)
|
||||
isAdministrator: wer.isAdministrator,
|
||||
avatarImage: wer.avatarImage), client)
|
||||
}
|
||||
|
||||
// MARK: - Der Weg über einen Code
|
||||
@@ -179,7 +181,8 @@ public enum FlaroSignIn {
|
||||
serverName: gruss?.serverName ?? baseURL.host() ?? "Flaro",
|
||||
userID: anmeldung.user.id,
|
||||
userName: anmeldung.user.userName,
|
||||
isAdministrator: anmeldung.user.isAdministrator)), client)
|
||||
isAdministrator: anmeldung.user.isAdministrator,
|
||||
avatarImage: anmeldung.user.avatarImage)), client)
|
||||
case "expired":
|
||||
return (.expired, nil)
|
||||
default:
|
||||
|
||||
@@ -71,6 +71,18 @@ public struct MediaItem: Sendable, Equatable, Identifiable, Codable {
|
||||
/// eine Fähigkeit, die es nur auf einem gibt.
|
||||
public let isFavourite: Bool
|
||||
|
||||
/// Ob der Server den Titel als angefangen führt.
|
||||
///
|
||||
/// **Die Entscheidung gehört dem Server, nicht der Beschriftung.** „Hat
|
||||
/// eine Stelle über null" und „ist angefangen" waren dasselbe, bis es eine
|
||||
/// einstellbare Untergrenze gab. Seitdem heisst ein Titel, in den jemand
|
||||
/// eine Sekunde hineingeschaut hat, nicht mehr „Weiterschauen" — er steht
|
||||
/// ja auch nicht mehr in der Reihe.
|
||||
///
|
||||
/// `nil` heisst: der Server sagt nichts dazu (Jellyfin, oder ein älterer
|
||||
/// Flaro). Dann gilt wie früher die blosse Stelle.
|
||||
public let isInProgress: Bool?
|
||||
|
||||
/// Ab wann es den Titel gibt — `nil` heisst: jetzt.
|
||||
///
|
||||
/// **Ein geplanter Titel steht in der Mediathek, läuft aber nicht.** Das
|
||||
@@ -125,6 +137,7 @@ public struct MediaItem: Sendable, Equatable, Identifiable, Codable {
|
||||
lastPlayedAt: Date? = nil,
|
||||
addedAt: Date? = nil,
|
||||
isFavourite: Bool = false,
|
||||
isInProgress: Bool? = nil,
|
||||
availableFrom: Date? = nil,
|
||||
episodeCount: Int? = nil,
|
||||
playedEpisodeCount: Int? = nil,
|
||||
@@ -164,6 +177,7 @@ public struct MediaItem: Sendable, Equatable, Identifiable, Codable {
|
||||
self.lastPlayedAt = lastPlayedAt
|
||||
self.addedAt = addedAt
|
||||
self.isFavourite = isFavourite
|
||||
self.isInProgress = isInProgress
|
||||
self.availableFrom = availableFrom
|
||||
self.episodeCount = episodeCount
|
||||
self.playedEpisodeCount = playedEpisodeCount
|
||||
@@ -235,6 +249,20 @@ extension MediaItem {
|
||||
|
||||
public var isWatched: Bool { (playCount ?? 0) > 0 }
|
||||
|
||||
/// Ob die Beschriftung „Weiterschauen" heissen soll.
|
||||
///
|
||||
/// Eine Stelle, die beide Fälle kennt: was der Server sagt, und was ohne
|
||||
/// seine Auskunft gilt. Stünde der Zweifel an jedem Knopf einzeln, hiesse
|
||||
/// derselbe Titel auf der Startseite anders als auf seiner Seite.
|
||||
public var isResumable: Bool { isInProgress ?? (watchProgress != nil) }
|
||||
|
||||
/// Der Balken unter der Kachel — nur, wenn der Titel wirklich angefangen ist.
|
||||
///
|
||||
/// **Sonst sagt die Kachel etwas anderes als der Knopf.** Ein Strich unter
|
||||
/// dem Plakat heisst „du bist mittendrin". Für eine Sekunde, in die jemand
|
||||
/// hineingeschaut hat, ist das keine Auskunft, sondern ein Fussel.
|
||||
public var resumeProgress: Double? { isResumable ? watchProgress : nil }
|
||||
|
||||
/// Ob der Titel erst noch kommt.
|
||||
///
|
||||
/// Gefragt wird an einer Stelle, beantwortet an einer: sonst steht an
|
||||
|
||||
Reference in New Issue
Block a user