mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
Yattee v2 rewrite
This commit is contained in:
224
Yattee/Services/Downloads/DownloadSettings.swift
Normal file
224
Yattee/Services/Downloads/DownloadSettings.swift
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// DownloadSettings.swift
|
||||
// Yattee
|
||||
//
|
||||
// Local-only settings for downloads sorting and grouping.
|
||||
// These settings are NOT synced to iCloud.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Sort options for completed downloads.
|
||||
enum DownloadSortOption: String, CaseIterable, Codable {
|
||||
case name
|
||||
case downloadDate
|
||||
case fileSize
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .name:
|
||||
return String(localized: "downloads.sort.name")
|
||||
case .downloadDate:
|
||||
return String(localized: "downloads.sort.downloadDate")
|
||||
case .fileSize:
|
||||
return String(localized: "downloads.sort.fileSize")
|
||||
}
|
||||
}
|
||||
|
||||
var systemImage: String {
|
||||
switch self {
|
||||
case .name:
|
||||
return "textformat"
|
||||
case .downloadDate:
|
||||
return "calendar"
|
||||
case .fileSize:
|
||||
return "internaldrive"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort direction.
|
||||
enum SortDirection: String, CaseIterable, Codable {
|
||||
case ascending
|
||||
case descending
|
||||
|
||||
var systemImage: String {
|
||||
switch self {
|
||||
case .ascending:
|
||||
return "arrow.up"
|
||||
case .descending:
|
||||
return "arrow.down"
|
||||
}
|
||||
}
|
||||
|
||||
mutating func toggle() {
|
||||
self = self == .ascending ? .descending : .ascending
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages download view settings locally (not synced to iCloud).
|
||||
@MainActor
|
||||
@Observable
|
||||
final class DownloadSettings {
|
||||
// MARK: - Storage Keys
|
||||
|
||||
private enum Keys {
|
||||
static let sortOption = "downloads.sortOption"
|
||||
static let sortDirection = "downloads.sortDirection"
|
||||
static let groupByChannel = "downloads.groupByChannel"
|
||||
static let allowCellularDownloads = "downloads.allowCellularDownloads"
|
||||
static let preferredQuality = "downloads.preferredQuality"
|
||||
static let includeSubtitlesInAutoDownload = "downloads.includeSubtitlesInAutoDownload"
|
||||
static let maxConcurrentDownloads = "downloads.maxConcurrentDownloads"
|
||||
}
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
// MARK: - Cached Values
|
||||
|
||||
private var _sortOption: DownloadSortOption?
|
||||
private var _sortDirection: SortDirection?
|
||||
private var _groupByChannel: Bool?
|
||||
private var _allowCellularDownloads: Bool?
|
||||
private var _preferredDownloadQuality: DownloadQuality?
|
||||
private var _includeSubtitlesInAutoDownload: Bool?
|
||||
private var _maxConcurrentDownloads: Int?
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The current sort option for completed downloads.
|
||||
var sortOption: DownloadSortOption {
|
||||
get {
|
||||
if let cached = _sortOption { return cached }
|
||||
guard let rawValue = defaults.string(forKey: Keys.sortOption),
|
||||
let option = DownloadSortOption(rawValue: rawValue) else {
|
||||
return .downloadDate
|
||||
}
|
||||
return option
|
||||
}
|
||||
set {
|
||||
_sortOption = newValue
|
||||
defaults.set(newValue.rawValue, forKey: Keys.sortOption)
|
||||
}
|
||||
}
|
||||
|
||||
/// The current sort direction.
|
||||
var sortDirection: SortDirection {
|
||||
get {
|
||||
if let cached = _sortDirection { return cached }
|
||||
guard let rawValue = defaults.string(forKey: Keys.sortDirection),
|
||||
let direction = SortDirection(rawValue: rawValue) else {
|
||||
return .descending
|
||||
}
|
||||
return direction
|
||||
}
|
||||
set {
|
||||
_sortDirection = newValue
|
||||
defaults.set(newValue.rawValue, forKey: Keys.sortDirection)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to group downloads by channel.
|
||||
var groupByChannel: Bool {
|
||||
get {
|
||||
if let cached = _groupByChannel { return cached }
|
||||
return defaults.bool(forKey: Keys.groupByChannel)
|
||||
}
|
||||
set {
|
||||
_groupByChannel = newValue
|
||||
defaults.set(newValue, forKey: Keys.groupByChannel)
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
/// Whether to allow downloads on cellular network. Default is false (WiFi only).
|
||||
var allowCellularDownloads: Bool {
|
||||
get {
|
||||
if let cached = _allowCellularDownloads { return cached }
|
||||
return defaults.bool(forKey: Keys.allowCellularDownloads)
|
||||
}
|
||||
set {
|
||||
_allowCellularDownloads = newValue
|
||||
defaults.set(newValue, forKey: Keys.allowCellularDownloads)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Preferred download quality. When set to anything other than .ask,
|
||||
/// downloads will start automatically without showing the stream selection sheet.
|
||||
var preferredDownloadQuality: DownloadQuality {
|
||||
get {
|
||||
if let cached = _preferredDownloadQuality { return cached }
|
||||
guard let rawValue = defaults.string(forKey: Keys.preferredQuality),
|
||||
let quality = DownloadQuality(rawValue: rawValue) else {
|
||||
return .hd1080p
|
||||
}
|
||||
return quality
|
||||
}
|
||||
set {
|
||||
_preferredDownloadQuality = newValue
|
||||
defaults.set(newValue.rawValue, forKey: Keys.preferredQuality)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to include subtitles when auto-downloading (non-Ask mode).
|
||||
/// Uses the preferred subtitle language from playback settings.
|
||||
var includeSubtitlesInAutoDownload: Bool {
|
||||
get {
|
||||
if let cached = _includeSubtitlesInAutoDownload { return cached }
|
||||
return defaults.bool(forKey: Keys.includeSubtitlesInAutoDownload)
|
||||
}
|
||||
set {
|
||||
_includeSubtitlesInAutoDownload = newValue
|
||||
defaults.set(newValue, forKey: Keys.includeSubtitlesInAutoDownload)
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum number of concurrent downloads. Default is 2.
|
||||
var maxConcurrentDownloads: Int {
|
||||
get {
|
||||
if let cached = _maxConcurrentDownloads { return cached }
|
||||
let value = defaults.integer(forKey: Keys.maxConcurrentDownloads)
|
||||
return value > 0 ? value : 2
|
||||
}
|
||||
set {
|
||||
_maxConcurrentDownloads = newValue
|
||||
defaults.set(newValue, forKey: Keys.maxConcurrentDownloads)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sorting
|
||||
|
||||
/// Sorts an array of downloads based on current settings.
|
||||
func sorted(_ downloads: [Download]) -> [Download] {
|
||||
let sorted = downloads.sorted { first, second in
|
||||
let comparison: Bool
|
||||
switch sortOption {
|
||||
case .name:
|
||||
comparison = first.title.localizedCaseInsensitiveCompare(second.title) == .orderedAscending
|
||||
case .downloadDate:
|
||||
let firstDate = first.completedAt ?? first.startedAt ?? Date.distantPast
|
||||
let secondDate = second.completedAt ?? second.startedAt ?? Date.distantPast
|
||||
comparison = firstDate < secondDate
|
||||
case .fileSize:
|
||||
comparison = first.totalBytes < second.totalBytes
|
||||
}
|
||||
|
||||
return sortDirection == .ascending ? comparison : !comparison
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
|
||||
/// Groups downloads by channel.
|
||||
func groupedByChannel(_ downloads: [Download]) -> [(channel: String, channelID: String, downloads: [Download])] {
|
||||
let grouped = Dictionary(grouping: downloads) { $0.channelID }
|
||||
|
||||
return grouped.map { (channelID, channelDownloads) in
|
||||
let channelName = channelDownloads.first?.channelName ?? channelID
|
||||
return (channel: channelName, channelID: channelID, downloads: sorted(channelDownloads))
|
||||
}
|
||||
.sorted { $0.channel.localizedCaseInsensitiveCompare($1.channel) == .orderedAscending }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user