Files
yattee/Yattee/Models/PlayerControls/GlobalLayoutSettings.swift
Arkadiusz Fal 4770bbe3b9 Remove dead code found by Periphery on all platforms
Delete declarations that Periphery reports unused on every platform that
compiles them (iOS, macOS, tvOS), including nine views used only by their
own previews. Platform-gated code still used on another platform, device-
only MPV render paths, migration models, CloudKit record names, App
Intents metadata and unused parameters are kept.
2026-10-08 23:57:56 +02:00

484 lines
16 KiB
Swift

//
// GlobalLayoutSettings.swift
// Yattee
//
// Global settings that apply to all control buttons in the player layout.
//
import Foundation
import SwiftUI
/// Theme options for player controls appearance.
enum ControlsTheme: String, Codable, Hashable, Sendable, CaseIterable {
/// Follow system appearance
case system
/// Always use light appearance
case light
/// Always use dark appearance
case dark
/// Localized display name.
var displayName: String {
switch self {
case .system:
return String(localized: "controls.theme.system")
case .light:
return String(localized: "controls.theme.light")
case .dark:
return String(localized: "controls.theme.dark")
}
}
/// Returns the ColorScheme to apply, or nil for system.
var colorScheme: ColorScheme? {
switch self {
case .system:
return nil
case .light:
return .light
case .dark:
return .dark
}
}
}
/// Size options for control buttons.
enum ButtonSize: String, Codable, Hashable, Sendable, CaseIterable {
case small
case medium
case large
/// The point size for buttons at this size.
var pointSize: CGFloat {
switch self {
case .small:
return 36
case .medium:
return 44
case .large:
return 52
}
}
/// The icon size relative to button size.
var iconSize: CGFloat {
switch self {
case .small:
return 18
case .medium:
return 22
case .large:
return 26
}
}
/// Localized display name.
var displayName: String {
switch self {
case .small:
return String(localized: "controls.size.small")
case .medium:
return String(localized: "controls.size.medium")
case .large:
return String(localized: "controls.size.large")
}
}
}
/// Background style options for control buttons.
enum ButtonBackgroundStyle: String, Codable, Hashable, Sendable, CaseIterable {
/// No background (default, current behavior)
case none
/// Clear glass - subtle circular glass background
case clearGlass
/// Regular glass - more visible circular glass background
case regularGlass
/// Localized display name.
var displayName: String {
switch self {
case .none:
return String(localized: "controls.buttonBackground.none")
case .clearGlass:
return String(localized: "controls.buttonBackground.clearGlass")
case .regularGlass:
return String(localized: "controls.buttonBackground.regularGlass")
}
}
/// The GlassStyle to use for this background style.
var glassStyle: GlassStyle? {
switch self {
case .none:
return nil
case .clearGlass:
return .clear
case .regularGlass:
return .regular
}
}
}
/// Overall style for player controls appearance.
enum ControlsStyle: String, Codable, Hashable, Sendable, CaseIterable {
/// No background, follows system theme
case plain
/// Regular glass background, always dark theme
case glass
/// Localized display name.
var displayName: String {
switch self {
case .plain:
return String(localized: "controls.style.plain")
case .glass:
return String(localized: "controls.style.glass")
}
}
/// The theme to apply for this style.
var theme: ControlsTheme {
switch self {
case .plain:
return .system
case .glass:
return .dark
}
}
/// The button background to apply for this style.
var buttonBackground: ButtonBackgroundStyle {
switch self {
case .plain:
return .none
case .glass:
return .regularGlass
}
}
}
// MARK: - Codable Color
/// A color stored as RGBA components for Codable support.
struct CodableColor: Codable, Hashable, Sendable {
var red: Double
var green: Double
var blue: Double
var opacity: Double
/// Creates a CodableColor from RGBA components.
/// - Parameters:
/// - red: Red component (0-1).
/// - green: Green component (0-1).
/// - blue: Blue component (0-1).
/// - opacity: Opacity component (0-1). Defaults to 1.
init(red: Double, green: Double, blue: Double, opacity: Double = 1.0) {
self.red = red
self.green = green
self.blue = blue
self.opacity = opacity
}
/// Creates a CodableColor from a SwiftUI Color.
/// - Parameter color: The SwiftUI Color to convert.
init(_ color: Color) {
let resolved = color.resolve(in: EnvironmentValues())
self.red = Double(resolved.red)
self.green = Double(resolved.green)
self.blue = Double(resolved.blue)
self.opacity = Double(resolved.opacity)
}
/// Converts to a SwiftUI Color.
var color: Color {
Color(red: red, green: green, blue: blue, opacity: opacity)
}
}
// MARK: - SponsorBlock Segment Settings
/// Settings for a single SponsorBlock category's appearance on the progress bar.
struct SponsorBlockCategorySettings: Codable, Hashable, Sendable {
/// Whether this category is visible on the progress bar.
var isVisible: Bool
/// The color to use for this category's segments.
var color: CodableColor
/// Creates category settings.
/// - Parameters:
/// - isVisible: Whether the category is visible. Defaults to `true`.
/// - color: The color for segments. Defaults to gray.
init(isVisible: Bool = true, color: CodableColor = CodableColor(red: 0.5, green: 0.5, blue: 0.5)) {
self.isVisible = isVisible
self.color = color
}
}
/// Settings for SponsorBlock segment display on the progress bar.
struct SponsorBlockSegmentSettings: Codable, Hashable, Sendable {
/// Global toggle for showing sponsor segments on the progress bar.
var showSegments: Bool
/// Per-category settings, keyed by SponsorBlockCategory raw value.
var categorySettings: [String: SponsorBlockCategorySettings]
/// Creates sponsor block segment settings.
/// - Parameters:
/// - showSegments: Whether to show segments. Defaults to `true`.
/// - categorySettings: Per-category settings. Defaults to default colors for all categories.
init(
showSegments: Bool = true,
categorySettings: [String: SponsorBlockCategorySettings]? = nil
) {
self.showSegments = showSegments
self.categorySettings = categorySettings ?? Self.defaultCategorySettings
}
/// Default settings with all categories visible using their default overlay colors.
static let `default` = SponsorBlockSegmentSettings()
/// Default category settings using default colors for each category.
/// Colors match the SponsorBlockCategory.overlayColor values.
static var defaultCategorySettings: [String: SponsorBlockCategorySettings] {
[
"sponsor": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.green)),
"selfpromo": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.yellow)),
"interaction": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.purple)),
"intro": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.cyan)),
"outro": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.blue)),
"preview": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.indigo)),
"music_offtopic": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.orange)),
"filler": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.gray)),
"poi_highlight": SponsorBlockCategorySettings(isVisible: true, color: CodableColor(.pink)),
]
}
/// Gets settings for a specific category key, returning defaults if not found.
/// - Parameter categoryKey: The category raw value key.
/// - Returns: The category settings.
func settings(forKey categoryKey: String) -> SponsorBlockCategorySettings {
categorySettings[categoryKey] ?? Self.defaultCategorySettings[categoryKey]
?? SponsorBlockCategorySettings()
}
/// Returns a copy with updated settings for a specific category key.
/// - Parameters:
/// - categoryKey: The category raw value key.
/// - settings: The new settings.
/// - Returns: Updated SponsorBlockSegmentSettings.
func withUpdatedSettings(
forKey categoryKey: String,
_ settings: SponsorBlockCategorySettings
) -> SponsorBlockSegmentSettings {
var newCategorySettings = categorySettings
newCategorySettings[categoryKey] = settings
return SponsorBlockSegmentSettings(
showSegments: showSegments,
categorySettings: newCategorySettings
)
}
}
// MARK: - SponsorBlockCategory Integration
extension SponsorBlockSegmentSettings {
/// Gets settings for a specific category, returning defaults if not found.
/// - Parameter category: The category to get settings for.
/// - Returns: The category settings.
func settings(for category: SponsorBlockCategory) -> SponsorBlockCategorySettings {
settings(forKey: category.rawValue)
}
}
// MARK: - Font Style
/// Font style options for player controls text.
enum ControlsFontStyle: String, Codable, Hashable, Sendable, CaseIterable {
/// System default font with monospaced digits.
case system
/// Fully monospaced font.
case monospaced
/// Rounded system font with monospaced digits.
case rounded
/// Localized display name.
var displayName: String {
switch self {
case .system:
return String(localized: "controls.fontStyle.system")
case .monospaced:
return String(localized: "controls.fontStyle.monospaced")
case .rounded:
return String(localized: "controls.fontStyle.rounded")
}
}
/// Returns a Font for the given text style.
func font(_ style: Font.TextStyle = .caption) -> Font {
switch self {
case .system:
return .system(style).monospacedDigit()
case .monospaced:
return .system(style, design: .monospaced)
case .rounded:
return .system(style, design: .rounded).monospacedDigit()
}
}
/// Returns a Font for the given size and weight.
func font(size: CGFloat, weight: Font.Weight = .regular) -> Font {
switch self {
case .system:
return .system(size: size, weight: weight).monospacedDigit()
case .monospaced:
return .system(size: size, weight: weight, design: .monospaced)
case .rounded:
return .system(size: size, weight: weight, design: .rounded).monospacedDigit()
}
}
}
/// Settings for the progress bar appearance.
struct ProgressBarSettings: Codable, Hashable, Sendable {
/// Color for the played portion of the progress bar.
var playedColor: CodableColor
/// Whether to show chapter markers on the progress bar.
var showChapters: Bool
/// Settings for SponsorBlock segment display.
var sponsorBlockSettings: SponsorBlockSegmentSettings
/// Creates progress bar settings.
/// - Parameters:
/// - playedColor: Color for the played portion. Defaults to red.
/// - showChapters: Whether to show chapter markers. Defaults to `true`.
/// - sponsorBlockSettings: Settings for SponsorBlock segments. Defaults to `.default`.
init(
playedColor: CodableColor = CodableColor(.red),
showChapters: Bool = true,
sponsorBlockSettings: SponsorBlockSegmentSettings = .default
) {
self.playedColor = playedColor
self.showChapters = showChapters
self.sponsorBlockSettings = sponsorBlockSettings
}
/// Default progress bar settings.
static let `default` = ProgressBarSettings()
}
/// Global settings that apply to all control buttons.
struct GlobalLayoutSettings: Codable, Hashable, Sendable {
/// Overall style for player controls.
var style: ControlsStyle
/// Size of control buttons.
var buttonSize: ButtonSize
/// Font style for text elements in player controls.
var fontStyle: ControlsFontStyle
/// Opacity of the controls background fade (0-1).
var controlsFadeOpacity: Double
/// Mode for system control buttons (Control Center, Lock Screen).
var systemControlsMode: SystemControlsMode
/// Duration for seek operations when systemControlsMode is .seek.
var systemControlsSeekDuration: SystemControlsSeekDuration
/// How volume is controlled during playback.
var volumeMode: VolumeMode
/// Forced appearance for the macOS control bar's glass background.
var controlBarTheme: ControlsTheme
/// Theme derived from style (for backwards compatibility).
var theme: ControlsTheme { style.theme }
/// Button background derived from style (for backwards compatibility).
var buttonBackground: ButtonBackgroundStyle { style.buttonBackground }
/// Cached settings for instant access (avoids flash on view recreation).
/// Updated whenever settings are loaded from the active preset.
nonisolated(unsafe) static var cached: GlobalLayoutSettings = .default
// MARK: - Codable
private enum CodingKeys: String, CodingKey {
case style
case buttonSize
case buttonBackground // Legacy, not used for encoding
case theme // Legacy, not used for encoding
case fontStyle
case controlsFadeOpacity
case systemControlsMode
case systemControlsSeekDuration
case volumeMode
case controlBarTheme
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
style = try container.decodeIfPresent(ControlsStyle.self, forKey: .style) ?? .glass
buttonSize = try container.decodeIfPresent(ButtonSize.self, forKey: .buttonSize) ?? .medium
fontStyle = try container.decodeIfPresent(ControlsFontStyle.self, forKey: .fontStyle) ?? .system
controlsFadeOpacity = try container.decodeIfPresent(Double.self, forKey: .controlsFadeOpacity) ?? 0.5
systemControlsMode = try container.decodeIfPresent(SystemControlsMode.self, forKey: .systemControlsMode) ?? .seek
systemControlsSeekDuration = try container.decodeIfPresent(SystemControlsSeekDuration.self, forKey: .systemControlsSeekDuration) ?? .tenSeconds
volumeMode = try container.decodeIfPresent(VolumeMode.self, forKey: .volumeMode) ?? .mpv
controlBarTheme = try container.decodeIfPresent(ControlsTheme.self, forKey: .controlBarTheme) ?? .system
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(style, forKey: .style)
try container.encode(buttonSize, forKey: .buttonSize)
try container.encode(fontStyle, forKey: .fontStyle)
try container.encode(controlsFadeOpacity, forKey: .controlsFadeOpacity)
try container.encode(systemControlsMode, forKey: .systemControlsMode)
try container.encode(systemControlsSeekDuration, forKey: .systemControlsSeekDuration)
try container.encode(volumeMode, forKey: .volumeMode)
try container.encode(controlBarTheme, forKey: .controlBarTheme)
}
// MARK: - Initialization
/// Creates global layout settings.
init(
style: ControlsStyle = .glass,
buttonSize: ButtonSize = .medium,
fontStyle: ControlsFontStyle = .system,
controlsFadeOpacity: Double = 0.5,
systemControlsMode: SystemControlsMode = .seek,
systemControlsSeekDuration: SystemControlsSeekDuration = .tenSeconds,
volumeMode: VolumeMode = .mpv,
controlBarTheme: ControlsTheme = .system
) {
self.style = style
self.buttonSize = buttonSize
self.fontStyle = fontStyle
self.controlsFadeOpacity = controlsFadeOpacity
self.systemControlsMode = systemControlsMode
self.systemControlsSeekDuration = systemControlsSeekDuration
self.volumeMode = volumeMode
self.controlBarTheme = controlBarTheme
}
// MARK: - Defaults
/// Default global layout settings.
static let `default` = GlobalLayoutSettings()
}