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