Files
yattee/Yattee/Services/Sources/SourcesSettings.swift
2026-02-08 18:33:56 +01:00

155 lines
4.7 KiB
Swift

//
// SourcesSettings.swift
// Yattee
//
// Local-only settings for sources sorting and grouping.
// These settings are NOT synced to iCloud.
//
import Foundation
/// Sort options for sources.
enum SourcesSortOption: String, CaseIterable, Codable {
case name
case type
case dateAdded
var displayName: String {
switch self {
case .name:
return String(localized: "sources.sort.name")
case .type:
return String(localized: "sources.sort.type")
case .dateAdded:
return String(localized: "sources.sort.dateAdded")
}
}
var systemImage: String {
switch self {
case .name:
return "textformat"
case .type:
return "square.grid.2x2"
case .dateAdded:
return "calendar"
}
}
}
/// Manages sources view settings locally (not synced to iCloud).
@MainActor
@Observable
final class SourcesSettings {
// MARK: - Storage Keys
private enum Keys {
static let sortOption = "sources.sortOption"
static let sortDirection = "sources.sortDirection"
static let groupByType = "sources.groupByType"
}
// MARK: - Storage
private let defaults = UserDefaults.standard
// MARK: - Cached Values
private var _sortOption: SourcesSortOption?
private var _sortDirection: SortDirection?
private var _groupByType: Bool?
// MARK: - Properties
/// The current sort option for sources.
var sortOption: SourcesSortOption {
get {
if let cached = _sortOption { return cached }
guard let rawValue = defaults.string(forKey: Keys.sortOption),
let option = SourcesSortOption(rawValue: rawValue) else {
return .name
}
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 .ascending
}
return direction
}
set {
_sortDirection = newValue
defaults.set(newValue.rawValue, forKey: Keys.sortDirection)
}
}
/// Whether to group sources by type.
var groupByType: Bool {
get {
if let cached = _groupByType { return cached }
// Default to true if not set
if defaults.object(forKey: Keys.groupByType) == nil {
return true
}
return defaults.bool(forKey: Keys.groupByType)
}
set {
_groupByType = newValue
defaults.set(newValue, forKey: Keys.groupByType)
}
}
// MARK: - Sorting
/// Sorts an array of instances based on current settings.
func sorted(_ instances: [Instance]) -> [Instance] {
instances.sorted { first, second in
let comparison: Bool
switch sortOption {
case .name:
comparison = first.displayName.localizedCaseInsensitiveCompare(second.displayName) == .orderedAscending
case .type:
comparison = first.type.displayName.localizedCaseInsensitiveCompare(second.type.displayName) == .orderedAscending
case .dateAdded:
comparison = first.dateAdded < second.dateAdded
}
return sortDirection == .ascending ? comparison : !comparison
}
}
/// Sorts an array of media sources based on current settings.
func sorted(_ sources: [MediaSource]) -> [MediaSource] {
sources.sorted { first, second in
let comparison: Bool
switch sortOption {
case .name:
comparison = first.name.localizedCaseInsensitiveCompare(second.name) == .orderedAscending
case .type:
comparison = first.type.displayName.localizedCaseInsensitiveCompare(second.type.displayName) == .orderedAscending
case .dateAdded:
comparison = first.dateAdded < second.dateAdded
}
return sortDirection == .ascending ? comparison : !comparison
}
}
/// Returns available sort options based on current grouping state.
/// When grouped by type, the "type" sort option is hidden.
var availableSortOptions: [SourcesSortOption] {
if groupByType {
return [.name, .dateAdded]
}
return SourcesSortOption.allCases
}
}