tv-anarchy/Sources/TVAnarchyCore/WatchState.swift
Natalie 4a2ceb9781 feat(offline): inline star-to-keep and trash-to-cull on cache rows
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>
2026-06-30 00:12:41 -04:00

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)) }
}
}