refactor(settings): typed Pref<T> declarations

This commit is contained in:
edde746
2026-04-25 10:52:34 +02:00
parent e4e8299981
commit 0531c33b0c
42 changed files with 1102 additions and 1971 deletions
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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);
+44 -192
View File
@@ -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);
}
+5 -5
View File
@@ -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)
+3 -3
View File
@@ -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();
+7 -6
View File
@@ -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);
+6 -6
View File
@@ -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);
},
),
);
+20 -18
View File
@@ -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);
},
),
+58 -55
View File
@@ -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));
}
+1 -1
View File
@@ -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();
+3 -3
View File
@@ -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);
}
+2 -2
View File
@@ -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)
+4 -4
View File
@@ -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();
+1 -1
View File
@@ -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) {
+23 -20
View File
@@ -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']);
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
}
+7 -6
View File
@@ -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);
}
}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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)));
}
+2 -2
View File
@@ -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();
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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,
+2 -2
View File
@@ -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
}
+22 -19
View File
@@ -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
View File
@@ -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