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