mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
My List is a list of titles to watch later, kept in my-list.json by
title key (pstr-core::mylist) and synced as one list/<key> register per
title, so an add or a removal on one device reaches every other. A list
file that will not parse is never written over: changing the list is
refused instead. It is a filter, a row on the library, a button on the
banner and the title page, and an entry in each tile's menu.
pstr-core::news reads node_first_seen against each share's first crawl,
so a newly added share does not badge its whole library. A title that
arrived in the last two weeks and was never played is New; a series
being watched counts the unwatched episodes that arrived after it was
last played ("3 new"). They fill the tile badge and the "New episodes"
and "Recently added" rows. shelves::home puts every row in one order
for all three apps, and a shelf tile of an airing show shows a
countdown ("Ep 8 · in 2 d").
The bridge gains TitleRecord.on_list and .badge, set_on_list, the
MyList filter, airing_countdown and SyncRecord.list_changed.
100 lines
4.1 KiB
Swift
100 lines
4.1 KiB
Swift
import Foundation
|
|
|
|
/// The settings that are this front end's alone.
|
|
///
|
|
/// Playback preferences — volume, languages, autoplay, auto-skip — deliberately
|
|
/// do *not* live here: they are `pstr_core::prefs`, reached over the bridge, so
|
|
/// there is one definition of them rather than one per front end. Keys and
|
|
/// defaults are Android's `SettingsStore`.
|
|
struct SettingsStore {
|
|
private let defaults = UserDefaults.standard
|
|
|
|
var wifiOnly: Bool {
|
|
get { bool(Key.wifiOnly, default: true) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.wifiOnly) }
|
|
}
|
|
|
|
var backgroundAudio: Bool {
|
|
get { bool(Key.backgroundAudio, default: true) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.backgroundAudio) }
|
|
}
|
|
|
|
/// Whether mpv may decode in hardware. Off is the way out for a file
|
|
/// VideoToolbox shows green or torn frames for; it costs battery, so it is
|
|
/// on unless the viewer turns it off. Read at each load, so a change applies
|
|
/// from the next file.
|
|
var hardwareDecoding: Bool {
|
|
get { bool(Key.hardwareDecoding, default: true) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.hardwareDecoding) }
|
|
}
|
|
|
|
/// The streaming cache's budget, in GiB. See `cacheBudgetChoices`.
|
|
var cacheBudgetGib: Int {
|
|
get { defaults.object(forKey: Key.cacheBudgetGib) as? Int ?? Self.defaultCacheBudgetGib }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.cacheBudgetGib) }
|
|
}
|
|
|
|
/// The desktop's range, in the steps a phone's storage is bought in.
|
|
static let cacheBudgetChoices = [1, 2, 4, 8, 16]
|
|
|
|
/// `DiskCacheConfig::DEFAULT_BUDGET_BYTES`, so an unset choice changes nothing.
|
|
static let defaultCacheBudgetGib = 4
|
|
|
|
static func gibToBytes(_ gib: Int) -> UInt64 { UInt64(gib) * 1024 * 1024 * 1024 }
|
|
|
|
/// How the library was last sorted; `.name` until the viewer chooses.
|
|
var librarySort: LibrarySort {
|
|
get {
|
|
let stored = defaults.string(forKey: Key.librarySort)
|
|
return Self.sorts.first { String(describing: $0) == stored } ?? .name
|
|
}
|
|
nonmutating set { defaults.set(String(describing: newValue), forKey: Key.librarySort) }
|
|
}
|
|
|
|
/// Which titles the library last showed.
|
|
var libraryFilter: LibraryFilter {
|
|
get {
|
|
let stored = defaults.string(forKey: Key.libraryFilter)
|
|
return Self.filters.first { String(describing: $0) == stored } ?? .all
|
|
}
|
|
nonmutating set { defaults.set(String(describing: newValue), forKey: Key.libraryFilter) }
|
|
}
|
|
|
|
/// The library's offer of posters and synopses was turned down.
|
|
var metadataOfferDismissed: Bool {
|
|
get { bool(Key.metadataOfferDismissed, default: false) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.metadataOfferDismissed) }
|
|
}
|
|
|
|
/// Whether the library folds each franchise into one tile.
|
|
var libraryGrouped: Bool {
|
|
get { bool(Key.libraryGrouped, default: true) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.libraryGrouped) }
|
|
}
|
|
|
|
/// Whether a background crawl may say that a show gained episodes.
|
|
var newEpisodeNotifications: Bool {
|
|
get { bool(Key.newEpisodeNotifications, default: true) }
|
|
nonmutating set { defaults.set(newValue, forKey: Key.newEpisodeNotifications) }
|
|
}
|
|
|
|
static let filters: [LibraryFilter] = [.all, .myList, .series, .films, .watching, .unwatched, .downloaded]
|
|
private static let sorts: [LibrarySort] = [.name, .recent, .added, .release, .rating, .popularity]
|
|
|
|
private func bool(_ key: String, default value: Bool) -> Bool {
|
|
defaults.object(forKey: key) as? Bool ?? value
|
|
}
|
|
|
|
private enum Key {
|
|
static let wifiOnly = "wifi_only"
|
|
static let backgroundAudio = "background_audio"
|
|
static let hardwareDecoding = "hardware_decoding"
|
|
static let cacheBudgetGib = "cache_budget_gib"
|
|
static let librarySort = "library_sort"
|
|
static let libraryFilter = "library_filter"
|
|
static let libraryGrouped = "library_grouped"
|
|
static let metadataOfferDismissed = "metadata_offer_dismissed"
|
|
static let newEpisodeNotifications = "new_episode_notifications"
|
|
}
|
|
}
|