mirror of
https://github.com/yattee/yattee.git
synced 2026-10-24 05:32:53 +00:00
Pages list - Info is pinned first and always enabled in every layout: normalized pages force it to the front and enabled (older presets self-correct), disabling it is a no-op, and any move that would displace it snaps back. The iOS/macOS list renders it outside the reorderable ForEach (no drag handle, nothing drops above it) and the editors hide its enabled toggle; tvOS also hides its move buttons. The "at least one page must stay enabled" guard and its strings are gone since Info always counts. - iPhone never renders queue or comments as pages, so its Sections list hides them (PanelSettings.hiddenSettingsPageTypes). Reordering goes through moveVisiblePages, which keeps hidden entries in their stored slots. Comments get their own group on the Details Panel screen (pill mode + avatars) and the overlay hosts pass the avatars flag. Default visibility - "When Player Opens" is now configured per orientation (portraitDefaultVisibility / landscapeDefaultVisibility). The legacy single value is still decoded and seeds both until set individually; presets that never set it default to portrait Visible, landscape Hidden. macOS shows one picker (no portrait layout). - "Remember Last" no longer leaks across orientations: separate remembered flags, written by the portrait toggles and by the wide and corner layouts respectively, with the old shared flag read once as a fallback. The corner layout seeded from the portrait resolution before; it now uses landscape. Section options (iOS inline-sections hosts) - Chapters and Related editors gain "Expanded by Default" (on by default). The Info editor gains a Description group with "Expanded by Default" (off) and a "Collapsed Lines" stepper (1–10, default 3). Applied when the panel appears and on each video change. - Expanding a collapsed section scrolls its header to the top of the panel: PanelPageScrollView can hand its scroll proxy to page content and each inline section is wrapped in one anchored container. - The portrait panel's bottom fade sized its touch blocker from a stale "expanded comments pill" answer that ignored collapse on scroll, so a collapsed section header (the last item) sat under it and could not be tapped. The predicate now tracks the pill's actual collapse state.
576 lines
20 KiB
Swift
576 lines
20 KiB
Swift
//
|
|
// SettingsManager.swift
|
|
// Yattee
|
|
//
|
|
// Manages user settings with iCloud sync via NSUbiquitousKeyValueStore.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
#if os(iOS)
|
|
import CoreHaptics
|
|
import UIKit
|
|
#endif
|
|
|
|
/// Manages application settings with platform-specific keys and iCloud sync.
|
|
@MainActor
|
|
@Observable
|
|
final class SettingsManager {
|
|
// MARK: - Storage
|
|
|
|
let ubiquitousStore = NSUbiquitousKeyValueStore.default
|
|
let localDefaults = UserDefaults.standard
|
|
|
|
// MARK: - Backing Storage for @Observable
|
|
// These stored properties trigger observation when modified.
|
|
// Internal access for extension use.
|
|
|
|
// Theme
|
|
var _theme: AppTheme?
|
|
var _accentColor: AccentColor?
|
|
var _customAccentColor: String?
|
|
var _accentColorDark: AccentColor?
|
|
var _customAccentColorDark: String?
|
|
var _useSeparateDarkAccentColor: Bool?
|
|
var _showWatchedCheckmark: Bool?
|
|
|
|
// Accounts
|
|
var _showAccountPickers: Bool?
|
|
|
|
// Playback
|
|
var _preferredQuality: VideoQuality?
|
|
var _cellularQuality: VideoQuality?
|
|
var _backgroundPlaybackEnabled: Bool?
|
|
var _preferredAudioLanguage: String?
|
|
var _preferredSubtitlesLanguage: String?
|
|
var _playerVolume: Float?
|
|
var _resumeAction: ResumeAction?
|
|
var _tvOSMenuButtonClosesVideo: Bool?
|
|
var _allowSoftwareDecodedFormats: Bool?
|
|
var _audioOnlyModeEnabled: Bool?
|
|
|
|
// SponsorBlock
|
|
var _sponsorBlockEnabled: Bool?
|
|
var _sponsorBlockCategories: Set<SponsorBlockCategory>?
|
|
var _sponsorBlockAPIURL: String?
|
|
|
|
// Return YouTube Dislike & DeArrow
|
|
var _returnYouTubeDislikeEnabled: Bool?
|
|
var _deArrowEnabled: Bool?
|
|
var _deArrowReplaceTitles: Bool?
|
|
var _deArrowReplaceThumbnails: Bool?
|
|
var _deArrowAPIURL: String?
|
|
var _deArrowThumbnailAPIURL: String?
|
|
|
|
// Short link resolution
|
|
var _resolveShortLinksEnabled: Bool?
|
|
|
|
// User Agent
|
|
var _customUserAgent: String?
|
|
var _randomizeUserAgentPerRequest: Bool?
|
|
|
|
// Feed
|
|
var _feedCacheValidityMinutes: Int?
|
|
|
|
// Player
|
|
var _keepPlayerPinnedEnabled: Bool?
|
|
#if os(iOS)
|
|
var _inAppOrientationLock: Bool?
|
|
var _rotateToMatchAspectRatio: Bool?
|
|
var _preferPortraitBrowsing: Bool?
|
|
#endif
|
|
#if os(macOS)
|
|
var _macPlayerSeparateWindow: Bool?
|
|
var _macPlayerFloating: Bool?
|
|
var _macControlsBarOffsetX: Double?
|
|
var _macControlsBarOffsetY: Double?
|
|
var _playerSheetAutoResize: Bool?
|
|
#endif
|
|
|
|
// Mini Player Minimize Behavior is kept as it's not part of the preset
|
|
|
|
// Mini Player Minimize Behavior (iOS 26+)
|
|
#if os(iOS)
|
|
var _miniPlayerMinimizeBehavior: (any RawRepresentable)?
|
|
#endif
|
|
|
|
// Haptics (iOS)
|
|
#if os(iOS)
|
|
var _hapticFeedbackEnabled: Bool?
|
|
var _hapticFeedbackIntensity: HapticFeedbackIntensity?
|
|
#endif
|
|
|
|
// iCloud sync
|
|
var _iCloudSyncEnabled: Bool?
|
|
var _lastSyncTime: Date?
|
|
var _syncInstances: Bool?
|
|
var _syncSubscriptions: Bool?
|
|
var _syncBookmarks: Bool?
|
|
var _syncPlaybackHistory: Bool?
|
|
var _syncPlaylists: Bool?
|
|
var _syncSettings: Bool?
|
|
var _syncMediaSources: Bool?
|
|
var _syncSearchHistory: Bool?
|
|
|
|
// Search history
|
|
var _searchHistoryLimit: Int?
|
|
|
|
// Home settings
|
|
var _homeShortcutOrder: [HomeShortcutItem]?
|
|
var _homeShortcutVisibility: [HomeShortcutItem: Bool]?
|
|
var _homeShortcutLayout: HomeShortcutLayout?
|
|
var _homeShortcutCardStyle: HomeShortcutCardStyle?
|
|
var _homeShortcutCardColor: HomeShortcutCardColor?
|
|
var _homeShortcutColorfulPalette: HomeShortcutColorfulPalette?
|
|
var _homeShortcutCustomPaletteColors: [String]?
|
|
var _homeSectionOrder: [HomeSectionItem]?
|
|
var _homeSectionVisibility: [HomeSectionItem: Bool]?
|
|
var _homeSectionItemsLimit: Int?
|
|
var _homeSectionLayout: HomeSectionLayout?
|
|
|
|
// Top Shelf (tvOS)
|
|
var _topShelfSections: [TopShelfSection]?
|
|
|
|
// Custom MPV options (local-only)
|
|
var _customMPVOptions: [String: String]?
|
|
|
|
// Tab bar settings (compact size class only - iOS)
|
|
var _tabBarItemOrder: [TabBarItem]?
|
|
var _tabBarItemVisibility: [TabBarItem: Bool]?
|
|
|
|
// Sidebar settings
|
|
var _sidebarMainItemOrder: [SidebarMainItem]?
|
|
var _sidebarMainItemVisibility: [SidebarMainItem: Bool]?
|
|
var _sidebarStartupTab: SidebarMainItem?
|
|
|
|
// Tab bar startup
|
|
var _tabBarStartupTab: SidebarMainItem?
|
|
var _sidebarSourcesEnabled: Bool?
|
|
var _sidebarSourceSort: SidebarSourceSort?
|
|
var _sidebarSourcesLimitEnabled: Bool?
|
|
var _sidebarMaxSources: Int?
|
|
var _sidebarChannelsEnabled: Bool?
|
|
var _sidebarMaxChannels: Int?
|
|
var _sidebarChannelSort: SidebarChannelSort?
|
|
var _sidebarChannelsLimitEnabled: Bool?
|
|
var _sidebarPlaylistsEnabled: Bool?
|
|
var _sidebarMaxPlaylists: Int?
|
|
var _sidebarPlaylistSort: SidebarPlaylistSort?
|
|
var _sidebarPlaylistsLimitEnabled: Bool?
|
|
|
|
// iCloud startup sync protection
|
|
/// When true, suppresses iCloud writes from set() methods to prevent
|
|
/// stale local values from overwriting newer iCloud data during app startup.
|
|
var isInitialSyncPending = false
|
|
|
|
// Advanced settings
|
|
var _showAdvancedStreamDetails: Bool?
|
|
var _showPlayerAreaDebug: Bool?
|
|
var _showTVDebugButton: Bool?
|
|
var _verboseMPVLogging: Bool?
|
|
var _verboseRemoteControlLogging: Bool?
|
|
var _mpvBufferSeconds: Double?
|
|
var _mpvUseEDLStreams: Bool?
|
|
var _zoomTransitionsEnabled: Bool?
|
|
var _tvMatchDisplayFrameRate: Bool?
|
|
var _tvMatchDisplayDynamicRange: Bool?
|
|
var _tvAudioDelayMs: Double?
|
|
var _tvVideoSyncMode: TVVideoSyncMode?
|
|
|
|
// Details panel settings
|
|
var _floatingDetailsPanelWidth: CGFloat?
|
|
var _landscapeDetailsPanelVisible: Bool?
|
|
var _landscapeDetailsPanelPinned: Bool?
|
|
var _portraitDetailsPanelLastVisible: Bool?
|
|
var _landscapeDetailsPanelLastVisible: Bool?
|
|
var _cornerPanelColumnWidth: CGFloat?
|
|
|
|
// Notification settings
|
|
var _backgroundNotificationsEnabled: Bool?
|
|
var _defaultNotificationsForNewChannels: Bool?
|
|
var _lastBackgroundCheck: Date?
|
|
var _clipboardURLDetectionEnabled: Bool?
|
|
var _incognitoModeEnabled: Bool?
|
|
var _historyRetentionDays: Int?
|
|
var _saveWatchHistory: Bool?
|
|
var _saveRecentSearches: Bool?
|
|
var _saveRecentChannels: Bool?
|
|
var _saveRecentPlaylists: Bool?
|
|
|
|
// Queue settings
|
|
var _queueEnabled: Bool?
|
|
var _queueAutoPlayNext: Bool?
|
|
var _queueAutoPlayCountdown: Int?
|
|
|
|
// Handoff settings
|
|
var _handoffEnabled: Bool?
|
|
|
|
// Sharing settings
|
|
var _privacyFrontendSharingEnabled: Bool?
|
|
|
|
// Remote Control settings
|
|
var _remoteControlCustomDeviceName: String?
|
|
var _remoteControlHideWhenBackgrounded: Bool?
|
|
|
|
// Link action settings
|
|
var _defaultLinkAction: DefaultLinkAction?
|
|
|
|
// Video tap actions (iOS/macOS only)
|
|
#if !os(tvOS)
|
|
var _thumbnailTapAction: VideoTapAction?
|
|
var _textAreaTapAction: VideoTapAction?
|
|
var _videoInfoActionsLayout: VideoInfoActionsLayout?
|
|
var _videoInfoPrimaryAction: VideoInfoPrimaryAction?
|
|
#endif
|
|
|
|
// Video tap action (tvOS only)
|
|
#if os(tvOS)
|
|
var _tvOSVideoTapAction: VideoTapAction?
|
|
#endif
|
|
|
|
// Player Controls settings (controlsButtonSize moved to preset)
|
|
|
|
// Appearance settings
|
|
var _listStyle: VideoListStyle?
|
|
var _appIcon: AppIcon?
|
|
|
|
// Video Swipe Actions
|
|
#if !os(tvOS)
|
|
var _videoSwipeActionOrder: [VideoSwipeAction]?
|
|
var _videoSwipeActionVisibility: [VideoSwipeAction: Bool]?
|
|
#endif
|
|
|
|
// MARK: - Initialization
|
|
|
|
/// Logs KVStore change reason for debugging iCloud account switches.
|
|
private func logKVStoreChangeReason(_ reason: Int) {
|
|
let reasonDescription: String
|
|
switch reason {
|
|
case NSUbiquitousKeyValueStoreServerChange:
|
|
reasonDescription = "ServerChange"
|
|
case NSUbiquitousKeyValueStoreInitialSyncChange:
|
|
reasonDescription = "InitialSyncChange"
|
|
case NSUbiquitousKeyValueStoreQuotaViolationChange:
|
|
reasonDescription = "QuotaViolationChange"
|
|
case NSUbiquitousKeyValueStoreAccountChange:
|
|
reasonDescription = "AccountChange (iCloud account switched!)"
|
|
LoggingService.shared.logCloudKit("SettingsManager: iCloud account changed - settings will sync with new account")
|
|
default:
|
|
reasonDescription = "Unknown(\(reason))"
|
|
}
|
|
|
|
LoggingService.shared.logCloudKit("SettingsManager KVStore change: \(reasonDescription)")
|
|
}
|
|
|
|
init() {
|
|
// Listen for external changes from iCloud
|
|
NotificationCenter.default.addObserver(
|
|
forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
|
|
object: ubiquitousStore,
|
|
queue: .main
|
|
) { [weak self] notification in
|
|
let changeReason = notification.userInfo?[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int
|
|
let changedKeys = notification.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String]
|
|
|
|
// Only process iCloud changes if sync is enabled
|
|
Task { @MainActor [weak self] in
|
|
// Log change reason for debugging account switches
|
|
if let changeReason {
|
|
self?.logKVStoreChangeReason(changeReason)
|
|
}
|
|
|
|
if let changedKeys {
|
|
LoggingService.shared.logCloudKit("SettingsManager KVStore changed keys: \(changedKeys)")
|
|
}
|
|
|
|
guard let self, self.iCloudSyncEnabled else { return }
|
|
let keySet = changedKeys.map { Set($0) }
|
|
self.refreshFromiCloud(changedKeys: keySet)
|
|
self.updateLastSyncTime()
|
|
}
|
|
}
|
|
|
|
// One-shot migration: move legacy unprefixed values for keys that became
|
|
// platform-specific into their new iOS./macOS./tvOS. slots. Must run before
|
|
// the initial iCloud refresh so it can seed the prefixed iCloud slot from the
|
|
// current local value before any remote data is read.
|
|
migrateLayoutKeysToPlatformPrefixed()
|
|
|
|
// Initial sync from iCloud to local storage (async to avoid blocking app launch)
|
|
// This ensures local defaults have the latest iCloud values before any reads.
|
|
// While sync is pending, suppress iCloud writes from set() to prevent stale
|
|
// local values from overwriting newer iCloud data.
|
|
if localDefaults.bool(forKey: "iCloudSyncEnabled") {
|
|
isInitialSyncPending = true
|
|
LoggingService.shared.logCloudKit("SettingsManager.init: isInitialSyncPending = true, suppressing iCloud writes until refresh completes")
|
|
Task { @MainActor [weak self] in
|
|
defer {
|
|
self?.isInitialSyncPending = false
|
|
LoggingService.shared.logCloudKit("SettingsManager.init: isInitialSyncPending = false, iCloud writes re-enabled")
|
|
}
|
|
self?.ubiquitousStore.synchronize()
|
|
self?.refreshFromiCloud()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Storage Helpers
|
|
// Internal access for extension use.
|
|
|
|
func platformKey(_ key: SettingsKey) -> String {
|
|
let baseKey = key.rawValue
|
|
#if os(iOS)
|
|
return key.isPlatformSpecific ? "iOS.\(baseKey)" : baseKey
|
|
#elseif os(macOS)
|
|
return key.isPlatformSpecific ? "macOS.\(baseKey)" : baseKey
|
|
#elseif os(tvOS)
|
|
return key.isPlatformSpecific ? "tvOS.\(baseKey)" : baseKey
|
|
#endif
|
|
}
|
|
|
|
/// Returns the companion key used to store the last-modified timestamp for a protected setting.
|
|
func modifiedAtKey(for key: SettingsKey) -> String {
|
|
"\(platformKey(key))_modifiedAt"
|
|
}
|
|
|
|
func string(for key: SettingsKey) -> String? {
|
|
// Always read from local storage to avoid blocking on iCloud XPC calls
|
|
return localDefaults.string(forKey: platformKey(key))
|
|
}
|
|
|
|
func bool(for key: SettingsKey, default defaultValue: Bool = false) -> Bool {
|
|
// Always read from local storage to avoid blocking on iCloud XPC calls
|
|
let pKey = platformKey(key)
|
|
if localDefaults.object(forKey: pKey) != nil {
|
|
return localDefaults.bool(forKey: pKey)
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func data(for key: SettingsKey) -> Data? {
|
|
// Always read from local storage to avoid blocking on iCloud XPC calls
|
|
return localDefaults.data(forKey: platformKey(key))
|
|
}
|
|
|
|
func integer(for key: SettingsKey, default defaultValue: Int) -> Int {
|
|
// Always read from local storage to avoid blocking on iCloud XPC calls
|
|
let pKey = platformKey(key)
|
|
if localDefaults.object(forKey: pKey) != nil {
|
|
return localDefaults.integer(forKey: pKey)
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func double(for key: SettingsKey) -> Double {
|
|
// Always read from local storage to avoid blocking on iCloud XPC calls
|
|
return localDefaults.double(forKey: platformKey(key))
|
|
}
|
|
|
|
func set(_ value: String, for key: SettingsKey) {
|
|
let pKey = platformKey(key)
|
|
localDefaults.set(value, forKey: pKey)
|
|
|
|
// Only write to iCloud if sync is enabled, settings sync is enabled, key is not local-only,
|
|
// and initial sync has completed (to prevent stale values overwriting iCloud during startup)
|
|
if iCloudSyncEnabled && syncSettings && !key.isLocalOnly && !isInitialSyncPending {
|
|
ubiquitousStore.set(value, forKey: pKey)
|
|
} else if isInitialSyncPending && !key.isLocalOnly {
|
|
LoggingService.shared.logCloudKit("set(String): suppressed iCloud write for \(pKey) (initial sync pending)")
|
|
}
|
|
}
|
|
|
|
func set(_ value: Bool, for key: SettingsKey) {
|
|
let pKey = platformKey(key)
|
|
localDefaults.set(value, forKey: pKey)
|
|
|
|
if iCloudSyncEnabled && syncSettings && !key.isLocalOnly && !isInitialSyncPending {
|
|
ubiquitousStore.set(value, forKey: pKey)
|
|
} else if isInitialSyncPending && !key.isLocalOnly {
|
|
LoggingService.shared.logCloudKit("set(Bool): suppressed iCloud write for \(pKey) (initial sync pending)")
|
|
}
|
|
}
|
|
|
|
func set(_ value: Data, for key: SettingsKey) {
|
|
let pKey = platformKey(key)
|
|
localDefaults.set(value, forKey: pKey)
|
|
|
|
if iCloudSyncEnabled && syncSettings && !key.isLocalOnly && !isInitialSyncPending {
|
|
ubiquitousStore.set(value, forKey: pKey)
|
|
} else if isInitialSyncPending && !key.isLocalOnly {
|
|
LoggingService.shared.logCloudKit("set(Data): suppressed iCloud write for \(pKey) (initial sync pending)")
|
|
}
|
|
}
|
|
|
|
func set(_ value: Int, for key: SettingsKey) {
|
|
let pKey = platformKey(key)
|
|
localDefaults.set(value, forKey: pKey)
|
|
|
|
if iCloudSyncEnabled && syncSettings && !key.isLocalOnly && !isInitialSyncPending {
|
|
ubiquitousStore.set(value, forKey: pKey)
|
|
} else if isInitialSyncPending && !key.isLocalOnly {
|
|
LoggingService.shared.logCloudKit("set(Int): suppressed iCloud write for \(pKey) (initial sync pending)")
|
|
}
|
|
}
|
|
|
|
func set(_ value: Double, for key: SettingsKey) {
|
|
let pKey = platformKey(key)
|
|
localDefaults.set(value, forKey: pKey)
|
|
|
|
if iCloudSyncEnabled && syncSettings && !key.isLocalOnly && !isInitialSyncPending {
|
|
ubiquitousStore.set(value, forKey: pKey)
|
|
} else if isInitialSyncPending && !key.isLocalOnly {
|
|
LoggingService.shared.logCloudKit("set(Double): suppressed iCloud write for \(pKey) (initial sync pending)")
|
|
}
|
|
}
|
|
|
|
// MARK: - Cache Management
|
|
|
|
/// Clears cached values to force re-read from storage
|
|
func clearCache() {
|
|
_theme = nil
|
|
_accentColor = nil
|
|
_customAccentColor = nil
|
|
_accentColorDark = nil
|
|
_customAccentColorDark = nil
|
|
_useSeparateDarkAccentColor = nil
|
|
_showWatchedCheckmark = nil
|
|
_showAccountPickers = nil
|
|
_preferredQuality = nil
|
|
_cellularQuality = nil
|
|
_backgroundPlaybackEnabled = nil
|
|
_preferredAudioLanguage = nil
|
|
_preferredSubtitlesLanguage = nil
|
|
_playerVolume = nil
|
|
_resumeAction = nil
|
|
_tvOSMenuButtonClosesVideo = nil
|
|
_allowSoftwareDecodedFormats = nil
|
|
_audioOnlyModeEnabled = nil
|
|
_sponsorBlockEnabled = nil
|
|
_sponsorBlockCategories = nil
|
|
_sponsorBlockAPIURL = nil
|
|
_returnYouTubeDislikeEnabled = nil
|
|
_deArrowEnabled = nil
|
|
_deArrowReplaceTitles = nil
|
|
_deArrowReplaceThumbnails = nil
|
|
_deArrowAPIURL = nil
|
|
_deArrowThumbnailAPIURL = nil
|
|
_resolveShortLinksEnabled = nil
|
|
_customUserAgent = nil
|
|
_randomizeUserAgentPerRequest = nil
|
|
_feedCacheValidityMinutes = nil
|
|
_keepPlayerPinnedEnabled = nil
|
|
#if os(iOS)
|
|
_hapticFeedbackEnabled = nil
|
|
_hapticFeedbackIntensity = nil
|
|
_inAppOrientationLock = nil
|
|
_rotateToMatchAspectRatio = nil
|
|
_preferPortraitBrowsing = nil
|
|
#endif
|
|
_iCloudSyncEnabled = nil
|
|
_lastSyncTime = nil
|
|
_syncInstances = nil
|
|
_syncSubscriptions = nil
|
|
_syncBookmarks = nil
|
|
_syncPlaybackHistory = nil
|
|
_syncPlaylists = nil
|
|
_syncSettings = nil
|
|
_syncMediaSources = nil
|
|
_syncSearchHistory = nil
|
|
_searchHistoryLimit = nil
|
|
#if os(macOS)
|
|
_macPlayerSeparateWindow = nil
|
|
_macPlayerFloating = nil
|
|
_macControlsBarOffsetX = nil
|
|
_macControlsBarOffsetY = nil
|
|
_playerSheetAutoResize = nil
|
|
#endif
|
|
// miniPlayerShowVideo and miniPlayerVideoTapAction moved to preset
|
|
#if os(iOS)
|
|
_miniPlayerMinimizeBehavior = nil
|
|
#endif
|
|
_homeShortcutOrder = nil
|
|
_homeShortcutVisibility = nil
|
|
_homeShortcutLayout = nil
|
|
_homeShortcutCardStyle = nil
|
|
_homeShortcutCardColor = nil
|
|
_homeShortcutColorfulPalette = nil
|
|
_homeShortcutCustomPaletteColors = nil
|
|
_homeSectionOrder = nil
|
|
_homeSectionVisibility = nil
|
|
_homeSectionItemsLimit = nil
|
|
_homeSectionLayout = nil
|
|
_topShelfSections = nil
|
|
_tabBarItemOrder = nil
|
|
_tabBarItemVisibility = nil
|
|
_sidebarMainItemOrder = nil
|
|
_sidebarMainItemVisibility = nil
|
|
_sidebarStartupTab = nil
|
|
_tabBarStartupTab = nil
|
|
_sidebarSourcesEnabled = nil
|
|
_sidebarSourceSort = nil
|
|
_sidebarSourcesLimitEnabled = nil
|
|
_sidebarMaxSources = nil
|
|
_sidebarChannelsEnabled = nil
|
|
_sidebarMaxChannels = nil
|
|
_sidebarChannelSort = nil
|
|
_sidebarChannelsLimitEnabled = nil
|
|
_sidebarPlaylistsEnabled = nil
|
|
_sidebarMaxPlaylists = nil
|
|
_sidebarPlaylistSort = nil
|
|
_sidebarPlaylistsLimitEnabled = nil
|
|
_showAdvancedStreamDetails = nil
|
|
_showPlayerAreaDebug = nil
|
|
_showTVDebugButton = nil
|
|
_verboseMPVLogging = nil
|
|
_verboseRemoteControlLogging = nil
|
|
_mpvBufferSeconds = nil
|
|
_mpvUseEDLStreams = nil
|
|
_zoomTransitionsEnabled = nil
|
|
_tvMatchDisplayFrameRate = nil
|
|
_tvMatchDisplayDynamicRange = nil
|
|
_tvAudioDelayMs = nil
|
|
_tvVideoSyncMode = nil
|
|
_floatingDetailsPanelWidth = nil
|
|
_landscapeDetailsPanelVisible = nil
|
|
_landscapeDetailsPanelPinned = nil
|
|
_portraitDetailsPanelLastVisible = nil
|
|
_landscapeDetailsPanelLastVisible = nil
|
|
_cornerPanelColumnWidth = nil
|
|
_backgroundNotificationsEnabled = nil
|
|
_defaultNotificationsForNewChannels = nil
|
|
_lastBackgroundCheck = nil
|
|
_clipboardURLDetectionEnabled = nil
|
|
_incognitoModeEnabled = nil
|
|
_historyRetentionDays = nil
|
|
_saveWatchHistory = nil
|
|
_saveRecentSearches = nil
|
|
_saveRecentChannels = nil
|
|
_saveRecentPlaylists = nil
|
|
_queueEnabled = nil
|
|
_queueAutoPlayNext = nil
|
|
_queueAutoPlayCountdown = nil
|
|
_handoffEnabled = nil
|
|
_privacyFrontendSharingEnabled = nil
|
|
_defaultLinkAction = nil
|
|
_remoteControlCustomDeviceName = nil
|
|
_remoteControlHideWhenBackgrounded = nil
|
|
#if !os(tvOS)
|
|
_thumbnailTapAction = nil
|
|
_textAreaTapAction = nil
|
|
_videoInfoActionsLayout = nil
|
|
_videoInfoPrimaryAction = nil
|
|
#endif
|
|
#if os(tvOS)
|
|
_tvOSVideoTapAction = nil
|
|
#endif
|
|
_listStyle = nil
|
|
#if os(iOS)
|
|
_appIcon = nil
|
|
#endif
|
|
#if !os(tvOS)
|
|
_videoSwipeActionOrder = nil
|
|
_videoSwipeActionVisibility = nil
|
|
#endif
|
|
}
|
|
}
|