Surface the existing pin (keep-from-cull) and per-file delete actions as visible inline buttons on each offline cache row instead of context-menu-only: a star toggles protection from auto-cull (and restore-if-missing), a trash culls that file early. Aligns wording/icons to the star metaphor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
2 KiB
Swift
46 lines
2 KiB
Swift
import Foundation
|
|
|
|
/// How much of a show (or one episode) has been watched, derived from the set of
|
|
/// episode paths that carry a "play" finish marker in the unified watch history.
|
|
/// (resume positions are separate and drive mid-ep resume + Netflix bars; a resume
|
|
/// alone does not mark an episode "started" for badges/next, to ignore fly-bys).
|
|
/// We surface three coarse states for list indicators.
|
|
public enum WatchState: String, Sendable, Equatable {
|
|
case unwatched // nothing started
|
|
case inProgress // some episodes started, not all
|
|
case watched // every episode started
|
|
|
|
public var icon: String {
|
|
switch self {
|
|
case .unwatched: "circle"
|
|
case .inProgress: "circle.lefthalf.filled"
|
|
case .watched: "checkmark.circle.fill"
|
|
}
|
|
}
|
|
}
|
|
|
|
public extension CachedShow {
|
|
/// True if this episode has been finished (has a "play" marker in watch history).
|
|
/// (Mid-ep resume positions are tracked separately for resume targets and progress bars.)
|
|
func isWatched(_ episode: CachedEpisode, watchedPaths: Set<String>) -> Bool {
|
|
watchedPaths.contains(MediaPaths.toRemote(episode.path))
|
|
}
|
|
|
|
/// The show's overall watch state from the watched-path set.
|
|
func watchState(watchedPaths: Set<String>) -> WatchState {
|
|
let eps = orderedEpisodes
|
|
guard !eps.isEmpty else { return .unwatched }
|
|
let started = eps.reduce(into: 0) { n, e in
|
|
if watchedPaths.contains(MediaPaths.toRemote(e.path)) { n += 1 }
|
|
}
|
|
if started == 0 { return .unwatched }
|
|
return started == eps.count ? .watched : .inProgress
|
|
}
|
|
|
|
/// The episode to play for "watch next": the first un-started episode in play
|
|
/// order (specials/movies last). `nil` when everything is watched — the caller
|
|
/// then offers "rewatch from the start".
|
|
func nextUnwatched(watchedPaths: Set<String>) -> CachedEpisode? {
|
|
orderedEpisodes.first { !watchedPaths.contains(MediaPaths.toRemote($0.path)) }
|
|
}
|
|
}
|