refactor(settings): typed Pref<T> declarations
This commit is contained in:
+5
-5
@@ -135,7 +135,7 @@ Future<void> main() async {
|
||||
Future<void> _bootstrapApp() async {
|
||||
// Initialize settings first to get saved locale
|
||||
final settings = await SettingsService.getInstance();
|
||||
final savedLocale = settings.getAppLocale();
|
||||
final savedLocale = settings.read(SettingsService.appLocale);
|
||||
|
||||
// Initialize localization with saved locale
|
||||
LocaleSettings.setLocale(savedLocale);
|
||||
@@ -162,7 +162,7 @@ Future<void> _bootstrapApp() async {
|
||||
|
||||
// Initialize TV detection (Android leanback or Apple TV) and PiP on Android.
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
futures.add(TvDetectionService.getInstance(forceTv: settings.getForceTvMode()));
|
||||
futures.add(TvDetectionService.getInstance(forceTv: settings.read(SettingsService.forceTvMode)));
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
// Initialize PiP service to listen for PiP state changes (Android only).
|
||||
@@ -188,7 +188,7 @@ Future<void> _bootstrapApp() async {
|
||||
}
|
||||
|
||||
// Initialize logger level based on debug setting
|
||||
final debugEnabled = settings.getEnableDebugLogging();
|
||||
final debugEnabled = settings.read(SettingsService.enableDebugLogging);
|
||||
setLoggerLevel(debugEnabled);
|
||||
|
||||
// Log app version and git commit at startup
|
||||
@@ -248,7 +248,7 @@ Breadcrumb? _beforeBreadcrumb(Breadcrumb? breadcrumb, Hint _) {
|
||||
FutureOr<SentryEvent?> _beforeSend(SentryEvent event, Hint _) {
|
||||
// Drop event if user opted out of crash reporting
|
||||
final instance = SettingsService.instanceOrNull;
|
||||
if (instance != null && !instance.getCrashReporting()) return null;
|
||||
if (instance != null && !instance.read(SettingsService.crashReporting)) return null;
|
||||
|
||||
// Drop unactionable errors
|
||||
var exceptions = event.exceptions;
|
||||
@@ -530,7 +530,7 @@ class _MainAppState extends State<MainApp> with WidgetsBindingObserver {
|
||||
await downloadProvider.refreshMetadataFromCache();
|
||||
final activeKey = VideoPlayerScreenState.activeRatingKey;
|
||||
final settings = SettingsService.instanceOrNull;
|
||||
if (settings != null && settings.getAutoRemoveWatchedDownloads()) {
|
||||
if (settings != null && settings.read(SettingsService.autoRemoveWatchedDownloads)) {
|
||||
final deleted = await downloadProvider.autoDeleteWatchedDownloads(activeRatingKey: activeKey);
|
||||
if (deleted.isNotEmpty) {
|
||||
final msg = deleted.length == 1
|
||||
|
||||
@@ -185,7 +185,7 @@ class OfflineWatchProvider extends ChangeNotifier {
|
||||
/// Auto-delete a download if the auto-remove setting is enabled.
|
||||
void _autoDeleteIfWatched(String serverId, String ratingKey) {
|
||||
final settings = SettingsService.instanceOrNull;
|
||||
if (settings == null || !settings.getAutoRemoveWatchedDownloads()) return;
|
||||
if (settings == null || !settings.read(SettingsService.autoRemoveWatchedDownloads)) return;
|
||||
|
||||
final globalKey = buildGlobalKey(serverId, ratingKey);
|
||||
final meta = _downloadProvider.getMetadata(globalKey);
|
||||
|
||||
@@ -1,229 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/transcode_quality_preset.dart';
|
||||
import '../services/base_shared_preferences_service.dart';
|
||||
import '../services/settings_service.dart';
|
||||
|
||||
class SettingsProvider extends ChangeNotifier {
|
||||
SettingsService? _settingsService;
|
||||
int _libraryDensity = LibraryDensity.defaultValue;
|
||||
ViewMode _viewMode = ViewMode.grid;
|
||||
EpisodePosterMode _episodePosterMode = EpisodePosterMode.seriesPoster;
|
||||
bool _showHeroSection = true;
|
||||
bool _useGlobalHubs = true;
|
||||
bool _showServerNameOnHubs = false;
|
||||
bool _groupLibrariesByServer = true;
|
||||
bool _alwaysKeepSidebarOpen = false;
|
||||
bool _showUnwatchedCount = true;
|
||||
bool _showEpisodeNumberOnCards = true;
|
||||
bool _hideSpoilers = false;
|
||||
bool _showNavBarLabels = true;
|
||||
bool _liveTvDefaultFavorites = false;
|
||||
bool _autoHidePerformanceOverlay = true;
|
||||
TranscodeQualityPreset _defaultQualityPreset = TranscodeQualityPreset.original;
|
||||
bool _isInitialized = false;
|
||||
Future<void>? _initFuture;
|
||||
|
||||
SettingsProvider() {
|
||||
// Start initialization eagerly to reduce race conditions
|
||||
_initFuture = _initializeSettings();
|
||||
}
|
||||
|
||||
/// Ensures the provider is initialized. Call this before accessing settings
|
||||
/// in contexts where you need the actual persisted values.
|
||||
Future<void> ensureInitialized() => _initFuture ?? _initializeSettings();
|
||||
|
||||
/// Whether the provider has finished loading persisted settings.
|
||||
/// Synchronous getters below return defaults until this is true.
|
||||
bool get isReady => _isInitialized;
|
||||
|
||||
/// Future that completes once persisted settings have been loaded.
|
||||
bool get isInitialized => _isInitialized;
|
||||
Future<void> get ready => _initFuture ?? Future.value();
|
||||
|
||||
Future<void> _initializeSettings() async {
|
||||
if (_isInitialized) return;
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
_readAllFromService();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _readAllFromService() {
|
||||
final s = _settingsService!;
|
||||
_libraryDensity = s.getLibraryDensity();
|
||||
_viewMode = s.getViewMode();
|
||||
_episodePosterMode = s.getEpisodePosterMode();
|
||||
_showHeroSection = s.getShowHeroSection();
|
||||
_useGlobalHubs = s.getUseGlobalHubs();
|
||||
_showServerNameOnHubs = s.getShowServerNameOnHubs();
|
||||
_groupLibrariesByServer = s.getGroupLibrariesByServer();
|
||||
_alwaysKeepSidebarOpen = s.getAlwaysKeepSidebarOpen();
|
||||
_showUnwatchedCount = s.getShowUnwatchedCount();
|
||||
_showEpisodeNumberOnCards = s.getShowEpisodeNumberOnCards();
|
||||
_hideSpoilers = s.getHideSpoilers();
|
||||
_showNavBarLabels = s.getShowNavBarLabels();
|
||||
_liveTvDefaultFavorites = s.getLiveTvDefaultFavorites();
|
||||
_autoHidePerformanceOverlay = s.getAutoHidePerformanceOverlay();
|
||||
_defaultQualityPreset = TranscodeQualityPreset.fromStorage(s.getDefaultQualityPreset());
|
||||
}
|
||||
|
||||
/// Re-read all settings from SharedPreferences. Used after imports or resets
|
||||
/// that change persisted settings outside this provider.
|
||||
/// Re-read settings after an external mutation (import, reset). The provider
|
||||
/// no longer caches values, so this just re-fetches the service instance and
|
||||
/// notifies listeners.
|
||||
Future<void> reload() async {
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
_readAllFromService();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Whether the provider has completed initialization
|
||||
bool get isInitialized => _isInitialized;
|
||||
T _read<T>(Pref<T> pref, T fallback) => _isInitialized ? _settingsService!.read(pref) : fallback;
|
||||
|
||||
int get libraryDensity => _libraryDensity;
|
||||
|
||||
ViewMode get viewMode => _viewMode;
|
||||
|
||||
EpisodePosterMode get episodePosterMode => _episodePosterMode;
|
||||
|
||||
bool get showHeroSection => _showHeroSection;
|
||||
|
||||
bool get useGlobalHubs => _useGlobalHubs;
|
||||
|
||||
bool get showServerNameOnHubs => _showServerNameOnHubs;
|
||||
|
||||
bool get groupLibrariesByServer => _groupLibrariesByServer;
|
||||
|
||||
bool get alwaysKeepSidebarOpen => _alwaysKeepSidebarOpen;
|
||||
|
||||
bool get showUnwatchedCount => _showUnwatchedCount;
|
||||
|
||||
bool get showEpisodeNumberOnCards => _showEpisodeNumberOnCards;
|
||||
|
||||
bool get hideSpoilers => _hideSpoilers;
|
||||
|
||||
bool get showNavBarLabels => _showNavBarLabels;
|
||||
|
||||
bool get liveTvDefaultFavorites => _liveTvDefaultFavorites;
|
||||
|
||||
bool get autoHidePerformanceOverlay => _autoHidePerformanceOverlay;
|
||||
|
||||
TranscodeQualityPreset get defaultQualityPreset => _defaultQualityPreset;
|
||||
|
||||
/// Helper to update a setting: ensures init, deduplicates, persists, notifies.
|
||||
Future<void> _updateSetting<T>({
|
||||
required T current,
|
||||
required T value,
|
||||
required void Function(T) setLocal,
|
||||
required Future<void> Function(T) persist,
|
||||
}) async {
|
||||
Future<void> _set<T>(Pref<T> pref, T value) async {
|
||||
if (!_isInitialized) await _initializeSettings();
|
||||
if (current != value) {
|
||||
setLocal(value);
|
||||
await persist(value);
|
||||
notifyListeners();
|
||||
}
|
||||
if (_settingsService!.read(pref) == value) return;
|
||||
await _settingsService!.write(pref, value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setLibraryDensity(int density) => _updateSetting(
|
||||
current: _libraryDensity,
|
||||
value: density.clamp(LibraryDensity.min, LibraryDensity.max),
|
||||
setLocal: (v) => _libraryDensity = v,
|
||||
persist: _settingsService!.setLibraryDensity,
|
||||
);
|
||||
int get libraryDensity => _read(SettingsService.libraryDensity, LibraryDensity.defaultValue);
|
||||
ViewMode get viewMode => _read(SettingsService.viewMode, ViewMode.grid);
|
||||
EpisodePosterMode get episodePosterMode =>
|
||||
_read(SettingsService.episodePosterMode, EpisodePosterMode.episodeThumbnail);
|
||||
bool get showHeroSection => _read(SettingsService.showHeroSection, true);
|
||||
bool get useGlobalHubs => _read(SettingsService.useGlobalHubs, true);
|
||||
bool get showServerNameOnHubs => _read(SettingsService.showServerNameOnHubs, false);
|
||||
bool get groupLibrariesByServer => _read(SettingsService.groupLibrariesByServer, true);
|
||||
bool get alwaysKeepSidebarOpen => _read(SettingsService.alwaysKeepSidebarOpen, false);
|
||||
bool get showUnwatchedCount => _read(SettingsService.showUnwatchedCount, true);
|
||||
bool get showEpisodeNumberOnCards => _read(SettingsService.showEpisodeNumberOnCards, true);
|
||||
bool get hideSpoilers => _read(SettingsService.hideSpoilers, false);
|
||||
bool get showNavBarLabels => _read(SettingsService.showNavBarLabels, true);
|
||||
bool get liveTvDefaultFavorites => _read(SettingsService.liveTvDefaultFavorites, false);
|
||||
bool get autoHidePerformanceOverlay => _read(SettingsService.autoHidePerformanceOverlay, true);
|
||||
TranscodeQualityPreset get defaultQualityPreset =>
|
||||
TranscodeQualityPreset.fromStorage(_read(SettingsService.defaultQualityPreset, 'original'));
|
||||
|
||||
Future<void> setViewMode(ViewMode mode) => _updateSetting(
|
||||
current: _viewMode,
|
||||
value: mode,
|
||||
setLocal: (v) => _viewMode = v,
|
||||
persist: _settingsService!.setViewMode,
|
||||
);
|
||||
|
||||
Future<void> setEpisodePosterMode(EpisodePosterMode mode) => _updateSetting(
|
||||
current: _episodePosterMode,
|
||||
value: mode,
|
||||
setLocal: (v) => _episodePosterMode = v,
|
||||
persist: _settingsService!.setEpisodePosterMode,
|
||||
);
|
||||
|
||||
Future<void> setShowHeroSection(bool value) => _updateSetting(
|
||||
current: _showHeroSection,
|
||||
value: value,
|
||||
setLocal: (v) => _showHeroSection = v,
|
||||
persist: _settingsService!.setShowHeroSection,
|
||||
);
|
||||
|
||||
Future<void> setUseGlobalHubs(bool value) => _updateSetting(
|
||||
current: _useGlobalHubs,
|
||||
value: value,
|
||||
setLocal: (v) => _useGlobalHubs = v,
|
||||
persist: _settingsService!.setUseGlobalHubs,
|
||||
);
|
||||
|
||||
Future<void> setShowServerNameOnHubs(bool value) => _updateSetting(
|
||||
current: _showServerNameOnHubs,
|
||||
value: value,
|
||||
setLocal: (v) => _showServerNameOnHubs = v,
|
||||
persist: _settingsService!.setShowServerNameOnHubs,
|
||||
);
|
||||
|
||||
Future<void> setGroupLibrariesByServer(bool value) => _updateSetting(
|
||||
current: _groupLibrariesByServer,
|
||||
value: value,
|
||||
setLocal: (v) => _groupLibrariesByServer = v,
|
||||
persist: _settingsService!.setGroupLibrariesByServer,
|
||||
);
|
||||
|
||||
Future<void> setAlwaysKeepSidebarOpen(bool value) => _updateSetting(
|
||||
current: _alwaysKeepSidebarOpen,
|
||||
value: value,
|
||||
setLocal: (v) => _alwaysKeepSidebarOpen = v,
|
||||
persist: _settingsService!.setAlwaysKeepSidebarOpen,
|
||||
);
|
||||
|
||||
Future<void> setShowUnwatchedCount(bool value) => _updateSetting(
|
||||
current: _showUnwatchedCount,
|
||||
value: value,
|
||||
setLocal: (v) => _showUnwatchedCount = v,
|
||||
persist: _settingsService!.setShowUnwatchedCount,
|
||||
);
|
||||
|
||||
Future<void> setShowEpisodeNumberOnCards(bool value) => _updateSetting(
|
||||
current: _showEpisodeNumberOnCards,
|
||||
value: value,
|
||||
setLocal: (v) => _showEpisodeNumberOnCards = v,
|
||||
persist: _settingsService!.setShowEpisodeNumberOnCards,
|
||||
);
|
||||
|
||||
Future<void> setHideSpoilers(bool value) => _updateSetting(
|
||||
current: _hideSpoilers,
|
||||
value: value,
|
||||
setLocal: (v) => _hideSpoilers = v,
|
||||
persist: _settingsService!.setHideSpoilers,
|
||||
);
|
||||
|
||||
Future<void> setShowNavBarLabels(bool value) => _updateSetting(
|
||||
current: _showNavBarLabels,
|
||||
value: value,
|
||||
setLocal: (v) => _showNavBarLabels = v,
|
||||
persist: _settingsService!.setShowNavBarLabels,
|
||||
);
|
||||
|
||||
Future<void> setLiveTvDefaultFavorites(bool value) => _updateSetting(
|
||||
current: _liveTvDefaultFavorites,
|
||||
value: value,
|
||||
setLocal: (v) => _liveTvDefaultFavorites = v,
|
||||
persist: _settingsService!.setLiveTvDefaultFavorites,
|
||||
);
|
||||
|
||||
Future<void> setAutoHidePerformanceOverlay(bool value) => _updateSetting(
|
||||
current: _autoHidePerformanceOverlay,
|
||||
value: value,
|
||||
setLocal: (v) => _autoHidePerformanceOverlay = v,
|
||||
persist: _settingsService!.setAutoHidePerformanceOverlay,
|
||||
);
|
||||
|
||||
Future<void> setDefaultQualityPreset(TranscodeQualityPreset preset) => _updateSetting(
|
||||
current: _defaultQualityPreset,
|
||||
value: preset,
|
||||
setLocal: (v) => _defaultQualityPreset = v,
|
||||
persist: (v) => _settingsService!.setDefaultQualityPreset(v.storageKey),
|
||||
);
|
||||
Future<void> setLibraryDensity(int density) =>
|
||||
_set(SettingsService.libraryDensity, density.clamp(LibraryDensity.min, LibraryDensity.max));
|
||||
Future<void> setViewMode(ViewMode mode) => _set(SettingsService.viewMode, mode);
|
||||
Future<void> setEpisodePosterMode(EpisodePosterMode mode) => _set(SettingsService.episodePosterMode, mode);
|
||||
Future<void> setShowHeroSection(bool value) => _set(SettingsService.showHeroSection, value);
|
||||
Future<void> setUseGlobalHubs(bool value) => _set(SettingsService.useGlobalHubs, value);
|
||||
Future<void> setShowServerNameOnHubs(bool value) => _set(SettingsService.showServerNameOnHubs, value);
|
||||
Future<void> setGroupLibrariesByServer(bool value) => _set(SettingsService.groupLibrariesByServer, value);
|
||||
Future<void> setAlwaysKeepSidebarOpen(bool value) => _set(SettingsService.alwaysKeepSidebarOpen, value);
|
||||
Future<void> setShowUnwatchedCount(bool value) => _set(SettingsService.showUnwatchedCount, value);
|
||||
Future<void> setShowEpisodeNumberOnCards(bool value) => _set(SettingsService.showEpisodeNumberOnCards, value);
|
||||
Future<void> setHideSpoilers(bool value) => _set(SettingsService.hideSpoilers, value);
|
||||
Future<void> setShowNavBarLabels(bool value) => _set(SettingsService.showNavBarLabels, value);
|
||||
Future<void> setLiveTvDefaultFavorites(bool value) => _set(SettingsService.liveTvDefaultFavorites, value);
|
||||
Future<void> setAutoHidePerformanceOverlay(bool value) => _set(SettingsService.autoHidePerformanceOverlay, value);
|
||||
Future<void> setDefaultQualityPreset(TranscodeQualityPreset preset) =>
|
||||
_set(SettingsService.defaultQualityPreset, preset.storageKey);
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ class ShaderProvider extends ChangeNotifier {
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
|
||||
// Load custom presets from storage
|
||||
final customData = _settingsService.getCustomShaderPresets();
|
||||
final customData = _settingsService.read(SettingsService.customShaderPresets);
|
||||
_customPresets = customData.map((json) => ShaderPreset.fromJson(json)).toList();
|
||||
|
||||
final presetId = _settingsService.getGlobalShaderPreset();
|
||||
final presetId = _settingsService.read(SettingsService.globalShaderPreset);
|
||||
_savedPreset = findPresetById(presetId) ?? ShaderPreset.none;
|
||||
_currentPreset = _savedPreset;
|
||||
|
||||
@@ -62,7 +62,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
Future<void> setPreset(ShaderPreset preset) async {
|
||||
_savedPreset = preset;
|
||||
_currentPreset = preset;
|
||||
await _settingsService.setGlobalShaderPreset(preset.id);
|
||||
await _settingsService.write(SettingsService.globalShaderPreset, preset.id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
if (_currentPreset.id == preset.id || _savedPreset.id == preset.id) {
|
||||
_savedPreset = ShaderPreset.none;
|
||||
_currentPreset = ShaderPreset.none;
|
||||
await _settingsService.setGlobalShaderPreset(ShaderPreset.none.id);
|
||||
await _settingsService.write(SettingsService.globalShaderPreset, ShaderPreset.none.id);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
@@ -108,7 +108,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
|
||||
Future<void> _saveCustomPresets() async {
|
||||
final data = _customPresets.map((p) => p.toJson()).toList();
|
||||
await _settingsService.setCustomShaderPresets(data);
|
||||
await _settingsService.write(SettingsService.customShaderPresets, data);
|
||||
}
|
||||
|
||||
/// Reset to default (no shaders)
|
||||
|
||||
@@ -33,7 +33,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
|
||||
Future<void> _initializeSettings() async {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
_themeMode = _settingsService.getThemeMode();
|
||||
_themeMode = _settingsService.read(settings.SettingsService.themeMode);
|
||||
_updateSplashTheme(_themeMode);
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -79,7 +79,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
Future<void> setThemeMode(settings.ThemeMode mode) async {
|
||||
if (_themeMode != mode) {
|
||||
_themeMode = mode;
|
||||
await _settingsService.setThemeMode(mode);
|
||||
await _settingsService.write(settings.SettingsService.themeMode, mode);
|
||||
_updateSplashTheme(mode);
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
/// resets that change persisted settings outside this provider.
|
||||
Future<void> reload() async {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
final mode = _settingsService.getThemeMode();
|
||||
final mode = _settingsService.read(settings.SettingsService.themeMode);
|
||||
_themeMode = mode;
|
||||
_updateSplashTheme(mode);
|
||||
notifyListeners();
|
||||
|
||||
@@ -199,7 +199,8 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
final needsInitial = userProfileProvider.needsInitialProfileSelection;
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
final requireOnOpen = settingsService.getRequireProfileSelectionOnOpen() && userProfileProvider.hasMultipleUsers;
|
||||
final requireOnOpen =
|
||||
settingsService.read(SettingsService.requireProfileSelectionOnOpen) && userProfileProvider.hasMultipleUsers;
|
||||
|
||||
if (!needsInitial && !requireOnOpen) return;
|
||||
|
||||
@@ -214,7 +215,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
if (!mounted) return;
|
||||
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (!settingsService.getAutoCheckUpdatesOnStartup()) return;
|
||||
if (!settingsService.read(SettingsService.autoCheckUpdatesOnStartup)) return;
|
||||
|
||||
// Native updater (Sparkle/WinSparkle) handles everything — skip Flutter dialog
|
||||
if (UpdateService.useNativeUpdater) {
|
||||
@@ -449,7 +450,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
Future<void> _autoStartCompanionRemoteServer(CompanionRemoteProvider companionRemote) async {
|
||||
try {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (!settings.getEnableCompanionRemoteServer()) return;
|
||||
if (!settings.read(SettingsService.enableCompanionRemoteServer)) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final home = context.read<UserProfileProvider>().home;
|
||||
@@ -510,7 +511,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
|
||||
Future<void> _showProfileSelectionOnResume() async {
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (!settingsService.getRequireProfileSelectionOnOpen()) return;
|
||||
if (!settingsService.read(SettingsService.requireProfileSelectionOnOpen)) return;
|
||||
if (!mounted) return;
|
||||
|
||||
final userProfileProvider = context.read<UserProfileProvider>();
|
||||
@@ -682,7 +683,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
return handleBackKeyAction(event, () async {
|
||||
if (PlatformDetector.isTV()) {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (settings.getConfirmExitOnBack() && mounted) {
|
||||
if (settings.read(SettingsService.confirmExitOnBack) && mounted) {
|
||||
final result = await showConfirmDialogWithCheckbox(
|
||||
context,
|
||||
title: t.common.exitConfirmTitle,
|
||||
@@ -691,7 +692,7 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
||||
checkboxLabel: t.common.dontAskAgain,
|
||||
);
|
||||
if (result.checked) {
|
||||
await settings.setConfirmExitOnBack(false);
|
||||
await settings.write(SettingsService.confirmExitOnBack, false);
|
||||
}
|
||||
if (!result.confirmed) return;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ class AppearanceSettingsScreen extends StatefulWidget {
|
||||
class _AppearanceSettingsScreenState extends State<AppearanceSettingsScreen> {
|
||||
late settings.SettingsService _settingsService;
|
||||
bool _isLoading = true;
|
||||
bool _requireProfileSelectionOnOpen = false;
|
||||
bool _confirmExitOnBack = true;
|
||||
bool _forceTvMode = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -39,12 +36,7 @@ class _AppearanceSettingsScreenState extends State<AppearanceSettingsScreen> {
|
||||
Future<void> _loadSettings() async {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_requireProfileSelectionOnOpen = _settingsService.getRequireProfileSelectionOnOpen();
|
||||
_confirmExitOnBack = _settingsService.getConfirmExitOnBack();
|
||||
_forceTvMode = _settingsService.getForceTvMode();
|
||||
_isLoading = false;
|
||||
});
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -135,7 +127,7 @@ class _AppearanceSettingsScreenState extends State<AppearanceSettingsScreen> {
|
||||
currentValue: LocaleSettings.currentLocale,
|
||||
);
|
||||
if (value != null) {
|
||||
await _settingsService.setAppLocale(value);
|
||||
await _settingsService.write(settings.SettingsService.appLocale, value);
|
||||
LocaleSettings.setLocale(value);
|
||||
_restartApp();
|
||||
}
|
||||
@@ -333,45 +325,55 @@ class _AppearanceSettingsScreenState extends State<AppearanceSettingsScreen> {
|
||||
return Consumer<UserProfileProvider>(
|
||||
builder: (context, userProfileProvider, child) {
|
||||
if (!userProfileProvider.hasMultipleUsers) return const SizedBox.shrink();
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.person_rounded, fill: 1),
|
||||
title: Text(t.settings.requireProfileSelectionOnOpen),
|
||||
subtitle: Text(t.settings.requireProfileSelectionOnOpenDescription),
|
||||
value: _requireProfileSelectionOnOpen,
|
||||
onChanged: (value) async {
|
||||
setState(() => _requireProfileSelectionOnOpen = value);
|
||||
await _settingsService.setRequireProfileSelectionOnOpen(value);
|
||||
},
|
||||
return _buildListenableSwitch(
|
||||
icon: Symbols.person_rounded,
|
||||
title: t.settings.requireProfileSelectionOnOpen,
|
||||
subtitle: t.settings.requireProfileSelectionOnOpenDescription,
|
||||
pref: settings.SettingsService.requireProfileSelectionOnOpen,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConfirmExitOnBack() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.exit_to_app_rounded, fill: 1),
|
||||
title: Text(t.settings.confirmExitOnBack),
|
||||
subtitle: Text(t.settings.confirmExitOnBackDescription),
|
||||
value: _confirmExitOnBack,
|
||||
onChanged: (value) async {
|
||||
setState(() => _confirmExitOnBack = value);
|
||||
await _settingsService.setConfirmExitOnBack(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
Widget _buildConfirmExitOnBack() => _buildListenableSwitch(
|
||||
icon: Symbols.exit_to_app_rounded,
|
||||
title: t.settings.confirmExitOnBack,
|
||||
subtitle: t.settings.confirmExitOnBackDescription,
|
||||
pref: settings.SettingsService.confirmExitOnBack,
|
||||
);
|
||||
|
||||
Widget _buildForceTvMode() {
|
||||
return SwitchListTile(
|
||||
secondary: const AppIcon(Symbols.tv_rounded, fill: 1),
|
||||
title: Text(t.settings.forceTvMode),
|
||||
subtitle: Text(t.settings.forceTvModeDescription),
|
||||
value: _forceTvMode,
|
||||
onChanged: (value) async {
|
||||
setState(() => _forceTvMode = value);
|
||||
await _settingsService.setForceTvMode(value);
|
||||
TvDetectionService.setForceTVSync(value);
|
||||
if (mounted) _restartApp();
|
||||
},
|
||||
Widget _buildForceTvMode() => _buildListenableSwitch(
|
||||
icon: Symbols.tv_rounded,
|
||||
title: t.settings.forceTvMode,
|
||||
subtitle: t.settings.forceTvModeDescription,
|
||||
pref: settings.SettingsService.forceTvMode,
|
||||
onAfterWrite: (value) {
|
||||
TvDetectionService.setForceTVSync(value);
|
||||
if (mounted) _restartApp();
|
||||
},
|
||||
);
|
||||
|
||||
/// Generic bool toggle that listens to a [Pref] directly via [ValueNotifier].
|
||||
/// No local state; rebuilds across the app stay consistent.
|
||||
Widget _buildListenableSwitch({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required settings.Pref<bool> pref,
|
||||
void Function(bool)? onAfterWrite,
|
||||
}) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: _settingsService.listenable(pref),
|
||||
builder: (_, value, _) => SwitchListTile(
|
||||
secondary: AppIcon(icon, fill: 1),
|
||||
title: Text(title),
|
||||
subtitle: Text(subtitle),
|
||||
value: value,
|
||||
onChanged: (v) async {
|
||||
await _settingsService.write(pref, v);
|
||||
onAfterWrite?.call(v);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ class _ExternalPlayerScreenState extends State<ExternalPlayerScreen> {
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_useExternalPlayer = _settingsService.getUseExternalPlayer();
|
||||
_selectedPlayer = _settingsService.getSelectedExternalPlayer();
|
||||
_customPlayers = _settingsService.getCustomExternalPlayers();
|
||||
_useExternalPlayer = _settingsService.read(SettingsService.useExternalPlayer);
|
||||
_selectedPlayer = _settingsService.read(SettingsService.selectedExternalPlayer);
|
||||
_customPlayers = _settingsService.read(SettingsService.customExternalPlayers);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class _ExternalPlayerScreenState extends State<ExternalPlayerScreen> {
|
||||
value: _useExternalPlayer,
|
||||
onChanged: (value) async {
|
||||
setState(() => _useExternalPlayer = value);
|
||||
await _settingsService.setUseExternalPlayer(value);
|
||||
await _settingsService.write(SettingsService.useExternalPlayer, value);
|
||||
},
|
||||
),
|
||||
if (_useExternalPlayer) ...[
|
||||
@@ -132,7 +132,7 @@ class _ExternalPlayerScreenState extends State<ExternalPlayerScreen> {
|
||||
),
|
||||
onTap: () async {
|
||||
setState(() => _selectedPlayer = player);
|
||||
await _settingsService.setSelectedExternalPlayer(player);
|
||||
await _settingsService.write(SettingsService.selectedExternalPlayer, player);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -142,7 +142,7 @@ class _ExternalPlayerScreenState extends State<ExternalPlayerScreen> {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_customPlayers.removeWhere((p) => p.id == player.id);
|
||||
_selectedPlayer = _settingsService.getSelectedExternalPlayer();
|
||||
_selectedPlayer = _settingsService.read(SettingsService.selectedExternalPlayer);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -255,7 +255,10 @@ class _ExternalPlayerScreenState extends State<ExternalPlayerScreen> {
|
||||
type: selectedType,
|
||||
);
|
||||
|
||||
await _settingsService.addCustomExternalPlayer(newPlayer);
|
||||
await _settingsService.write(SettingsService.customExternalPlayers, [
|
||||
..._settingsService.read(SettingsService.customExternalPlayers),
|
||||
newPlayer,
|
||||
]);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_customPlayers.add(newPlayer);
|
||||
|
||||
@@ -47,14 +47,14 @@ class _MpvConfigScreenState extends State<MpvConfigScreen> {
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_textController.text = _settingsService.getMpvConfigText();
|
||||
_presets = _settingsService.getMpvPresets();
|
||||
_textController.text = _settingsService.read(SettingsService.mpvConfigText);
|
||||
_presets = _settingsService.read(SettingsService.mpvPresets);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveText() async {
|
||||
await _settingsService.setMpvConfigText(_textController.text);
|
||||
await _settingsService.write(SettingsService.mpvConfigText, _textController.text);
|
||||
}
|
||||
|
||||
Future<void> _showSavePresetDialog() async {
|
||||
@@ -71,7 +71,7 @@ class _MpvConfigScreenState extends State<MpvConfigScreen> {
|
||||
await _settingsService.saveMpvPreset(name.trim(), _textController.text);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_presets = _settingsService.getMpvPresets();
|
||||
_presets = _settingsService.read(SettingsService.mpvPresets);
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
@@ -84,7 +84,7 @@ class _MpvConfigScreenState extends State<MpvConfigScreen> {
|
||||
await _settingsService.loadMpvPreset(preset.name);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_textController.text = _settingsService.getMpvConfigText();
|
||||
_textController.text = _settingsService.read(SettingsService.mpvConfigText);
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
@@ -103,7 +103,7 @@ class _MpvConfigScreenState extends State<MpvConfigScreen> {
|
||||
await _settingsService.deleteMpvPreset(preset.name);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_presets = _settingsService.getMpvPresets();
|
||||
_presets = _settingsService.read(SettingsService.mpvPresets);
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
|
||||
@@ -73,32 +73,34 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_enableHardwareDecoding = _settingsService.getEnableHardwareDecoding();
|
||||
_bufferSize = _settingsService.getBufferSize();
|
||||
_seekTimeSmall = _settingsService.getSeekTimeSmall();
|
||||
_seekTimeLarge = _settingsService.getSeekTimeLarge();
|
||||
_rewindOnResume = _settingsService.getRewindOnResume();
|
||||
_sleepTimerDuration = _settingsService.getSleepTimerDuration();
|
||||
_rememberTrackSelections = _settingsService.getRememberTrackSelections();
|
||||
_clickVideoTogglesPlayback = _settingsService.getClickVideoTogglesPlayback();
|
||||
_autoSkipIntro = _settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = _settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = _settingsService.getAutoSkipDelay();
|
||||
_introPattern = _settingsService.getIntroPattern();
|
||||
_creditsPattern = _settingsService.getCreditsPattern();
|
||||
_maxVolume = _settingsService.getMaxVolume();
|
||||
_enableDiscordRPC = _settingsService.getEnableDiscordRPC();
|
||||
_enableCompanionRemoteServer = _settingsService.getEnableCompanionRemoteServer();
|
||||
_autoPip = _settingsService.getAutoPip();
|
||||
_matchContentFrameRate = _settingsService.getMatchContentFrameRate();
|
||||
_matchRefreshRate = _settingsService.getMatchRefreshRate();
|
||||
_matchDynamicRange = _settingsService.getMatchDynamicRange();
|
||||
_displaySwitchDelay = _settingsService.getDisplaySwitchDelay();
|
||||
_tunneledPlayback = _settingsService.getTunneledPlayback();
|
||||
_useExoPlayer = _settingsService.getUseExoPlayer();
|
||||
_useExternalPlayer = _settingsService.getUseExternalPlayer();
|
||||
_selectedExternalPlayerName = _settingsService.getSelectedExternalPlayer().name;
|
||||
_defaultQualityPreset = TranscodeQualityPreset.fromStorage(_settingsService.getDefaultQualityPreset());
|
||||
_enableHardwareDecoding = _settingsService.read(settings.SettingsService.enableHardwareDecoding);
|
||||
_bufferSize = _settingsService.read(settings.SettingsService.bufferSize);
|
||||
_seekTimeSmall = _settingsService.read(settings.SettingsService.seekTimeSmall);
|
||||
_seekTimeLarge = _settingsService.read(settings.SettingsService.seekTimeLarge);
|
||||
_rewindOnResume = _settingsService.read(settings.SettingsService.rewindOnResume);
|
||||
_sleepTimerDuration = _settingsService.read(settings.SettingsService.sleepTimerDuration);
|
||||
_rememberTrackSelections = _settingsService.read(settings.SettingsService.rememberTrackSelections);
|
||||
_clickVideoTogglesPlayback = _settingsService.read(settings.SettingsService.clickVideoTogglesPlayback);
|
||||
_autoSkipIntro = _settingsService.read(settings.SettingsService.autoSkipIntro);
|
||||
_autoSkipCredits = _settingsService.read(settings.SettingsService.autoSkipCredits);
|
||||
_autoSkipDelay = _settingsService.read(settings.SettingsService.autoSkipDelay);
|
||||
_introPattern = _settingsService.read(settings.SettingsService.introPattern);
|
||||
_creditsPattern = _settingsService.read(settings.SettingsService.creditsPattern);
|
||||
_maxVolume = _settingsService.read(settings.SettingsService.maxVolume);
|
||||
_enableDiscordRPC = _settingsService.read(settings.SettingsService.enableDiscordRPC);
|
||||
_enableCompanionRemoteServer = _settingsService.read(settings.SettingsService.enableCompanionRemoteServer);
|
||||
_autoPip = _settingsService.read(settings.SettingsService.autoPip);
|
||||
_matchContentFrameRate = _settingsService.read(settings.SettingsService.matchContentFrameRate);
|
||||
_matchRefreshRate = _settingsService.read(settings.SettingsService.matchRefreshRate);
|
||||
_matchDynamicRange = _settingsService.read(settings.SettingsService.matchDynamicRange);
|
||||
_displaySwitchDelay = _settingsService.read(settings.SettingsService.displaySwitchDelay);
|
||||
_tunneledPlayback = _settingsService.read(settings.SettingsService.tunneledPlayback);
|
||||
_useExoPlayer = _settingsService.read(settings.SettingsService.useExoPlayer);
|
||||
_useExternalPlayer = _settingsService.read(settings.SettingsService.useExternalPlayer);
|
||||
_selectedExternalPlayerName = _settingsService.read(settings.SettingsService.selectedExternalPlayer).name;
|
||||
_defaultQualityPreset = TranscodeQualityPreset.fromStorage(
|
||||
_settingsService.read(settings.SettingsService.defaultQualityPreset),
|
||||
);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -182,7 +184,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
selected: _useExoPlayer,
|
||||
onChanged: (value) async {
|
||||
setState(() => _useExoPlayer = value);
|
||||
await _settingsService.setUseExoPlayer(value);
|
||||
await _settingsService.write(settings.SettingsService.useExoPlayer, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -198,8 +200,8 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
final s = await settings.SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_useExternalPlayer = s.getUseExternalPlayer();
|
||||
_selectedExternalPlayerName = s.getSelectedExternalPlayer().name;
|
||||
_useExternalPlayer = s.read(settings.SettingsService.useExternalPlayer);
|
||||
_selectedExternalPlayerName = s.read(settings.SettingsService.selectedExternalPlayer).name;
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -213,7 +215,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _enableHardwareDecoding,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableHardwareDecoding = value);
|
||||
await _settingsService.setEnableHardwareDecoding(value);
|
||||
await _settingsService.write(settings.SettingsService.enableHardwareDecoding, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -226,7 +228,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _autoPip,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoPip = value);
|
||||
await _settingsService.setAutoPip(value);
|
||||
await _settingsService.write(settings.SettingsService.autoPip, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -239,7 +241,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _matchContentFrameRate,
|
||||
onChanged: (value) async {
|
||||
setState(() => _matchContentFrameRate = value);
|
||||
await _settingsService.setMatchContentFrameRate(value);
|
||||
await _settingsService.write(settings.SettingsService.matchContentFrameRate, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -252,7 +254,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _matchRefreshRate,
|
||||
onChanged: (value) async {
|
||||
setState(() => _matchRefreshRate = value);
|
||||
await _settingsService.setMatchRefreshRate(value);
|
||||
await _settingsService.write(settings.SettingsService.matchRefreshRate, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -265,7 +267,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _matchDynamicRange,
|
||||
onChanged: (value) async {
|
||||
setState(() => _matchDynamicRange = value);
|
||||
await _settingsService.setMatchDynamicRange(value);
|
||||
await _settingsService.write(settings.SettingsService.matchDynamicRange, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -286,7 +288,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
currentValue: _displaySwitchDelay,
|
||||
onSave: (value) async {
|
||||
setState(() => _displaySwitchDelay = value);
|
||||
await _settingsService.setDisplaySwitchDelay(value);
|
||||
await _settingsService.write(settings.SettingsService.displaySwitchDelay, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -300,7 +302,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _tunneledPlayback,
|
||||
onChanged: (value) async {
|
||||
setState(() => _tunneledPlayback = value);
|
||||
await _settingsService.setTunneledPlayback(value);
|
||||
await _settingsService.write(settings.SettingsService.tunneledPlayback, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -323,7 +325,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_defaultQualityPreset = value;
|
||||
_settingsService.setDefaultQualityPreset(value.storageKey);
|
||||
_settingsService.write(settings.SettingsService.defaultQualityPreset, value.storageKey);
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -351,7 +353,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_bufferSize = value;
|
||||
_settingsService.setBufferSize(value);
|
||||
_settingsService.write(settings.SettingsService.bufferSize, value);
|
||||
});
|
||||
if (Platform.isAndroid && value > 0) {
|
||||
final heapMB = await PlayerAndroid.getHeapSize();
|
||||
@@ -409,7 +411,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_seekTimeSmall = value;
|
||||
_settingsService.setSeekTimeSmall(value);
|
||||
_settingsService.write(settings.SettingsService.seekTimeSmall, value);
|
||||
});
|
||||
await _keyboardService?.refreshFromStorage();
|
||||
},
|
||||
@@ -434,7 +436,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_seekTimeLarge = value;
|
||||
_settingsService.setSeekTimeLarge(value);
|
||||
_settingsService.write(settings.SettingsService.seekTimeLarge, value);
|
||||
});
|
||||
await _keyboardService?.refreshFromStorage();
|
||||
},
|
||||
@@ -459,7 +461,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
onSave: (value) async {
|
||||
setState(() {
|
||||
_rewindOnResume = value;
|
||||
_settingsService.setRewindOnResume(value);
|
||||
_settingsService.write(settings.SettingsService.rewindOnResume, value);
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -482,7 +484,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
currentValue: _sleepTimerDuration,
|
||||
onSave: (value) async {
|
||||
setState(() => _sleepTimerDuration = value);
|
||||
await _settingsService.setSleepTimerDuration(value);
|
||||
await _settingsService.write(settings.SettingsService.sleepTimerDuration, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -504,7 +506,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
currentValue: _maxVolume,
|
||||
onSave: (value) async {
|
||||
setState(() => _maxVolume = value);
|
||||
await _settingsService.setMaxVolume(value);
|
||||
await _settingsService.write(settings.SettingsService.maxVolume, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -520,7 +522,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _enableDiscordRPC,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableDiscordRPC = value);
|
||||
await _settingsService.setEnableDiscordRPC(value);
|
||||
await _settingsService.write(settings.SettingsService.enableDiscordRPC, value);
|
||||
await DiscordRPCService.instance.setEnabled(value);
|
||||
},
|
||||
);
|
||||
@@ -534,7 +536,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _enableCompanionRemoteServer,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableCompanionRemoteServer = value);
|
||||
await _settingsService.setEnableCompanionRemoteServer(value);
|
||||
await _settingsService.write(settings.SettingsService.enableCompanionRemoteServer, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -547,7 +549,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _rememberTrackSelections,
|
||||
onChanged: (value) async {
|
||||
setState(() => _rememberTrackSelections = value);
|
||||
await _settingsService.setRememberTrackSelections(value);
|
||||
await _settingsService.write(settings.SettingsService.rememberTrackSelections, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -560,7 +562,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _clickVideoTogglesPlayback,
|
||||
onChanged: (value) async {
|
||||
setState(() => _clickVideoTogglesPlayback = value);
|
||||
await _settingsService.setClickVideoTogglesPlayback(value);
|
||||
await _settingsService.write(settings.SettingsService.clickVideoTogglesPlayback, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -575,7 +577,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _autoSkipIntro,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoSkipIntro = value);
|
||||
await _settingsService.setAutoSkipIntro(value);
|
||||
await _settingsService.write(settings.SettingsService.autoSkipIntro, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -588,7 +590,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
value: _autoSkipCredits,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoSkipCredits = value);
|
||||
await _settingsService.setAutoSkipCredits(value);
|
||||
await _settingsService.write(settings.SettingsService.autoSkipCredits, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -609,7 +611,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
currentValue: _autoSkipDelay,
|
||||
onSave: (value) async {
|
||||
setState(() => _autoSkipDelay = value);
|
||||
await _settingsService.setAutoSkipDelay(value);
|
||||
await _settingsService.write(settings.SettingsService.autoSkipDelay, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -628,7 +630,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
defaultValue: settings.SettingsService.defaultIntroPattern,
|
||||
onSave: (value) async {
|
||||
setState(() => _introPattern = value);
|
||||
await _settingsService.setIntroPattern(value);
|
||||
await _settingsService.write(settings.SettingsService.introPattern, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -647,7 +649,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
||||
defaultValue: settings.SettingsService.defaultCreditsPattern,
|
||||
onSave: (value) async {
|
||||
setState(() => _creditsPattern = value);
|
||||
await _settingsService.setCreditsPattern(value);
|
||||
await _settingsService.write(settings.SettingsService.creditsPattern, value);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -136,13 +136,13 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_crashReporting = _settingsService.getCrashReporting();
|
||||
_enableDebugLogging = _settingsService.getEnableDebugLogging();
|
||||
_downloadOnWifiOnly = _settingsService.getDownloadOnWifiOnly();
|
||||
_autoRemoveWatchedDownloads = _settingsService.getAutoRemoveWatchedDownloads();
|
||||
_autoCheckUpdatesOnStartup = _settingsService.getAutoCheckUpdatesOnStartup();
|
||||
_videoPlayerNavigationEnabled = _settingsService.getVideoPlayerNavigationEnabled();
|
||||
_customRelayUrl = _settingsService.getCustomRelayUrl();
|
||||
_crashReporting = _settingsService.read(settings.SettingsService.crashReporting);
|
||||
_enableDebugLogging = _settingsService.read(settings.SettingsService.enableDebugLogging);
|
||||
_downloadOnWifiOnly = _settingsService.read(settings.SettingsService.downloadOnWifiOnly);
|
||||
_autoRemoveWatchedDownloads = _settingsService.read(settings.SettingsService.autoRemoveWatchedDownloads);
|
||||
_autoCheckUpdatesOnStartup = _settingsService.read(settings.SettingsService.autoCheckUpdatesOnStartup);
|
||||
_videoPlayerNavigationEnabled = _settingsService.read(settings.SettingsService.videoPlayerNavigationEnabled);
|
||||
_customRelayUrl = _settingsService.read(settings.SettingsService.customRelayUrl);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -312,7 +312,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _downloadOnWifiOnly,
|
||||
onChanged: (value) async {
|
||||
setState(() => _downloadOnWifiOnly = value);
|
||||
await _settingsService.setDownloadOnWifiOnly(value);
|
||||
await _settingsService.write(settings.SettingsService.downloadOnWifiOnly, value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
@@ -323,7 +323,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _autoRemoveWatchedDownloads,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoRemoveWatchedDownloads = value);
|
||||
await _settingsService.setAutoRemoveWatchedDownloads(value);
|
||||
await _settingsService.write(settings.SettingsService.autoRemoveWatchedDownloads, value);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -358,7 +358,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _videoPlayerNavigationEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() => _videoPlayerNavigationEnabled = value);
|
||||
await _settingsService.setVideoPlayerNavigationEnabled(value);
|
||||
await _settingsService.write(settings.SettingsService.videoPlayerNavigationEnabled, value);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -386,7 +386,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _crashReporting,
|
||||
onChanged: (value) async {
|
||||
setState(() => _crashReporting = value);
|
||||
await _settingsService.setCrashReporting(value);
|
||||
await _settingsService.write(settings.SettingsService.crashReporting, value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
@@ -397,7 +397,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _enableDebugLogging,
|
||||
onChanged: (value) async {
|
||||
setState(() => _enableDebugLogging = value);
|
||||
await _settingsService.setEnableDebugLogging(value);
|
||||
await _settingsService.write(settings.SettingsService.enableDebugLogging, value);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
@@ -486,7 +486,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
value: _autoCheckUpdatesOnStartup,
|
||||
onChanged: (value) async {
|
||||
setState(() => _autoCheckUpdatesOnStartup = value);
|
||||
await _settingsService.setAutoCheckUpdatesOnStartup(value);
|
||||
await _settingsService.write(settings.SettingsService.autoCheckUpdatesOnStartup, value);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -625,7 +625,8 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
}
|
||||
}
|
||||
|
||||
await _settingsService.setCustomDownloadPath(selectedPath, type: pathType);
|
||||
await _settingsService.write(settings.SettingsService.customDownloadPath, selectedPath);
|
||||
await _settingsService.write(settings.SettingsService.customDownloadPathType, pathType);
|
||||
await DownloadStorageService.instance.refreshCustomPath();
|
||||
|
||||
if (mounted) {
|
||||
@@ -642,7 +643,8 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
}
|
||||
|
||||
Future<void> _resetDownloadLocation() async {
|
||||
await _settingsService.setCustomDownloadPath(null);
|
||||
await _settingsService.write(settings.SettingsService.customDownloadPath, null);
|
||||
await _settingsService.write(settings.SettingsService.customDownloadPathType, null);
|
||||
await DownloadStorageService.instance.refreshCustomPath();
|
||||
|
||||
if (mounted) {
|
||||
@@ -672,7 +674,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
DialogActionButton(
|
||||
onPressed: () async {
|
||||
controller.clear();
|
||||
await _settingsService.setCustomRelayUrl(null);
|
||||
await _settingsService.write(settings.SettingsService.customRelayUrl, null);
|
||||
if (mounted) setState(() => _customRelayUrl = null);
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
@@ -683,7 +685,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
focusNode: saveFocusNode,
|
||||
onPressed: () async {
|
||||
final url = controller.text.trim().isEmpty ? null : controller.text.trim();
|
||||
await _settingsService.setCustomRelayUrl(url);
|
||||
await _settingsService.write(settings.SettingsService.customRelayUrl, url);
|
||||
if (mounted) setState(() => _customRelayUrl = url);
|
||||
if (dialogContext.mounted) Navigator.pop(dialogContext);
|
||||
},
|
||||
@@ -764,7 +766,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab {
|
||||
if (!mounted) return;
|
||||
if (result == null) return; // user cancelled file picker
|
||||
|
||||
LocaleSettings.setLocale(_settingsService.getAppLocale());
|
||||
LocaleSettings.setLocale(_settingsService.read(settings.SettingsService.appLocale));
|
||||
await Future.wait([
|
||||
themeProvider.reload(),
|
||||
settingsProvider.reload(),
|
||||
|
||||
@@ -44,16 +44,16 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_fontSize = _settingsService.getSubtitleFontSize();
|
||||
_textColor = _settingsService.getSubtitleTextColor();
|
||||
_borderSize = _settingsService.getSubtitleBorderSize();
|
||||
_borderColor = _settingsService.getSubtitleBorderColor();
|
||||
_backgroundColor = _settingsService.getSubtitleBackgroundColor();
|
||||
_backgroundOpacity = _settingsService.getSubtitleBackgroundOpacity();
|
||||
_subtitlePosition = _settingsService.getSubtitlePosition();
|
||||
_assOverride = _settingsService.getSubAssOverride();
|
||||
_bold = _settingsService.getSubtitleBold();
|
||||
_italic = _settingsService.getSubtitleItalic();
|
||||
_fontSize = _settingsService.read(SettingsService.subtitleFontSize);
|
||||
_textColor = _settingsService.read(SettingsService.subtitleTextColor);
|
||||
_borderSize = _settingsService.read(SettingsService.subtitleBorderSize);
|
||||
_borderColor = _settingsService.read(SettingsService.subtitleBorderColor);
|
||||
_backgroundColor = _settingsService.read(SettingsService.subtitleBackgroundColor);
|
||||
_backgroundOpacity = _settingsService.read(SettingsService.subtitleBackgroundOpacity);
|
||||
_subtitlePosition = _settingsService.read(SettingsService.subtitlePosition);
|
||||
_assOverride = _settingsService.read(SettingsService.subAssOverride);
|
||||
_bold = _settingsService.read(SettingsService.subtitleBold);
|
||||
_italic = _settingsService.read(SettingsService.subtitleItalic);
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
@@ -200,7 +200,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
);
|
||||
if (value != null) {
|
||||
setState(() => _assOverride = value);
|
||||
await _settingsService.setSubAssOverride(value);
|
||||
await _settingsService.write(SettingsService.subAssOverride, value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -219,7 +219,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
currentValue: _fontSize,
|
||||
onSave: (value) async {
|
||||
setState(() => _fontSize = value);
|
||||
await _settingsService.setSubtitleFontSize(value);
|
||||
await _settingsService.write(SettingsService.subtitleFontSize, value);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -238,7 +238,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => _openColorPicker(t.subtitlingStyling.textColor, _textColor, (color) {
|
||||
setState(() => _textColor = color);
|
||||
_settingsService.setSubtitleTextColor(color);
|
||||
_settingsService.write(SettingsService.subtitleTextColor, color);
|
||||
}),
|
||||
),
|
||||
ListTile(
|
||||
@@ -256,7 +256,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
currentValue: _subtitlePosition,
|
||||
onSave: (value) async {
|
||||
setState(() => _subtitlePosition = value);
|
||||
await _settingsService.setSubtitlePosition(value);
|
||||
await _settingsService.write(SettingsService.subtitlePosition, value);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -266,7 +266,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
value: _bold,
|
||||
onChanged: (value) async {
|
||||
setState(() => _bold = value);
|
||||
await _settingsService.setSubtitleBold(value);
|
||||
await _settingsService.write(SettingsService.subtitleBold, value);
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
@@ -275,7 +275,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
value: _italic,
|
||||
onChanged: (value) async {
|
||||
setState(() => _italic = value);
|
||||
await _settingsService.setSubtitleItalic(value);
|
||||
await _settingsService.write(SettingsService.subtitleItalic, value);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -296,7 +296,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
currentValue: _borderSize,
|
||||
onSave: (value) async {
|
||||
setState(() => _borderSize = value);
|
||||
await _settingsService.setSubtitleBorderSize(value);
|
||||
await _settingsService.write(SettingsService.subtitleBorderSize, value);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -315,7 +315,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => _openColorPicker(t.subtitlingStyling.borderColor, _borderColor, (color) {
|
||||
setState(() => _borderColor = color);
|
||||
_settingsService.setSubtitleBorderColor(color);
|
||||
_settingsService.write(SettingsService.subtitleBorderColor, color);
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -336,7 +336,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
currentValue: _backgroundOpacity,
|
||||
onSave: (value) async {
|
||||
setState(() => _backgroundOpacity = value);
|
||||
await _settingsService.setSubtitleBackgroundOpacity(value);
|
||||
await _settingsService.write(SettingsService.subtitleBackgroundOpacity, value);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -355,7 +355,7 @@ class _SubtitleStylingScreenState extends State<SubtitleStylingScreen> {
|
||||
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
|
||||
onTap: () => _openColorPicker(t.subtitlingStyling.backgroundColor, _backgroundColor, (color) {
|
||||
setState(() => _backgroundColor = color);
|
||||
_settingsService.setSubtitleBackgroundColor(color);
|
||||
_settingsService.write(SettingsService.subtitleBackgroundColor, color);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
@@ -23,8 +23,8 @@ class TrackerLibraryFilterScreen extends StatefulWidget {
|
||||
/// Human-readable summary of the current filter state for [service], used as
|
||||
/// the subtitle on the parent settings screen's "Library filter" tile.
|
||||
static String subtitleFor(SettingsService settings, TrackerService service) {
|
||||
final mode = settings.getTrackerLibraryFilterMode(service);
|
||||
final ids = settings.getTrackerLibraryFilterIds(service);
|
||||
final mode = settings.read(SettingsService.trackerFilterModePref(service));
|
||||
final ids = settings.read(SettingsService.trackerFilterIdsPref(service)).toSet();
|
||||
if (ids.isEmpty) {
|
||||
return mode == TrackerLibraryFilterMode.blacklist
|
||||
? t.trackers.libraryFilter.subtitleAllSyncing
|
||||
@@ -57,10 +57,10 @@ class _TrackerLibraryFilterScreenState extends State<TrackerLibraryFilterScreen>
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_settings = s;
|
||||
_mode = s.getTrackerLibraryFilterMode(widget.service);
|
||||
_mode = s.read(SettingsService.trackerFilterModePref(widget.service));
|
||||
_selectedIds
|
||||
..clear()
|
||||
..addAll(s.getTrackerLibraryFilterIds(widget.service));
|
||||
..addAll(s.read(SettingsService.trackerFilterIdsPref(widget.service)).toSet());
|
||||
_loaded = true;
|
||||
});
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class _TrackerLibraryFilterScreenState extends State<TrackerLibraryFilterScreen>
|
||||
Future<void> _setMode(TrackerLibraryFilterMode mode) async {
|
||||
if (mode == _mode) return;
|
||||
setState(() => _mode = mode);
|
||||
await _settings.setTrackerLibraryFilterMode(widget.service, mode);
|
||||
await _settings.write(SettingsService.trackerFilterModePref(widget.service), mode);
|
||||
}
|
||||
|
||||
Future<void> _toggleLibrary(String globalKey, bool value) async {
|
||||
@@ -79,7 +79,7 @@ class _TrackerLibraryFilterScreenState extends State<TrackerLibraryFilterScreen>
|
||||
_selectedIds.remove(globalKey);
|
||||
}
|
||||
});
|
||||
await _settings.setTrackerLibraryFilterIds(widget.service, _selectedIds);
|
||||
await _settings.write(SettingsService.trackerFilterIdsPref(widget.service), _selectedIds.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -88,9 +88,9 @@ class TrackerConfig {
|
||||
displayName: t.trackers.services.mal,
|
||||
isConnected: (a) => a.isMalConnected,
|
||||
username: (a) => a.malUsername,
|
||||
readScrobbleEnabled: (s) => s.getEnableMalScrobble(),
|
||||
readScrobbleEnabled: (s) => s.read(SettingsService.enableMalScrobble),
|
||||
setScrobbleEnabled: (s, v) async {
|
||||
await s.setEnableMalScrobble(v);
|
||||
await s.write(SettingsService.enableMalScrobble, v);
|
||||
await MalTracker.instance.setEnabled(v);
|
||||
},
|
||||
disconnect: (a) => a.disconnectMal(),
|
||||
@@ -101,9 +101,9 @@ class TrackerConfig {
|
||||
displayName: t.trackers.services.anilist,
|
||||
isConnected: (a) => a.isAnilistConnected,
|
||||
username: (a) => a.anilistUsername,
|
||||
readScrobbleEnabled: (s) => s.getEnableAnilistScrobble(),
|
||||
readScrobbleEnabled: (s) => s.read(SettingsService.enableAnilistScrobble),
|
||||
setScrobbleEnabled: (s, v) async {
|
||||
await s.setEnableAnilistScrobble(v);
|
||||
await s.write(SettingsService.enableAnilistScrobble, v);
|
||||
await AnilistTracker.instance.setEnabled(v);
|
||||
},
|
||||
disconnect: (a) => a.disconnectAnilist(),
|
||||
@@ -114,9 +114,9 @@ class TrackerConfig {
|
||||
displayName: t.trackers.services.simkl,
|
||||
isConnected: (a) => a.isSimklConnected,
|
||||
username: (a) => a.simklUsername,
|
||||
readScrobbleEnabled: (s) => s.getEnableSimklScrobble(),
|
||||
readScrobbleEnabled: (s) => s.read(SettingsService.enableSimklScrobble),
|
||||
setScrobbleEnabled: (s, v) async {
|
||||
await s.setEnableSimklScrobble(v);
|
||||
await s.write(SettingsService.enableSimklScrobble, v);
|
||||
await SimklTracker.instance.setEnabled(v);
|
||||
},
|
||||
disconnect: (a) => a.disconnectSimkl(),
|
||||
|
||||
@@ -55,8 +55,8 @@ class _TraktSettingsScreenState extends State<TraktSettingsScreen> {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_settings = s;
|
||||
_scrobbleEnabled = s.getEnableTraktScrobble();
|
||||
_watchedSyncEnabled = s.getEnableTraktWatchedSync();
|
||||
_scrobbleEnabled = s.read(SettingsService.enableTraktScrobble);
|
||||
_watchedSyncEnabled = s.read(SettingsService.enableTraktWatchedSync);
|
||||
_loaded = true;
|
||||
});
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class _TraktSettingsScreenState extends State<TraktSettingsScreen> {
|
||||
value: _scrobbleEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() => _scrobbleEnabled = value);
|
||||
await _settings!.setEnableTraktScrobble(value);
|
||||
await _settings!.write(SettingsService.enableTraktScrobble, value);
|
||||
await TraktScrobbleService.instance.setEnabled(value);
|
||||
},
|
||||
),
|
||||
@@ -129,7 +129,7 @@ class _TraktSettingsScreenState extends State<TraktSettingsScreen> {
|
||||
value: _watchedSyncEnabled,
|
||||
onChanged: (value) async {
|
||||
setState(() => _watchedSyncEnabled = value);
|
||||
await _settings!.setEnableTraktWatchedSync(value);
|
||||
await _settings!.write(SettingsService.enableTraktWatchedSync, value);
|
||||
await TraktSyncService.instance.setEnabled(value);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -602,13 +602,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
try {
|
||||
// Load buffer size from settings
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
_videoPlayerNavigationEnabled = settingsService.getVideoPlayerNavigationEnabled();
|
||||
_autoPipEnabled = settingsService.getAutoPip();
|
||||
_rewindOnResume = settingsService.getRewindOnResume();
|
||||
final bufferSizeMB = settingsService.getBufferSize();
|
||||
final enableHardwareDecoding = settingsService.getEnableHardwareDecoding();
|
||||
final debugLoggingEnabled = settingsService.getEnableDebugLogging();
|
||||
final useExoPlayer = settingsService.getUseExoPlayer();
|
||||
_videoPlayerNavigationEnabled = settingsService.read(SettingsService.videoPlayerNavigationEnabled);
|
||||
_autoPipEnabled = settingsService.read(SettingsService.autoPip);
|
||||
_rewindOnResume = settingsService.read(SettingsService.rewindOnResume);
|
||||
final bufferSizeMB = settingsService.read(SettingsService.bufferSize);
|
||||
final enableHardwareDecoding = settingsService.read(SettingsService.enableHardwareDecoding);
|
||||
final debugLoggingEnabled = settingsService.read(SettingsService.enableDebugLogging);
|
||||
final useExoPlayer = settingsService.read(SettingsService.useExoPlayer);
|
||||
|
||||
// Initialize Windows display mode service.
|
||||
if (Platform.isWindows) {
|
||||
@@ -667,7 +667,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
await player!.configureSubtitleFonts();
|
||||
await player!.setProperty('sub-ass', 'yes'); // Enable libass
|
||||
if (Platform.isAndroid && useExoPlayer) {
|
||||
final tunneledPlayback = settingsService.getTunneledPlayback();
|
||||
final tunneledPlayback = settingsService.read(SettingsService.tunneledPlayback);
|
||||
await player!.setProperty('tunneled-playback', tunneledPlayback ? 'yes' : 'no');
|
||||
}
|
||||
if (bufferSizeMB > 0) {
|
||||
@@ -716,24 +716,24 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
await player!.setProperty('hwdec', _getHwdecValue(enableHardwareDecoding));
|
||||
|
||||
// Subtitle styling
|
||||
await player!.setProperty('sub-font-size', settingsService.getSubtitleFontSize().toString());
|
||||
await player!.setProperty('sub-color', settingsService.getSubtitleTextColor());
|
||||
await player!.setProperty('sub-border-size', settingsService.getSubtitleBorderSize().toString());
|
||||
await player!.setProperty('sub-border-color', settingsService.getSubtitleBorderColor());
|
||||
await player!.setProperty('sub-bold', settingsService.getSubtitleBold() ? 'yes' : 'no');
|
||||
await player!.setProperty('sub-italic', settingsService.getSubtitleItalic() ? 'yes' : 'no');
|
||||
final bgOpacity = (settingsService.getSubtitleBackgroundOpacity() * 255 / 100).toInt();
|
||||
final bgColor = settingsService.getSubtitleBackgroundColor().replaceFirst('#', '');
|
||||
await player!.setProperty('sub-font-size', settingsService.read(SettingsService.subtitleFontSize).toString());
|
||||
await player!.setProperty('sub-color', settingsService.read(SettingsService.subtitleTextColor));
|
||||
await player!.setProperty('sub-border-size', settingsService.read(SettingsService.subtitleBorderSize).toString());
|
||||
await player!.setProperty('sub-border-color', settingsService.read(SettingsService.subtitleBorderColor));
|
||||
await player!.setProperty('sub-bold', settingsService.read(SettingsService.subtitleBold) ? 'yes' : 'no');
|
||||
await player!.setProperty('sub-italic', settingsService.read(SettingsService.subtitleItalic) ? 'yes' : 'no');
|
||||
final bgOpacity = (settingsService.read(SettingsService.subtitleBackgroundOpacity) * 255 / 100).toInt();
|
||||
final bgColor = settingsService.read(SettingsService.subtitleBackgroundColor).replaceFirst('#', '');
|
||||
await player!.setProperty(
|
||||
'sub-back-color',
|
||||
'#${bgOpacity.toRadixString(16).padLeft(2, '0').toUpperCase()}$bgColor',
|
||||
);
|
||||
if (settingsService.getSubtitleBackgroundOpacity() > 0) {
|
||||
if (settingsService.read(SettingsService.subtitleBackgroundOpacity) > 0) {
|
||||
await player!.setProperty('sub-border-style', 'background-box');
|
||||
}
|
||||
await player!.setProperty('sub-ass-override', settingsService.getSubAssOverride().name);
|
||||
await player!.setProperty('sub-ass-override', settingsService.read(SettingsService.subAssOverride).name);
|
||||
await player!.setProperty('sub-ass-video-aspect-override', '1');
|
||||
await player!.setProperty('sub-pos', settingsService.getSubtitlePosition().toString());
|
||||
await player!.setProperty('sub-pos', settingsService.read(SettingsService.subtitlePosition).toString());
|
||||
|
||||
// Platform-specific settings
|
||||
if (Platform.isIOS) {
|
||||
@@ -742,38 +742,38 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
|
||||
// Audio passthrough (desktop only - sends bitstream to receiver)
|
||||
if (PlatformDetector.isDesktopOS()) {
|
||||
if (settingsService.getAudioPassthrough()) {
|
||||
if (settingsService.read(SettingsService.audioPassthrough)) {
|
||||
await player!.setAudioPassthrough(true);
|
||||
}
|
||||
}
|
||||
|
||||
// HDR is controlled via custom hdr-enabled property on iOS/macOS/Windows
|
||||
if (Platform.isIOS || Platform.isMacOS || Platform.isWindows) {
|
||||
final enableHDR = settingsService.getEnableHDR();
|
||||
final enableHDR = settingsService.read(SettingsService.enableHDR);
|
||||
await player!.setProperty('hdr-enabled', enableHDR ? 'yes' : 'no');
|
||||
}
|
||||
|
||||
// Apply audio sync offset
|
||||
final audioSyncOffset = settingsService.getAudioSyncOffset();
|
||||
final audioSyncOffset = settingsService.read(SettingsService.audioSyncOffset);
|
||||
if (audioSyncOffset != 0) {
|
||||
final offsetSeconds = audioSyncOffset / 1000.0;
|
||||
await player!.setProperty('audio-delay', offsetSeconds.toString());
|
||||
}
|
||||
|
||||
// Apply subtitle sync offset
|
||||
final subtitleSyncOffset = settingsService.getSubtitleSyncOffset();
|
||||
final subtitleSyncOffset = settingsService.read(SettingsService.subtitleSyncOffset);
|
||||
if (subtitleSyncOffset != 0) {
|
||||
final offsetSeconds = subtitleSyncOffset / 1000.0;
|
||||
await player!.setProperty('sub-delay', offsetSeconds.toString());
|
||||
}
|
||||
|
||||
// Apply audio normalization (loudnorm filter)
|
||||
if (settingsService.getAudioNormalization()) {
|
||||
if (settingsService.read(SettingsService.audioNormalization)) {
|
||||
await player!.setProperty('af', 'loudnorm=I=-14:TP=-3:LRA=4');
|
||||
}
|
||||
|
||||
// Apply custom MPV config entries
|
||||
final customMpvConfig = settingsService.getEnabledMpvConfigEntries();
|
||||
final customMpvConfig = SettingsService.parseMpvConfigText(settingsService.read(SettingsService.mpvConfigText));
|
||||
for (final entry in customMpvConfig.entries) {
|
||||
try {
|
||||
await player!.setProperty(entry.key, entry.value);
|
||||
@@ -784,11 +784,11 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
}
|
||||
|
||||
// Set max volume limit for volume boost
|
||||
final maxVolume = settingsService.getMaxVolume();
|
||||
final maxVolume = settingsService.read(SettingsService.maxVolume);
|
||||
await player!.setProperty('volume-max', maxVolume.toString());
|
||||
|
||||
// Apply saved volume (clamped to max volume)
|
||||
final savedVolume = settingsService.getVolume().clamp(0.0, maxVolume.toDouble());
|
||||
final savedVolume = settingsService.read(SettingsService.volume).clamp(0.0, maxVolume.toDouble());
|
||||
player!.setVolume(savedVolume);
|
||||
|
||||
// Notify that player is ready
|
||||
@@ -815,7 +815,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
if (mounted) {
|
||||
try {
|
||||
// Check rotation lock setting before applying orientation
|
||||
final isRotationLocked = settingsService.getRotationLocked();
|
||||
final isRotationLocked = settingsService.read(SettingsService.rotationLocked);
|
||||
|
||||
if (isRotationLocked) {
|
||||
// Locked: Apply landscape orientation only
|
||||
@@ -906,7 +906,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'First frame ready', category: 'player'));
|
||||
|
||||
// Apply frame rate matching on Android if enabled
|
||||
if (Platform.isAndroid && settingsService.getMatchContentFrameRate()) {
|
||||
if (Platform.isAndroid && settingsService.read(SettingsService.matchContentFrameRate)) {
|
||||
await _applyFrameRateMatching();
|
||||
}
|
||||
|
||||
@@ -933,7 +933,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
|
||||
// Apply frame rate matching here too, since this fallback may fire
|
||||
// before playbackRestart (race condition with resume positions > 0)
|
||||
if (Platform.isAndroid && settingsService.getMatchContentFrameRate()) {
|
||||
if (Platform.isAndroid && settingsService.read(SettingsService.matchContentFrameRate)) {
|
||||
_applyFrameRateMatching();
|
||||
}
|
||||
}
|
||||
@@ -1005,7 +1005,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
_frameRateMatchingApplied = true;
|
||||
final durationMs = player!.state.duration.inMilliseconds;
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
final delaySec = settingsService.getDisplaySwitchDelay();
|
||||
final delaySec = settingsService.read(SettingsService.displaySwitchDelay);
|
||||
|
||||
// Suppress spurious PauseEvent from MediaSession during HDMI renegotiation.
|
||||
// Fire Stick (and similar Android TV devices) send onPause() through the
|
||||
@@ -1525,7 +1525,10 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
final preKnownFps = result.mediaInfo?.frameRate;
|
||||
final willAutoSwitch =
|
||||
Platform.isAndroid && settingsService.getMatchContentFrameRate() && preKnownFps != null && preKnownFps > 0;
|
||||
Platform.isAndroid &&
|
||||
settingsService.read(SettingsService.matchContentFrameRate) &&
|
||||
preKnownFps != null &&
|
||||
preKnownFps > 0;
|
||||
|
||||
// Open video through Player
|
||||
if (result.videoUrl != null) {
|
||||
@@ -1588,15 +1591,15 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
// Must be called after open() since that's when ExoPlayer initializes
|
||||
if (player is PlayerAndroid) {
|
||||
await (player as PlayerAndroid).setSubtitleStyle(
|
||||
fontSize: settingsService.getSubtitleFontSize().toDouble(),
|
||||
textColor: settingsService.getSubtitleTextColor(),
|
||||
borderSize: settingsService.getSubtitleBorderSize().toDouble(),
|
||||
borderColor: settingsService.getSubtitleBorderColor(),
|
||||
bgColor: settingsService.getSubtitleBackgroundColor(),
|
||||
bgOpacity: settingsService.getSubtitleBackgroundOpacity(),
|
||||
subtitlePosition: settingsService.getSubtitlePosition(),
|
||||
bold: settingsService.getSubtitleBold(),
|
||||
italic: settingsService.getSubtitleItalic(),
|
||||
fontSize: settingsService.read(SettingsService.subtitleFontSize).toDouble(),
|
||||
textColor: settingsService.read(SettingsService.subtitleTextColor),
|
||||
borderSize: settingsService.read(SettingsService.subtitleBorderSize).toDouble(),
|
||||
borderColor: settingsService.read(SettingsService.subtitleBorderColor),
|
||||
bgColor: settingsService.read(SettingsService.subtitleBackgroundColor),
|
||||
bgOpacity: settingsService.read(SettingsService.subtitleBackgroundOpacity),
|
||||
subtitlePosition: settingsService.read(SettingsService.subtitlePosition),
|
||||
bold: settingsService.read(SettingsService.subtitleBold),
|
||||
italic: settingsService.read(SettingsService.subtitleItalic),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1714,7 +1717,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
// the switch has settled.
|
||||
if (willAutoSwitch && mounted && player != null) {
|
||||
_frameRateMatchingApplied = true;
|
||||
final delaySec = settingsService.getDisplaySwitchDelay();
|
||||
final delaySec = settingsService.read(SettingsService.displaySwitchDelay);
|
||||
final durationMs = _currentMetadata.duration ?? player!.state.duration.inMilliseconds;
|
||||
_suppressMediaPauseDuringFrameRateSwitch = true;
|
||||
Future.delayed(Duration(seconds: 2 + delaySec + 1), () {
|
||||
@@ -1854,8 +1857,8 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
player: player!,
|
||||
availableVersions: _availableVersions,
|
||||
selectedMediaIndex: widget.selectedMediaIndex,
|
||||
initialBoxFitMode: settings.getDefaultBoxFitMode(),
|
||||
onBoxFitModeChanged: (mode) => settings.setDefaultBoxFitMode(mode),
|
||||
initialBoxFitMode: settings.read(SettingsService.defaultBoxFitMode),
|
||||
onBoxFitModeChanged: (mode) => settings.write(SettingsService.defaultBoxFitMode, mode),
|
||||
);
|
||||
_videoFilterManager!.updateVideoFilter();
|
||||
|
||||
@@ -1902,7 +1905,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
try {
|
||||
final shaderProvider = context.read<ShaderProvider>();
|
||||
final settings = await SettingsService.getInstance();
|
||||
final presetId = settings.getGlobalShaderPreset();
|
||||
final presetId = settings.read(SettingsService.globalShaderPreset);
|
||||
final preset =
|
||||
(shaderProvider.initialized ? shaderProvider.findPresetById(presetId) : ShaderPreset.fromId(presetId)) ??
|
||||
ShaderPreset.none;
|
||||
@@ -1918,7 +1921,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
Future<void> _restoreAmbientLighting() async {
|
||||
final shaderProvider = context.read<ShaderProvider>();
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (!settings.getAmbientLighting()) return;
|
||||
if (!settings.read(SettingsService.ambientLighting)) return;
|
||||
|
||||
final ambientLighting = _ambientLightingService;
|
||||
if (ambientLighting == null || !ambientLighting.isSupported) return;
|
||||
@@ -2005,7 +2008,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
|
||||
// Persist ambient lighting state
|
||||
final settings = await SettingsService.getInstance();
|
||||
settings.setAmbientLighting(ambientLighting.isEnabled);
|
||||
settings.write(SettingsService.ambientLighting, ambientLighting.isEnabled);
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
@@ -2123,7 +2126,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
receiver.onSeekForward = () async {
|
||||
if (player == null) return;
|
||||
final settings = await SettingsService.getInstance();
|
||||
final seekSeconds = settings.getSeekTimeSmall();
|
||||
final seekSeconds = settings.read(SettingsService.seekTimeSmall);
|
||||
if (widget.isLive && _captureBuffer != null) {
|
||||
await _seekLivePosition(_currentPositionEpoch + seekSeconds);
|
||||
return;
|
||||
@@ -2134,7 +2137,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
receiver.onSeekBackward = () async {
|
||||
if (player == null) return;
|
||||
final settings = await SettingsService.getInstance();
|
||||
final seekSeconds = settings.getSeekTimeSmall();
|
||||
final seekSeconds = settings.read(SettingsService.seekTimeSmall);
|
||||
if (widget.isLive && _captureBuffer != null) {
|
||||
await _seekLivePosition(_currentPositionEpoch - seekSeconds);
|
||||
return;
|
||||
@@ -2145,25 +2148,25 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
receiver.onVolumeUp = () async {
|
||||
if (player == null) return;
|
||||
final settings = await SettingsService.getInstance();
|
||||
final maxVol = settings.getMaxVolume().toDouble();
|
||||
final maxVol = settings.read(SettingsService.maxVolume).toDouble();
|
||||
final newVolume = (player!.state.volume + 10).clamp(0.0, maxVol);
|
||||
player!.setVolume(newVolume);
|
||||
settings.setVolume(newVolume);
|
||||
settings.write(SettingsService.volume, newVolume);
|
||||
};
|
||||
receiver.onVolumeDown = () async {
|
||||
if (player == null) return;
|
||||
final settings = await SettingsService.getInstance();
|
||||
final maxVol = settings.getMaxVolume().toDouble();
|
||||
final maxVol = settings.read(SettingsService.maxVolume).toDouble();
|
||||
final newVolume = (player!.state.volume - 10).clamp(0.0, maxVol);
|
||||
player!.setVolume(newVolume);
|
||||
settings.setVolume(newVolume);
|
||||
settings.write(SettingsService.volume, newVolume);
|
||||
};
|
||||
receiver.onVolumeMute = () async {
|
||||
if (player == null) return;
|
||||
final settings = await SettingsService.getInstance();
|
||||
final newVolume = player!.state.volume > 0 ? 0.0 : 100.0;
|
||||
player!.setVolume(newVolume);
|
||||
settings.setVolume(newVolume);
|
||||
settings.write(SettingsService.volume, newVolume);
|
||||
};
|
||||
receiver.onSubtitles = _cycleSubtitleTrack;
|
||||
receiver.onAudioTracks = _cycleAudioTrack;
|
||||
@@ -2489,7 +2492,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
||||
final isKeyboardMode = PlatformDetector.isTV() && InputModeTracker.isKeyboardMode(context);
|
||||
|
||||
final settings = await SettingsService.getInstance();
|
||||
final autoPlayEnabled = settings.getAutoPlayNextEpisode();
|
||||
final autoPlayEnabled = settings.read(SettingsService.autoPlayNextEpisode);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:shared_preferences/util/legacy_to_async_migration_util.dart';
|
||||
|
||||
/// Base class for services that use SharedPreferences singleton pattern.
|
||||
///
|
||||
@@ -10,30 +12,63 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
/// 3. Optionally override onInit() for post-initialization setup
|
||||
abstract class BaseSharedPreferencesService {
|
||||
static final Map<Type, BaseSharedPreferencesService> _instances = {};
|
||||
late SharedPreferences _prefs;
|
||||
// Single shared cache across all subclasses so writes from one service are
|
||||
// visible to reads from another without per-instance cache divergence.
|
||||
static Future<SharedPreferencesWithCache>? _cacheFuture;
|
||||
|
||||
late SharedPreferencesWithCache _cache;
|
||||
|
||||
/// Protected constructor for subclasses
|
||||
BaseSharedPreferencesService();
|
||||
|
||||
/// Access to SharedPreferences instance
|
||||
SharedPreferences get prefs => _prefs;
|
||||
/// Access to the underlying preferences cache.
|
||||
SharedPreferencesWithCache get prefs => _cache;
|
||||
|
||||
/// Initialize the SharedPreferences instance
|
||||
/// Initialize the preferences instance.
|
||||
///
|
||||
/// This method handles:
|
||||
/// - Singleton instance management
|
||||
/// - SharedPreferences initialization
|
||||
/// - One-time migration from the legacy SharedPreferences API to the
|
||||
/// SharedPreferencesAsync-backed cache (idempotent across launches)
|
||||
/// - Calling onInit() hook for subclass-specific setup
|
||||
static Future<T> initializeInstance<T extends BaseSharedPreferencesService>(T Function() constructor) async {
|
||||
if (_instances[T] == null) {
|
||||
final instance = constructor();
|
||||
_instances[T] = instance;
|
||||
instance._prefs = await SharedPreferences.getInstance();
|
||||
instance._cache = await _ensureCache();
|
||||
await instance.onInit();
|
||||
}
|
||||
return _instances[T] as T;
|
||||
}
|
||||
|
||||
static Future<SharedPreferencesWithCache> _ensureCache() {
|
||||
return _cacheFuture ??= () async {
|
||||
final legacy = await SharedPreferences.getInstance();
|
||||
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary(
|
||||
legacySharedPreferencesInstance: legacy,
|
||||
sharedPreferencesAsyncOptions: const SharedPreferencesOptions(),
|
||||
migrationCompletedKey: 'plezy_legacy_prefs_migrated_v1',
|
||||
);
|
||||
return SharedPreferencesWithCache.create(cacheOptions: const SharedPreferencesWithCacheOptions());
|
||||
}();
|
||||
}
|
||||
|
||||
/// Typed read helpers — return the stored value or [defaultValue] when missing.
|
||||
bool readBool(String key, {bool defaultValue = false}) => _cache.getBool(key) ?? defaultValue;
|
||||
int readInt(String key, {int defaultValue = 0}) => _cache.getInt(key) ?? defaultValue;
|
||||
double readDouble(String key, {double defaultValue = 0.0}) => _cache.getDouble(key) ?? defaultValue;
|
||||
String readString(String key, {String defaultValue = ''}) => _cache.getString(key) ?? defaultValue;
|
||||
List<String> readStringList(String key, {List<String> defaultValue = const []}) =>
|
||||
_cache.getStringList(key) ?? defaultValue;
|
||||
|
||||
/// Typed write helpers — symmetric with the read helpers above; use these
|
||||
/// instead of `prefs.setX(...)` so call sites stay terse.
|
||||
Future<void> writeBool(String key, bool value) => _cache.setBool(key, value);
|
||||
Future<void> writeInt(String key, int value) => _cache.setInt(key, value);
|
||||
Future<void> writeDouble(String key, double value) => _cache.setDouble(key, value);
|
||||
Future<void> writeString(String key, String value) => _cache.setString(key, value);
|
||||
Future<void> writeStringList(String key, List<String> value) => _cache.setStringList(key, value);
|
||||
|
||||
/// Decode a JSON string to a Map with error handling.
|
||||
///
|
||||
/// If [legacyStringOk] is true and the value is a plain string (not valid
|
||||
@@ -50,6 +85,26 @@ abstract class BaseSharedPreferencesService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a value typed by [pref]; falls back to its `defaultValue`.
|
||||
T read<T>(Pref<T> pref) => pref.readFrom(this);
|
||||
|
||||
/// Write a value typed by [pref]. Pushes the post-transform value into any
|
||||
/// listenable previously vended for this key so widgets rebuild automatically.
|
||||
Future<void> write<T>(Pref<T> pref, T value) async {
|
||||
await pref.writeTo(this, value);
|
||||
final n = _listenables[pref.key];
|
||||
if (n != null) (n as ValueNotifier<T>).value = read(pref);
|
||||
}
|
||||
|
||||
/// Lazy per-key [ValueNotifier]. Use with [ValueListenableBuilder] to rebuild
|
||||
/// when the value changes. Notifiers live for the app lifetime — do not
|
||||
/// dispose them.
|
||||
final Map<String, ValueNotifier<dynamic>> _listenables = {};
|
||||
|
||||
ValueNotifier<T> listenable<T>(Pref<T> pref) {
|
||||
return (_listenables[pref.key] ??= ValueNotifier<T>(read(pref))) as ValueNotifier<T>;
|
||||
}
|
||||
|
||||
/// Hook for subclass-specific initialization after SharedPreferences is ready.
|
||||
///
|
||||
/// Override this method to perform any setup that requires access to
|
||||
@@ -58,3 +113,136 @@ abstract class BaseSharedPreferencesService {
|
||||
// Default implementation does nothing
|
||||
}
|
||||
}
|
||||
|
||||
/// Typed preference declaration. Pair with [BaseSharedPreferencesService.read]
|
||||
/// and [BaseSharedPreferencesService.write] to remove per-setting `?? default`
|
||||
/// boilerplate.
|
||||
abstract class Pref<T> {
|
||||
final String key;
|
||||
const Pref(this.key);
|
||||
|
||||
/// Implementation hook — call [BaseSharedPreferencesService.read] instead.
|
||||
T readFrom(BaseSharedPreferencesService svc);
|
||||
|
||||
/// Implementation hook — call [BaseSharedPreferencesService.write] instead.
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, T value);
|
||||
}
|
||||
|
||||
class BoolPref extends Pref<bool> {
|
||||
final bool defaultValue;
|
||||
final void Function(bool)? onWrite;
|
||||
const BoolPref(super.key, {this.defaultValue = false, this.onWrite});
|
||||
@override
|
||||
bool readFrom(BaseSharedPreferencesService svc) => svc.readBool(key, defaultValue: defaultValue);
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, bool value) async {
|
||||
await svc.writeBool(key, value);
|
||||
onWrite?.call(value);
|
||||
}
|
||||
}
|
||||
|
||||
class IntPref extends Pref<int> {
|
||||
final int defaultValue;
|
||||
final int Function(int)? transform;
|
||||
const IntPref(super.key, {this.defaultValue = 0, this.transform});
|
||||
@override
|
||||
int readFrom(BaseSharedPreferencesService svc) {
|
||||
final raw = svc.readInt(key, defaultValue: defaultValue);
|
||||
return transform == null ? raw : transform!(raw);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, int value) =>
|
||||
svc.writeInt(key, transform == null ? value : transform!(value));
|
||||
}
|
||||
|
||||
class DoublePref extends Pref<double> {
|
||||
final double defaultValue;
|
||||
final double Function(double)? transform;
|
||||
const DoublePref(super.key, {this.defaultValue = 0.0, this.transform});
|
||||
@override
|
||||
double readFrom(BaseSharedPreferencesService svc) {
|
||||
final raw = svc.readDouble(key, defaultValue: defaultValue);
|
||||
return transform == null ? raw : transform!(raw);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, double value) =>
|
||||
svc.writeDouble(key, transform == null ? value : transform!(value));
|
||||
}
|
||||
|
||||
class StringPref extends Pref<String> {
|
||||
final String defaultValue;
|
||||
const StringPref(super.key, {this.defaultValue = ''});
|
||||
@override
|
||||
String readFrom(BaseSharedPreferencesService svc) => svc.readString(key, defaultValue: defaultValue);
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, String value) => svc.writeString(key, value);
|
||||
}
|
||||
|
||||
/// Like [StringPref] but null = key absent. [transform] runs on write; if it
|
||||
/// returns null the key is removed.
|
||||
class NullableStringPref extends Pref<String?> {
|
||||
final String? Function(String?)? transform;
|
||||
const NullableStringPref(super.key, {this.transform});
|
||||
@override
|
||||
String? readFrom(BaseSharedPreferencesService svc) => svc.prefs.getString(key);
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, String? value) async {
|
||||
final normalized = transform == null ? value : transform!(value);
|
||||
if (normalized == null) {
|
||||
await svc.prefs.remove(key);
|
||||
} else {
|
||||
await svc.writeString(key, normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StringListPref extends Pref<List<String>> {
|
||||
final List<String> defaultValue;
|
||||
const StringListPref(super.key, {this.defaultValue = const []});
|
||||
@override
|
||||
List<String> readFrom(BaseSharedPreferencesService svc) => svc.readStringList(key, defaultValue: defaultValue);
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, List<String> value) => svc.writeStringList(key, value);
|
||||
}
|
||||
|
||||
/// Stores an enum by its [Enum.name]; falls back to [defaultValue] when the
|
||||
/// stored string doesn't match any value in [values].
|
||||
class EnumPref<T extends Enum> extends Pref<T> {
|
||||
final List<T> values;
|
||||
final T defaultValue;
|
||||
const EnumPref(super.key, {required this.values, required this.defaultValue});
|
||||
@override
|
||||
T readFrom(BaseSharedPreferencesService svc) {
|
||||
final stored = svc.prefs.getString(key);
|
||||
if (stored == null) return defaultValue;
|
||||
return values.firstWhere((v) => v.name == stored, orElse: () => defaultValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, T value) => svc.writeString(key, value.name);
|
||||
}
|
||||
|
||||
/// Stores an arbitrary value as a JSON-encoded string. Decode failures and
|
||||
/// missing keys both fall back to [defaultValue].
|
||||
class JsonPref<T> extends Pref<T> {
|
||||
final T defaultValue;
|
||||
final String Function(T) encode;
|
||||
final T Function(dynamic) decode;
|
||||
JsonPref(super.key, {required this.defaultValue, required this.encode, required this.decode});
|
||||
|
||||
@override
|
||||
T readFrom(BaseSharedPreferencesService svc) {
|
||||
final s = svc.prefs.getString(key);
|
||||
if (s == null) return defaultValue;
|
||||
try {
|
||||
return decode(json.decode(s));
|
||||
} catch (_) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeTo(BaseSharedPreferencesService svc, T value) => svc.writeString(key, encode(value));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class DiscordRPCService {
|
||||
_isInitialized = true;
|
||||
|
||||
final settings = await SettingsService.getInstance();
|
||||
_isEnabled = settings.getEnableDiscordRPC();
|
||||
_isEnabled = settings.read(SettingsService.enableDiscordRPC);
|
||||
|
||||
if (_isEnabled) {
|
||||
await _connect();
|
||||
|
||||
@@ -34,7 +34,7 @@ class DisplayModeService {
|
||||
bool anyChange = false;
|
||||
|
||||
// Refresh rate matching.
|
||||
if (_settings.getMatchRefreshRate() && fps != null && fps > 0) {
|
||||
if (_settings.read(SettingsService.matchRefreshRate) && fps != null && fps > 0) {
|
||||
try {
|
||||
final success = await _matchRefreshRate(fps);
|
||||
anyChange |= success;
|
||||
@@ -44,7 +44,7 @@ class DisplayModeService {
|
||||
}
|
||||
|
||||
// Dynamic range matching.
|
||||
if (_settings.getMatchDynamicRange() && sigPeak != null && sigPeak > 1.0) {
|
||||
if (_settings.read(SettingsService.matchDynamicRange) && sigPeak != null && sigPeak > 1.0) {
|
||||
try {
|
||||
final success = await _enableSystemHDR();
|
||||
anyChange |= success;
|
||||
@@ -54,7 +54,7 @@ class DisplayModeService {
|
||||
}
|
||||
|
||||
if (anyChange) {
|
||||
final delaySec = _settings.getDisplaySwitchDelay();
|
||||
final delaySec = _settings.read(SettingsService.displaySwitchDelay);
|
||||
return Duration(seconds: delaySec);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class DownloadManagerService {
|
||||
/// pay for a second platform round-trip.
|
||||
static Future<bool> shouldBlockDownloadOnCellularWith(List<ConnectivityResult> connectivity) async {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (!settings.getDownloadOnWifiOnly()) return false;
|
||||
if (!settings.read(SettingsService.downloadOnWifiOnly)) return false;
|
||||
if (connectivity.isEmpty) return false;
|
||||
return connectivity.contains(ConnectivityResult.mobile) &&
|
||||
!connectivity.contains(ConnectivityResult.wifi) &&
|
||||
@@ -610,7 +610,7 @@ class DownloadManagerService {
|
||||
|
||||
// Get WiFi-only setting for native enforcement
|
||||
final settings = await SettingsService.getInstance();
|
||||
final requiresWiFi = settings.getDownloadOnWifiOnly();
|
||||
final requiresWiFi = settings.read(SettingsService.downloadOnWifiOnly);
|
||||
|
||||
if (_storageService.isUsingSaf) {
|
||||
// SAF mode: use UriDownloadTask (writes directly to content:// URI, no pause/resume)
|
||||
|
||||
@@ -47,8 +47,8 @@ class DownloadStorageService {
|
||||
/// Initialize with settings service (call during app startup)
|
||||
Future<void> initialize(SettingsService settingsService) async {
|
||||
_settingsService = settingsService;
|
||||
_customDownloadPath = settingsService.getCustomDownloadPath();
|
||||
_customPathType = settingsService.getCustomDownloadPathType();
|
||||
_customDownloadPath = settingsService.read(SettingsService.customDownloadPath);
|
||||
_customPathType = settingsService.read(SettingsService.customDownloadPathType) ?? 'file';
|
||||
// Reset cached directories to force recalculation
|
||||
_baseDownloadsDir = null;
|
||||
_artworkDirectoryPath = null;
|
||||
@@ -60,8 +60,8 @@ class DownloadStorageService {
|
||||
/// Refresh custom path from settings (call when settings change)
|
||||
Future<void> refreshCustomPath() async {
|
||||
if (_settingsService != null) {
|
||||
_customDownloadPath = _settingsService!.getCustomDownloadPath();
|
||||
_customPathType = _settingsService!.getCustomDownloadPathType();
|
||||
_customDownloadPath = _settingsService!.read(SettingsService.customDownloadPath);
|
||||
_customPathType = _settingsService!.read(SettingsService.customDownloadPathType) ?? 'file';
|
||||
_baseDownloadsDir = null;
|
||||
_artworkDirectoryPath = null;
|
||||
await getArtworkDirectory();
|
||||
|
||||
@@ -44,7 +44,7 @@ class ExternalPlayerService {
|
||||
}
|
||||
|
||||
final settings = await SettingsService.getInstance();
|
||||
final player = settings.getSelectedExternalPlayer();
|
||||
final player = settings.read(SettingsService.selectedExternalPlayer);
|
||||
|
||||
// On Android, always use native intent to avoid url_launcher opening in browser
|
||||
if (Platform.isAndroid && context.mounted) {
|
||||
|
||||
@@ -38,11 +38,11 @@ class KeyboardShortcutsService {
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
// Ensure settings service is fully initialized before loading data
|
||||
await Future.delayed(Duration.zero); // Allow event loop to complete
|
||||
_shortcuts = _settingsService.getKeyboardShortcuts(); // Keep for legacy compatibility
|
||||
_hotkeys = await _settingsService.getKeyboardHotkeys(); // Primary method
|
||||
_seekTimeSmall = _settingsService.getSeekTimeSmall();
|
||||
_seekTimeLarge = _settingsService.getSeekTimeLarge();
|
||||
_maxVolume = _settingsService.getMaxVolume();
|
||||
_shortcuts = _settingsService.read(SettingsService.keyboardShortcuts); // Keep for legacy compatibility
|
||||
_hotkeys = _settingsService.read(SettingsService.keyboardHotkeys); // Primary method
|
||||
_seekTimeSmall = _settingsService.read(SettingsService.seekTimeSmall);
|
||||
_seekTimeLarge = _settingsService.read(SettingsService.seekTimeLarge);
|
||||
_maxVolume = _settingsService.read(SettingsService.maxVolume);
|
||||
}
|
||||
|
||||
Map<String, String> get shortcuts => Map.from(_shortcuts);
|
||||
@@ -59,7 +59,7 @@ class KeyboardShortcutsService {
|
||||
|
||||
Future<void> setShortcut(String action, String key) async {
|
||||
_shortcuts[action] = key;
|
||||
await _settingsService.setKeyboardShortcuts(_shortcuts);
|
||||
await _settingsService.write(SettingsService.keyboardShortcuts, _shortcuts);
|
||||
}
|
||||
|
||||
Future<void> setHotkey(String action, HotKey hotkey) async {
|
||||
@@ -67,7 +67,10 @@ class KeyboardShortcutsService {
|
||||
_hotkeys[action] = hotkey;
|
||||
|
||||
// Save to persistent storage
|
||||
await _settingsService.setKeyboardHotkey(action, hotkey);
|
||||
await _settingsService.write(SettingsService.keyboardHotkeys, {
|
||||
..._settingsService.read(SettingsService.keyboardHotkeys),
|
||||
action: hotkey,
|
||||
});
|
||||
|
||||
// Verify local cache is still correct
|
||||
if (_hotkeys[action] != hotkey) {
|
||||
@@ -76,16 +79,16 @@ class KeyboardShortcutsService {
|
||||
}
|
||||
|
||||
Future<void> refreshFromStorage() async {
|
||||
_hotkeys = await _settingsService.getKeyboardHotkeys();
|
||||
_seekTimeSmall = _settingsService.getSeekTimeSmall();
|
||||
_seekTimeLarge = _settingsService.getSeekTimeLarge();
|
||||
_hotkeys = _settingsService.read(SettingsService.keyboardHotkeys);
|
||||
_seekTimeSmall = _settingsService.read(SettingsService.seekTimeSmall);
|
||||
_seekTimeLarge = _settingsService.read(SettingsService.seekTimeLarge);
|
||||
}
|
||||
|
||||
Future<void> resetToDefaults() async {
|
||||
_shortcuts = _settingsService.getDefaultKeyboardShortcuts();
|
||||
_hotkeys = _settingsService.getDefaultKeyboardHotkeys();
|
||||
await _settingsService.setKeyboardShortcuts(_shortcuts);
|
||||
await _settingsService.setKeyboardHotkeys(_hotkeys);
|
||||
_shortcuts = SettingsService.defaultKeyboardShortcuts();
|
||||
_hotkeys = SettingsService.defaultKeyboardHotkeys();
|
||||
await _settingsService.write(SettingsService.keyboardShortcuts, _shortcuts);
|
||||
await _settingsService.write(SettingsService.keyboardHotkeys, _hotkeys);
|
||||
// Refresh cache to ensure consistency
|
||||
await refreshFromStorage();
|
||||
}
|
||||
@@ -265,12 +268,12 @@ class KeyboardShortcutsService {
|
||||
case 'volume_up':
|
||||
final newVolume = (player.state.volume + 10).clamp(0.0, _maxVolume.toDouble());
|
||||
player.setVolume(newVolume);
|
||||
_settingsService.setVolume(newVolume);
|
||||
_settingsService.write(SettingsService.volume, newVolume);
|
||||
break;
|
||||
case 'volume_down':
|
||||
final newVolume = (player.state.volume - 10).clamp(0.0, _maxVolume.toDouble());
|
||||
player.setVolume(newVolume);
|
||||
_settingsService.setVolume(newVolume);
|
||||
_settingsService.write(SettingsService.volume, newVolume);
|
||||
break;
|
||||
case 'seek_forward':
|
||||
performSeek(_seekTimeSmall);
|
||||
@@ -290,7 +293,7 @@ class KeyboardShortcutsService {
|
||||
case 'mute_toggle':
|
||||
final newVolume = player.state.volume > 0 ? 0.0 : 100.0;
|
||||
player.setVolume(newVolume);
|
||||
_settingsService.setVolume(newVolume);
|
||||
_settingsService.write(SettingsService.volume, newVolume);
|
||||
break;
|
||||
case 'subtitle_toggle':
|
||||
onToggleSubtitles?.call();
|
||||
@@ -316,16 +319,16 @@ class KeyboardShortcutsService {
|
||||
case 'speed_increase':
|
||||
final newRateUp = (player.state.rate + 0.25).clamp(0.25, 3.0);
|
||||
player.setRate(newRateUp);
|
||||
_settingsService.setDefaultPlaybackSpeed(newRateUp);
|
||||
_settingsService.write(SettingsService.defaultPlaybackSpeed, newRateUp);
|
||||
break;
|
||||
case 'speed_decrease':
|
||||
final newRateDown = (player.state.rate - 0.25).clamp(0.25, 3.0);
|
||||
player.setRate(newRateDown);
|
||||
_settingsService.setDefaultPlaybackSpeed(newRateDown);
|
||||
_settingsService.write(SettingsService.defaultPlaybackSpeed, newRateDown);
|
||||
break;
|
||||
case 'speed_reset':
|
||||
player.setRate(1.0);
|
||||
_settingsService.setDefaultPlaybackSpeed(1.0);
|
||||
_settingsService.write(SettingsService.defaultPlaybackSpeed, 1.0);
|
||||
break;
|
||||
case 'sub_seek_next':
|
||||
player.command(['sub-seek', '1']);
|
||||
|
||||
@@ -242,7 +242,7 @@ class MultiServerManager {
|
||||
.fetchServerPrefs()
|
||||
.then((_) {
|
||||
final threshold = client.watchedThresholdPercent;
|
||||
SettingsService.instanceOrNull?.setWatchedThreshold(serverId, threshold);
|
||||
SettingsService.instanceOrNull?.write(SettingsService.watchedThresholdPref(serverId), threshold);
|
||||
})
|
||||
.catchError((Object e, StackTrace st) {
|
||||
appLogger.w('fetchServerPrefs failed for ${server.name}', error: e, stackTrace: st);
|
||||
|
||||
@@ -44,7 +44,7 @@ class OfflineWatchSyncService extends ChangeNotifier {
|
||||
if (client != null && client.serverPrefs.isNotEmpty) {
|
||||
return client.watchedThresholdPercent / 100.0;
|
||||
}
|
||||
final cached = SettingsService.instanceOrNull?.getWatchedThreshold(serverId) ?? 90;
|
||||
final cached = SettingsService.instanceOrNull?.read(SettingsService.watchedThresholdPref(serverId)) ?? 90;
|
||||
return cached / 100.0;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/platform_detector.dart';
|
||||
import 'file_picker_service.dart';
|
||||
import 'settings_service.dart';
|
||||
import 'storage_service.dart';
|
||||
|
||||
class ImportResult {
|
||||
@@ -101,7 +102,7 @@ class SettingsExportService {
|
||||
/// prefix stripped so they can be re-scoped on import. Keys belonging to any
|
||||
/// OTHER user are skipped (we only export the active user's prefs).
|
||||
static Map<String, dynamic> buildExportMap(
|
||||
SharedPreferences prefs, {
|
||||
SharedPreferencesWithCache prefs, {
|
||||
String? currentUserUuid,
|
||||
String appVersion = '',
|
||||
}) {
|
||||
@@ -110,7 +111,7 @@ class SettingsExportService {
|
||||
? '$_userPrefixRoot${currentUserUuid}_'
|
||||
: null;
|
||||
|
||||
for (final fullKey in prefs.getKeys()) {
|
||||
for (final fullKey in prefs.keys) {
|
||||
String baseKey;
|
||||
if (currentUserPrefix != null && fullKey.startsWith(currentUserPrefix)) {
|
||||
baseKey = fullKey.substring(currentUserPrefix.length);
|
||||
@@ -159,7 +160,7 @@ class SettingsExportService {
|
||||
/// Throws [SettingsExportException] for structural problems.
|
||||
static Future<ImportResult> applyImportMap(
|
||||
Map<String, dynamic> data,
|
||||
SharedPreferences prefs, {
|
||||
SharedPreferencesWithCache prefs, {
|
||||
required String currentUserUuid,
|
||||
}) async {
|
||||
final version = data['formatVersion'];
|
||||
@@ -222,7 +223,7 @@ class SettingsExportService {
|
||||
return prefixes.any(baseKey.startsWith);
|
||||
}
|
||||
|
||||
static Future<bool> _writeTyped(SharedPreferences prefs, String key, String type, Object? value) async {
|
||||
static Future<bool> _writeTyped(SharedPreferencesWithCache prefs, String key, String type, Object? value) async {
|
||||
try {
|
||||
switch (type) {
|
||||
case _typeBool:
|
||||
@@ -270,7 +271,7 @@ class SettingsExportService {
|
||||
///
|
||||
/// Throws [SettingsExportException] on failure.
|
||||
static Future<String?> exportToFile() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = (await SettingsService.getInstance()).prefs;
|
||||
final storage = await StorageService.getInstance();
|
||||
String appVersion = '';
|
||||
try {
|
||||
@@ -359,7 +360,7 @@ class SettingsExportService {
|
||||
throw const InvalidExportFileException('Invalid export file');
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = (await SettingsService.getInstance()).prefs;
|
||||
return applyImportMap(data, prefs, currentUserUuid: uuid);
|
||||
}
|
||||
}
|
||||
|
||||
+511
-1443
File diff suppressed because it is too large
Load Diff
@@ -368,7 +368,7 @@ class StorageService extends BaseSharedPreferencesService {
|
||||
/// Load all persisted episode counts
|
||||
Map<String, int> loadAllEpisodeCounts() {
|
||||
final counts = <String, int>{};
|
||||
final keys = prefs.getKeys().where((k) => k.startsWith(_prefixEpisodeCount));
|
||||
final keys = prefs.keys.where((k) => k.startsWith(_prefixEpisodeCount));
|
||||
|
||||
for (final key in keys) {
|
||||
final globalKey = key.replaceFirst(_prefixEpisodeCount, '');
|
||||
@@ -415,7 +415,7 @@ class StorageService extends BaseSharedPreferencesService {
|
||||
|
||||
/// Remove all keys matching a prefix
|
||||
Future<void> _clearKeysWithPrefix(String prefix) async {
|
||||
final keys = prefs.getKeys().where((k) => k.startsWith(prefix));
|
||||
final keys = prefs.keys.where((k) => k.startsWith(prefix));
|
||||
await Future.wait(keys.map((k) => prefs.remove(k)));
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ class TrackManager {
|
||||
preferredAudioTrack: preferredAudioTrack,
|
||||
preferredSubtitleTrack: preferredSubtitleTrack,
|
||||
preferredSecondarySubtitleTrack: preferredSecondarySubtitleTrack,
|
||||
defaultPlaybackSpeed: settingsService.getDefaultPlaybackSpeed(),
|
||||
defaultPlaybackSpeed: settingsService.read(SettingsService.defaultPlaybackSpeed),
|
||||
onAudioTrackChanged: onAudioTrackChanged,
|
||||
onSubtitleTrackChanged: onSubtitleTrackChanged,
|
||||
);
|
||||
@@ -354,7 +354,7 @@ class TrackManager {
|
||||
/// Common guard checks for track change handlers.
|
||||
Future<int?> _guardTrackChange(PlexMediaInfo? info) async {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (!settings.getRememberTrackSelections()) return null;
|
||||
if (!settings.read(SettingsService.rememberTrackSelections)) return null;
|
||||
|
||||
if (info == null) {
|
||||
appLogger.w('No media info available, cannot save stream selection');
|
||||
|
||||
@@ -30,7 +30,7 @@ class AnilistTracker extends TrackerBase {
|
||||
bool get hasActiveClient => _client != null;
|
||||
|
||||
@override
|
||||
bool readEnabledSetting(SettingsService settings) => settings.getEnableAnilistScrobble();
|
||||
bool readEnabledSetting(SettingsService settings) => settings.read(SettingsService.enableAnilistScrobble);
|
||||
|
||||
void rebindSession(AnilistSession? session, {required void Function() onSessionInvalidated}) {
|
||||
_client?.dispose();
|
||||
|
||||
@@ -35,7 +35,7 @@ class MalTracker extends TrackerBase {
|
||||
bool get hasActiveClient => _client != null;
|
||||
|
||||
@override
|
||||
bool readEnabledSetting(SettingsService settings) => settings.getEnableMalScrobble();
|
||||
bool readEnabledSetting(SettingsService settings) => settings.read(SettingsService.enableMalScrobble);
|
||||
|
||||
void rebindSession(
|
||||
MalSession? session, {
|
||||
|
||||
@@ -33,7 +33,7 @@ class SimklTracker extends TrackerBase {
|
||||
bool get hasActiveClient => _client != null;
|
||||
|
||||
@override
|
||||
bool readEnabledSetting(SettingsService settings) => settings.getEnableSimklScrobble();
|
||||
bool readEnabledSetting(SettingsService settings) => settings.read(SettingsService.enableSimklScrobble);
|
||||
|
||||
void rebindSession(SimklSession? session, {required void Function() onSessionInvalidated}) {
|
||||
_client?.dispose();
|
||||
|
||||
@@ -55,7 +55,7 @@ class TraktScrobbleService {
|
||||
if (_isInitialized) return;
|
||||
_isInitialized = true;
|
||||
final settings = await SettingsService.getInstance();
|
||||
_isEnabled = settings.getEnableTraktScrobble();
|
||||
_isEnabled = settings.read(SettingsService.enableTraktScrobble);
|
||||
}
|
||||
|
||||
Future<void> setEnabled(bool enabled) async {
|
||||
|
||||
@@ -56,7 +56,7 @@ class TraktSyncService {
|
||||
_serverManager = serverManager;
|
||||
|
||||
final settings = await SettingsService.getInstance();
|
||||
_isEnabled = settings.getEnableTraktWatchedSync();
|
||||
_isEnabled = settings.read(SettingsService.enableTraktWatchedSync);
|
||||
|
||||
_subscription = WatchStateNotifier().stream.listen(
|
||||
_onWatchStateEvent,
|
||||
|
||||
@@ -70,7 +70,7 @@ Future<bool?> navigateToVideoPlayer(
|
||||
try {
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
final seriesKey = metadata.grandparentRatingKey ?? metadata.ratingKey;
|
||||
final savedPreference = settingsService.getMediaVersionPreference(seriesKey);
|
||||
final savedPreference = settingsService.read(SettingsService.mediaVersionPreferences)[seriesKey];
|
||||
if (savedPreference != null) {
|
||||
mediaIndex = savedPreference;
|
||||
// Pre-parsed playbackData was built with mediaIndex=0; invalidate if
|
||||
@@ -87,7 +87,7 @@ Future<bool?> navigateToVideoPlayer(
|
||||
// Check if external player is enabled
|
||||
try {
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (settingsService.getUseExternalPlayer()) {
|
||||
if (settingsService.read(SettingsService.useExternalPlayer)) {
|
||||
bool launched = false;
|
||||
|
||||
if (isOffline) {
|
||||
|
||||
@@ -210,7 +210,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
appLogger.d('WatchTogether: Creating session with control mode: $controlMode');
|
||||
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl();
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.read(SettingsService.customRelayUrl);
|
||||
_peerService = WatchTogetherPeerService(customBaseUrl: customRelayUrl);
|
||||
_setupPeerServiceListeners();
|
||||
|
||||
@@ -258,7 +258,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
|
||||
appLogger.d('WatchTogether: Joining session: $sessionId');
|
||||
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl();
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.read(SettingsService.customRelayUrl);
|
||||
_peerService = WatchTogetherPeerService(customBaseUrl: customRelayUrl);
|
||||
_setupPeerServiceListeners();
|
||||
|
||||
@@ -312,7 +312,7 @@ class WatchTogetherProvider with ChangeNotifier {
|
||||
// Probe the relay with a lightweight peer service to check room occupancy,
|
||||
// then do a single createSession or joinSession. This avoids the crash-prone
|
||||
// join→teardown→create cycle on the provider.
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl();
|
||||
final customRelayUrl = SettingsService.instanceOrNull?.read(SettingsService.customRelayUrl);
|
||||
final probe = WatchTogetherPeerService(customBaseUrl: customRelayUrl);
|
||||
bool shouldBeHost;
|
||||
try {
|
||||
|
||||
@@ -91,7 +91,7 @@ class _NotInSessionViewState extends State<_NotInSessionView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl();
|
||||
_customRelayUrl = SettingsService.instanceOrNull?.read(SettingsService.customRelayUrl);
|
||||
_recentRooms = RecentRoomsService.getRecentRooms();
|
||||
_checkHealth();
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class RecentRoomsService {
|
||||
static const int _maxRooms = 20;
|
||||
|
||||
static List<RecentRoom> getRecentRooms() {
|
||||
final json = SettingsService.instanceOrNull?.getRecentRooms();
|
||||
final json = SettingsService.instanceOrNull?.read(SettingsService.recentRooms);
|
||||
if (json == null) return [];
|
||||
try {
|
||||
final list = jsonDecode(json) as List<dynamic>;
|
||||
@@ -61,7 +61,10 @@ class RecentRoomsService {
|
||||
static Future<void> _save(List<RecentRoom> rooms) async {
|
||||
rooms.sort((a, b) => b.lastUsed.compareTo(a.lastUsed));
|
||||
if (rooms.length > _maxRooms) rooms.removeRange(_maxRooms, rooms.length);
|
||||
await SettingsService.instanceOrNull?.setRecentRooms(jsonEncode(rooms.map((r) => r.toJson()).toList()));
|
||||
await SettingsService.instanceOrNull?.write(
|
||||
SettingsService.recentRooms,
|
||||
jsonEncode(rooms.map((r) => r.toJson()).toList()),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> addOrUpdateRoom(String code, {String? name, ControlMode? controlMode}) async {
|
||||
|
||||
@@ -150,18 +150,18 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_enableHDR = settings.getEnableHDR();
|
||||
_showPerformanceOverlay = settings.getShowPerformanceOverlay();
|
||||
_autoPlayNextEpisode = settings.getAutoPlayNextEpisode();
|
||||
_audioPassthrough = settings.getAudioPassthrough();
|
||||
_audioNormalization = settings.getAudioNormalization();
|
||||
_enableHDR = settings.read(SettingsService.enableHDR);
|
||||
_showPerformanceOverlay = settings.read(SettingsService.showPerformanceOverlay);
|
||||
_autoPlayNextEpisode = settings.read(SettingsService.autoPlayNextEpisode);
|
||||
_audioPassthrough = settings.read(SettingsService.audioPassthrough);
|
||||
_audioNormalization = settings.read(SettingsService.audioNormalization);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _toggleHDR() async {
|
||||
final newValue = !_enableHDR;
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setEnableHDR(newValue);
|
||||
await settings.write(SettingsService.enableHDR, newValue);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_enableHDR = newValue;
|
||||
@@ -173,7 +173,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
Future<void> _togglePerformanceOverlay() async {
|
||||
final newValue = !_showPerformanceOverlay;
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setShowPerformanceOverlay(newValue);
|
||||
await settings.write(SettingsService.showPerformanceOverlay, newValue);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_showPerformanceOverlay = newValue;
|
||||
@@ -183,7 +183,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
Future<void> _toggleAutoPlayNextEpisode() async {
|
||||
final newValue = !_autoPlayNextEpisode;
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setAutoPlayNextEpisode(newValue);
|
||||
await settings.write(SettingsService.autoPlayNextEpisode, newValue);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_autoPlayNextEpisode = newValue;
|
||||
@@ -193,7 +193,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
Future<void> _toggleAudioPassthrough() async {
|
||||
final newValue = !_audioPassthrough;
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setAudioPassthrough(newValue);
|
||||
await settings.write(SettingsService.audioPassthrough, newValue);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_audioPassthrough = newValue;
|
||||
@@ -204,7 +204,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
Future<void> _toggleAudioNormalization() async {
|
||||
final newValue = !_audioNormalization;
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setAudioNormalization(newValue);
|
||||
await settings.write(SettingsService.audioNormalization, newValue);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_audioNormalization = newValue;
|
||||
@@ -256,9 +256,9 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
onOffsetChanged: (offset) async {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (isSubtitle) {
|
||||
await settings.setSubtitleSyncOffset(offset);
|
||||
await settings.write(SettingsService.subtitleSyncOffset, offset);
|
||||
} else {
|
||||
await settings.setAudioSyncOffset(offset);
|
||||
await settings.write(SettingsService.audioSyncOffset, offset);
|
||||
}
|
||||
widget.onSyncOffsetChanged?.call(propertyName, offset);
|
||||
},
|
||||
@@ -562,7 +562,7 @@ class _VideoSettingsSheetState extends State<VideoSettingsSheet> {
|
||||
widget.player.setRate(speed);
|
||||
// Save as default playback speed
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setDefaultPlaybackSpeed(speed);
|
||||
await settings.write(SettingsService.defaultPlaybackSpeed, speed);
|
||||
if (context.mounted) {
|
||||
OverlaySheetController.of(context).close(); // Close sheet after selection
|
||||
}
|
||||
|
||||
@@ -647,18 +647,18 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_seekTimeSmall = settingsService.getSeekTimeSmall();
|
||||
_rewindOnResume = settingsService.getRewindOnResume();
|
||||
_audioSyncOffset = settingsService.getAudioSyncOffset();
|
||||
_subtitleSyncOffset = settingsService.getSubtitleSyncOffset();
|
||||
_isRotationLocked = settingsService.getRotationLocked();
|
||||
_autoSkipIntro = settingsService.getAutoSkipIntro();
|
||||
_autoSkipCredits = settingsService.getAutoSkipCredits();
|
||||
_autoSkipDelay = settingsService.getAutoSkipDelay();
|
||||
_videoPlayerNavigationEnabled = settingsService.getVideoPlayerNavigationEnabled();
|
||||
_showPerformanceOverlay = settingsService.getShowPerformanceOverlay();
|
||||
_autoHidePerformanceOverlay = settingsService.getAutoHidePerformanceOverlay();
|
||||
_clickVideoTogglesPlayback = settingsService.getClickVideoTogglesPlayback();
|
||||
_seekTimeSmall = settingsService.read(SettingsService.seekTimeSmall);
|
||||
_rewindOnResume = settingsService.read(SettingsService.rewindOnResume);
|
||||
_audioSyncOffset = settingsService.read(SettingsService.audioSyncOffset);
|
||||
_subtitleSyncOffset = settingsService.read(SettingsService.subtitleSyncOffset);
|
||||
_isRotationLocked = settingsService.read(SettingsService.rotationLocked);
|
||||
_autoSkipIntro = settingsService.read(SettingsService.autoSkipIntro);
|
||||
_autoSkipCredits = settingsService.read(SettingsService.autoSkipCredits);
|
||||
_autoSkipDelay = settingsService.read(SettingsService.autoSkipDelay);
|
||||
_videoPlayerNavigationEnabled = settingsService.read(SettingsService.videoPlayerNavigationEnabled);
|
||||
_showPerformanceOverlay = settingsService.read(SettingsService.showPerformanceOverlay);
|
||||
_autoHidePerformanceOverlay = settingsService.read(SettingsService.autoHidePerformanceOverlay);
|
||||
_clickVideoTogglesPlayback = settingsService.read(SettingsService.clickVideoTogglesPlayback);
|
||||
});
|
||||
|
||||
// Focus play/pause if navigation is now enabled and controls are visible
|
||||
@@ -940,7 +940,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
final maxVol = _keyboardService!.maxVolume.toDouble();
|
||||
final newVolume = (volume - delta / 20).clamp(0.0, maxVol);
|
||||
widget.player.setVolume(newVolume);
|
||||
unawaited(SettingsService.getInstance().then((s) => s.setVolume(newVolume)));
|
||||
unawaited(SettingsService.getInstance().then((s) => s.write(SettingsService.volume, newVolume)));
|
||||
_showControlsFromPointerActivity();
|
||||
}
|
||||
}
|
||||
@@ -990,7 +990,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
|
||||
// Save to settings
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
await settingsService.setRotationLocked(_isRotationLocked);
|
||||
await settingsService.write(SettingsService.rotationLocked, _isRotationLocked);
|
||||
|
||||
if (_isRotationLocked) {
|
||||
// Locked: Allow landscape orientations only
|
||||
@@ -1085,8 +1085,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
appLogger.d('_loadPlaybackExtras: got client with serverId=${client.serverId}');
|
||||
|
||||
final settings = await SettingsService.getInstance();
|
||||
final introPattern = settings.getIntroPattern();
|
||||
final creditsPattern = settings.getCreditsPattern();
|
||||
final introPattern = settings.read(SettingsService.introPattern);
|
||||
final creditsPattern = settings.read(SettingsService.creditsPattern);
|
||||
final extras = await client.getPlaybackExtras(
|
||||
widget.metadata.ratingKey,
|
||||
introPattern: introPattern,
|
||||
@@ -1172,8 +1172,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
return PlaybackExtras.withChapterFallback(
|
||||
chapters: chapters,
|
||||
markers: markers,
|
||||
introPatternStr: settings.getIntroPattern(),
|
||||
creditsPatternStr: settings.getCreditsPattern(),
|
||||
introPatternStr: settings.read(SettingsService.introPattern),
|
||||
creditsPatternStr: settings.read(SettingsService.creditsPattern),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2592,7 +2592,10 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
||||
if (isVersionChange) {
|
||||
final settingsService = await SettingsService.getInstance();
|
||||
final seriesKey = widget.metadata.grandparentRatingKey ?? widget.metadata.ratingKey;
|
||||
await settingsService.setMediaVersionPreference(seriesKey, effectiveMediaIndex);
|
||||
await settingsService.write(SettingsService.mediaVersionPreferences, {
|
||||
...settingsService.read(SettingsService.mediaVersionPreferences),
|
||||
seriesKey: effectiveMediaIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Preserve session identifiers across the reload so Plex reuses the
|
||||
|
||||
@@ -67,7 +67,7 @@ class _VolumeControlState extends State<VolumeControl> {
|
||||
final settings = await SettingsService.getInstance();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_maxVolume = settings.getMaxVolume();
|
||||
_maxVolume = settings.read(SettingsService.maxVolume);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class _VolumeControlState extends State<VolumeControl> {
|
||||
final newVolume = (currentVolume + delta).clamp(0.0, _maxVolume.toDouble());
|
||||
widget.player.setVolume(newVolume);
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setVolume(newVolume);
|
||||
await settings.write(SettingsService.volume, newVolume);
|
||||
}
|
||||
|
||||
KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
|
||||
@@ -170,7 +170,7 @@ class _VolumeControlState extends State<VolumeControl> {
|
||||
final newVolume = isMuted ? 100.0 : 0.0;
|
||||
widget.player.setVolume(newVolume);
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setVolume(newVolume);
|
||||
await settings.write(SettingsService.volume, newVolume);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -261,7 +261,7 @@ class _VolumeControlState extends State<VolumeControl> {
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
final settings = await SettingsService.getInstance();
|
||||
await settings.setVolume(value);
|
||||
await settings.write(SettingsService.volume, value);
|
||||
},
|
||||
activeColor: Colors.white,
|
||||
inactiveColor: Colors.white.withValues(alpha: 0.3),
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
intl: ^0.20.2
|
||||
json_annotation: ^4.9.0
|
||||
shared_preferences: ^2.2.2
|
||||
shared_preferences: ^2.5.4
|
||||
cached_network_image: ^3.4.1
|
||||
flutter_cache_manager: ^3.4.1
|
||||
http: ^1.2.0
|
||||
|
||||
Reference in New Issue
Block a user