diff --git a/lib/components/settings/color_scheme_picker_dialog.dart b/lib/components/settings/color_scheme_picker_dialog.dart index 170bae94..f06a9d84 100644 --- a/lib/components/settings/color_scheme_picker_dialog.dart +++ b/lib/components/settings/color_scheme_picker_dialog.dart @@ -49,6 +49,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); final scheme = preferences.accentColorScheme; final active = useState(colorsMap.firstWhere( (element) { @@ -57,7 +58,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget { ).name); onOk() { - preferences.setAccentColorScheme( + preferencesNotifier.setAccentColorScheme( colorsMap.firstWhere( (element) { return element.name == active.value; diff --git a/lib/pages/settings/sections/about.dart b/lib/pages/settings/sections/about.dart index 0340b27c..85181355 100644 --- a/lib/pages/settings/sections/about.dart +++ b/lib/pages/settings/sections/about.dart @@ -16,6 +16,7 @@ class SettingsAboutSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); return SectionCardWithHeading( heading: context.l10n.about, @@ -68,7 +69,7 @@ class SettingsAboutSection extends HookConsumerWidget { secondary: const Icon(SpotubeIcons.update), title: Text(context.l10n.check_for_updates), value: preferences.checkUpdate, - onChanged: (checked) => preferences.setCheckUpdate(checked), + onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked), ), ListTile( leading: const Icon(SpotubeIcons.info), diff --git a/lib/pages/settings/sections/appearance.dart b/lib/pages/settings/sections/appearance.dart index f4b097e8..5e1ffa50 100644 --- a/lib/pages/settings/sections/appearance.dart +++ b/lib/pages/settings/sections/appearance.dart @@ -15,6 +15,7 @@ class SettingsAppearanceSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); final pickColorScheme = useCallback(() { return () => showDialog( context: context, @@ -33,7 +34,7 @@ class SettingsAppearanceSection extends HookConsumerWidget { value: preferences.layoutMode, onChanged: (value) { if (value != null) { - preferences.setLayoutMode(value); + preferencesNotifier.setLayoutMode(value); } }, options: [ @@ -71,7 +72,7 @@ class SettingsAppearanceSection extends HookConsumerWidget { ], onChanged: (value) { if (value != null) { - preferences.setThemeMode(value); + preferencesNotifier.setThemeMode(value); } }, ), @@ -80,7 +81,7 @@ class SettingsAppearanceSection extends HookConsumerWidget { title: Text(context.l10n.use_amoled_mode), subtitle: Text(context.l10n.pitch_dark_theme), value: preferences.amoledDarkTheme, - onChanged: preferences.setAmoledDarkTheme, + onChanged: preferencesNotifier.setAmoledDarkTheme, ), ListTile( leading: const Icon(SpotubeIcons.palette), @@ -101,7 +102,7 @@ class SettingsAppearanceSection extends HookConsumerWidget { title: Text(context.l10n.sync_album_color), subtitle: Text(context.l10n.sync_album_color_description), value: preferences.albumColorSync, - onChanged: preferences.setAlbumColorSync, + onChanged: preferencesNotifier.setAlbumColorSync, ), ], ); diff --git a/lib/pages/settings/sections/desktop.dart b/lib/pages/settings/sections/desktop.dart index d12bcb41..1cc2c5c8 100644 --- a/lib/pages/settings/sections/desktop.dart +++ b/lib/pages/settings/sections/desktop.dart @@ -12,6 +12,7 @@ class SettingsDesktopSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); return SectionCardWithHeading( heading: context.l10n.desktop, @@ -32,7 +33,7 @@ class SettingsDesktopSection extends HookConsumerWidget { ], onChanged: (value) { if (value != null) { - preferences.setCloseBehavior(value); + preferencesNotifier.setCloseBehavior(value); } }, ), @@ -40,13 +41,13 @@ class SettingsDesktopSection extends HookConsumerWidget { secondary: const Icon(SpotubeIcons.tray), title: Text(context.l10n.show_tray_icon), value: preferences.showSystemTrayIcon, - onChanged: preferences.setShowSystemTrayIcon, + onChanged: preferencesNotifier.setShowSystemTrayIcon, ), SwitchListTile( secondary: const Icon(SpotubeIcons.window), title: Text(context.l10n.use_system_title_bar), value: preferences.systemTitleBar, - onChanged: preferences.setSystemTitleBar, + onChanged: preferencesNotifier.setSystemTitleBar, ), ], ); diff --git a/lib/pages/settings/sections/downloads.dart b/lib/pages/settings/sections/downloads.dart index 1f157037..ff64cdea 100644 --- a/lib/pages/settings/sections/downloads.dart +++ b/lib/pages/settings/sections/downloads.dart @@ -14,6 +14,7 @@ class SettingsDownloadsSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); final preferences = ref.watch(userPreferencesProvider); final pickDownloadLocation = useCallback(() async { @@ -22,13 +23,13 @@ class SettingsDownloadsSection extends HookConsumerWidget { initialDirectory: preferences.downloadLocation, ); if (dirStr == null) return; - preferences.setDownloadLocation(dirStr); + preferencesNotifier.setDownloadLocation(dirStr); } else { String? dirStr = await getDirectoryPath( initialDirectory: preferences.downloadLocation, ); if (dirStr == null) return; - preferences.setDownloadLocation(dirStr); + preferencesNotifier.setDownloadLocation(dirStr); } }, [preferences.downloadLocation]); diff --git a/lib/pages/settings/sections/language_region.dart b/lib/pages/settings/sections/language_region.dart index 64c56224..ece28455 100644 --- a/lib/pages/settings/sections/language_region.dart +++ b/lib/pages/settings/sections/language_region.dart @@ -17,6 +17,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); final mediaQuery = MediaQuery.of(context); return SectionCardWithHeading( @@ -26,7 +27,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget { value: preferences.locale, onChanged: (locale) { if (locale == null) return; - preferences.setLocale(locale); + preferencesNotifier.setLocale(locale); }, title: Text(context.l10n.language), secondary: const Icon(SpotubeIcons.language), @@ -57,7 +58,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget { value: preferences.recommendationMarket, onChanged: (value) { if (value == null) return; - preferences.setRecommendationMarket(value); + preferencesNotifier.setRecommendationMarket(value); }, options: spotifyMarkets .map( diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index cf7e33e9..39d9b7c2 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -18,6 +18,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); final theme = Theme.of(context); return SectionCardWithHeading( @@ -39,7 +40,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { ], onChanged: (value) { if (value != null) { - preferences.setAudioQuality(value); + preferencesNotifier.setAudioQuality(value); } }, ), @@ -55,7 +56,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { .toList(), onChanged: (value) { if (value == null) return; - preferences.setYoutubeApiType(value); + preferencesNotifier.setYoutubeApiType(value); }, ), AnimatedSwitcher( @@ -113,7 +114,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { .toList(), onChanged: (value) { if (value != null) { - preferences.setPipedInstance(value); + preferencesNotifier.setPipedInstance(value); } }, ); @@ -141,7 +142,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { .toList(), onChanged: (value) { if (value == null) return; - preferences.setSearchMode(value); + preferencesNotifier.setSearchMode(value); }, ), ), @@ -155,7 +156,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { title: Text(context.l10n.skip_non_music), value: preferences.skipNonMusic, onChanged: (state) { - preferences.setSkipNonMusic(state); + preferencesNotifier.setSkipNonMusic(state); }, ), ), @@ -172,7 +173,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { secondary: const Icon(SpotubeIcons.normalize), title: Text(context.l10n.normalize_audio), value: preferences.normalizeAudio, - onChanged: preferences.setNormalizeAudio, + onChanged: preferencesNotifier.setNormalizeAudio, ), AdaptiveSelectTile( secondary: const Icon(SpotubeIcons.stream), @@ -190,7 +191,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { .toList(), onChanged: (value) { if (value == null) return; - preferences.setStreamMusicCodec(value); + preferencesNotifier.setStreamMusicCodec(value); }, ), AdaptiveSelectTile( @@ -209,7 +210,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { .toList(), onChanged: (value) { if (value == null) return; - preferences.setDownloadMusicCodec(value); + preferencesNotifier.setDownloadMusicCodec(value); }, ), ], diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 5b377a1f..baf245b4 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -20,7 +20,7 @@ class SettingsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final preferences = ref.watch(userPreferencesProvider); + final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); return SafeArea( bottom: false, @@ -49,7 +49,7 @@ class SettingsPage extends HookConsumerWidget { const SettingsAboutSection(), Center( child: FilledButton( - onPressed: preferences.reset, + onPressed: preferencesNotifier.reset, child: Text(context.l10n.restore_defaults), ), ), diff --git a/lib/provider/user_preferences_provider.dart b/lib/provider/user_preferences_provider.dart index 3355adb0..80c71de9 100644 --- a/lib/provider/user_preferences_provider.dart +++ b/lib/provider/user_preferences_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -13,7 +12,7 @@ import 'package:spotube/provider/palette_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/utils/persisted_change_notifier.dart'; +import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/platform.dart'; import 'package:path/path.dart' as path; @@ -48,220 +47,103 @@ enum MusicCodec { const MusicCodec._(this.label); } -class UserPreferences extends PersistedChangeNotifier { - AudioQuality audioQuality; - bool albumColorSync; - bool amoledDarkTheme; - bool checkUpdate; - bool normalizeAudio; - bool showSystemTrayIcon; - bool skipNonMusic; - bool systemTitleBar; - CloseBehavior closeBehavior; - late SpotubeColor accentColorScheme; - LayoutMode layoutMode; - Locale locale; - Market recommendationMarket; - SearchMode searchMode; +class UserPreferences { + final AudioQuality audioQuality; + final bool albumColorSync; + final bool amoledDarkTheme; + final bool checkUpdate; + final bool normalizeAudio; + final bool showSystemTrayIcon; + final bool skipNonMusic; + final bool systemTitleBar; + final CloseBehavior closeBehavior; + final SpotubeColor accentColorScheme; + final LayoutMode layoutMode; + final Locale locale; + final Market recommendationMarket; + final SearchMode searchMode; String downloadLocation; - String pipedInstance; - ThemeMode themeMode; - YoutubeApiType youtubeApiType; - MusicCodec streamMusicCodec; - MusicCodec downloadMusicCodec; + final String pipedInstance; + final ThemeMode themeMode; + final YoutubeApiType youtubeApiType; + final MusicCodec streamMusicCodec; + final MusicCodec downloadMusicCodec; - final Ref ref; - - UserPreferences( - this.ref, { - this.recommendationMarket = Market.US, - this.themeMode = ThemeMode.system, - this.layoutMode = LayoutMode.adaptive, - this.albumColorSync = true, - this.checkUpdate = true, - this.audioQuality = AudioQuality.high, - this.downloadLocation = "", - this.closeBehavior = CloseBehavior.close, - this.showSystemTrayIcon = true, - this.locale = const Locale("system", "system"), - this.pipedInstance = "https://pipedapi.kavin.rocks", - this.searchMode = SearchMode.youtube, - this.skipNonMusic = true, - this.youtubeApiType = YoutubeApiType.youtube, - this.systemTitleBar = false, - this.amoledDarkTheme = false, - this.normalizeAudio = true, - this.streamMusicCodec = MusicCodec.weba, - this.downloadMusicCodec = MusicCodec.m4a, - SpotubeColor? accentColorScheme, - }) : super() { - this.accentColorScheme = - accentColorScheme ?? SpotubeColor(Colors.blue.value, name: "Blue"); - if (downloadLocation.isEmpty && !kIsWeb) { + UserPreferences({ + required AudioQuality? audioQuality, + required bool? albumColorSync, + required bool? amoledDarkTheme, + required bool? checkUpdate, + required bool? normalizeAudio, + required bool? showSystemTrayIcon, + required bool? skipNonMusic, + required bool? systemTitleBar, + required CloseBehavior? closeBehavior, + required SpotubeColor? accentColorScheme, + required LayoutMode? layoutMode, + required Locale? locale, + required Market? recommendationMarket, + required SearchMode? searchMode, + required String? downloadLocation, + required String? pipedInstance, + required ThemeMode? themeMode, + required YoutubeApiType? youtubeApiType, + required MusicCodec? streamMusicCodec, + required MusicCodec? downloadMusicCodec, + }) : accentColorScheme = + accentColorScheme ?? const SpotubeColor(0xFF2196F3, name: "Blue"), + albumColorSync = albumColorSync ?? true, + amoledDarkTheme = amoledDarkTheme ?? false, + audioQuality = audioQuality ?? AudioQuality.high, + checkUpdate = checkUpdate ?? true, + closeBehavior = closeBehavior ?? CloseBehavior.close, + downloadLocation = downloadLocation ?? "", + downloadMusicCodec = downloadMusicCodec ?? MusicCodec.m4a, + layoutMode = layoutMode ?? LayoutMode.adaptive, + locale = locale ?? const Locale("system", "system"), + normalizeAudio = normalizeAudio ?? true, + pipedInstance = pipedInstance ?? "https://pipedapi.kavin.rocks", + recommendationMarket = recommendationMarket ?? Market.US, + searchMode = searchMode ?? SearchMode.youtube, + showSystemTrayIcon = showSystemTrayIcon ?? true, + skipNonMusic = skipNonMusic ?? true, + streamMusicCodec = streamMusicCodec ?? MusicCodec.weba, + systemTitleBar = systemTitleBar ?? false, + themeMode = themeMode ?? ThemeMode.system, + youtubeApiType = youtubeApiType ?? YoutubeApiType.youtube { + if (downloadLocation == null) { _getDefaultDownloadDirectory().then( - (value) { - downloadLocation = value; - }, + (value) => this.downloadLocation = value, ); } } - void reset() { - setRecommendationMarket(Market.US); - setThemeMode(ThemeMode.system); - setLayoutMode(LayoutMode.adaptive); - setAlbumColorSync(true); - setCheckUpdate(true); - setAudioQuality(AudioQuality.high); - setDownloadLocation(""); - setCloseBehavior(CloseBehavior.close); - setShowSystemTrayIcon(true); - setLocale(const Locale("system", "system")); - setPipedInstance("https://pipedapi.kavin.rocks"); - setSearchMode(SearchMode.youtube); - setSkipNonMusic(true); - setYoutubeApiType(YoutubeApiType.youtube); - setSystemTitleBar(false); - setAmoledDarkTheme(false); - setNormalizeAudio(true); - setAccentColorScheme(SpotubeColor(Colors.blue.value, name: "Blue")); - setStreamMusicCodec(MusicCodec.weba); - setDownloadMusicCodec(MusicCodec.m4a); + factory UserPreferences.withDefaults() { + return UserPreferences( + audioQuality: null, + albumColorSync: null, + amoledDarkTheme: null, + checkUpdate: null, + normalizeAudio: null, + showSystemTrayIcon: null, + skipNonMusic: null, + systemTitleBar: null, + closeBehavior: null, + accentColorScheme: null, + layoutMode: null, + locale: null, + recommendationMarket: null, + searchMode: null, + downloadLocation: null, + pipedInstance: null, + themeMode: null, + youtubeApiType: null, + streamMusicCodec: null, + downloadMusicCodec: null, + ); } - void setStreamMusicCodec(MusicCodec codec) { - streamMusicCodec = codec; - notifyListeners(); - updatePersistence(); - } - - void setDownloadMusicCodec(MusicCodec codec) { - downloadMusicCodec = codec; - notifyListeners(); - updatePersistence(); - } - - void setThemeMode(ThemeMode mode) { - themeMode = mode; - notifyListeners(); - updatePersistence(); - } - - void setRecommendationMarket(Market country) { - recommendationMarket = country; - notifyListeners(); - updatePersistence(); - } - - void setAccentColorScheme(SpotubeColor color) { - accentColorScheme = color; - notifyListeners(); - updatePersistence(); - } - - void setAlbumColorSync(bool sync) { - albumColorSync = sync; - if (!sync) { - ref.read(paletteProvider.notifier).state = null; - } else { - ref.read(ProxyPlaylistNotifier.notifier).updatePalette(); - } - notifyListeners(); - updatePersistence(); - } - - void setCheckUpdate(bool check) { - checkUpdate = check; - notifyListeners(); - updatePersistence(); - } - - void setAudioQuality(AudioQuality quality) { - audioQuality = quality; - notifyListeners(); - updatePersistence(); - } - - void setDownloadLocation(String downloadDir) { - if (downloadDir.isEmpty) return; - downloadLocation = downloadDir; - notifyListeners(); - updatePersistence(); - } - - void setLayoutMode(LayoutMode mode) { - layoutMode = mode; - notifyListeners(); - updatePersistence(); - } - - void setCloseBehavior(CloseBehavior behavior) { - closeBehavior = behavior; - notifyListeners(); - updatePersistence(); - } - - void setShowSystemTrayIcon(bool show) { - showSystemTrayIcon = show; - notifyListeners(); - updatePersistence(); - } - - void setLocale(Locale locale) { - this.locale = locale; - notifyListeners(); - updatePersistence(); - } - - void setPipedInstance(String instance) { - pipedInstance = instance; - notifyListeners(); - updatePersistence(); - } - - void setSearchMode(SearchMode mode) { - searchMode = mode; - notifyListeners(); - updatePersistence(); - } - - void setSkipNonMusic(bool skip) { - skipNonMusic = skip; - notifyListeners(); - updatePersistence(); - } - - void setYoutubeApiType(YoutubeApiType type) { - youtubeApiType = type; - notifyListeners(); - updatePersistence(); - } - - void setSystemTitleBar(bool isSystemTitleBar) { - systemTitleBar = isSystemTitleBar; - if (DesktopTools.platform.isDesktop) { - DesktopTools.window.setTitleBarStyle( - systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, - ); - } - notifyListeners(); - updatePersistence(); - } - - void setAmoledDarkTheme(bool isAmoled) { - amoledDarkTheme = isAmoled; - notifyListeners(); - updatePersistence(); - } - - void setNormalizeAudio(bool normalize) { - normalizeAudio = normalize; - audioPlayer.setAudioNormalization(normalize); - notifyListeners(); - updatePersistence(); - } - - Future _getDefaultDownloadDirectory() async { + static Future _getDefaultDownloadDirectory() async { if (kIsAndroid) return "/storage/emulated/0/Download/Spotube"; if (kIsMacOS) { @@ -273,102 +155,71 @@ class UserPreferences extends PersistedChangeNotifier { }); } - @override - FutureOr loadFromLocal(Map map) async { - recommendationMarket = Market.values.firstWhere( - (market) => - market.name == (map["recommendationMarket"] ?? recommendationMarket), - orElse: () => Market.US, - ); - checkUpdate = map["checkUpdate"] ?? checkUpdate; + static Future fromJson(Map json) async { + final localeMap = + json["locale"] != null ? jsonDecode(json["locale"]) : null; - themeMode = ThemeMode.values[map["themeMode"] ?? 0]; - accentColorScheme = map["accentColorScheme"] != null - ? SpotubeColor.fromString(map["accentColorScheme"]) - : accentColorScheme; - albumColorSync = map["albumColorSync"] ?? albumColorSync; - audioQuality = map["audioQuality"] != null - ? AudioQuality.values[map["audioQuality"]] - : audioQuality; - - if (!kIsWeb) { - downloadLocation = - map["downloadLocation"] ?? await _getDefaultDownloadDirectory(); + final systemTitleBar = json["systemTitleBar"] ?? false; + if (DesktopTools.platform.isDesktop) { + await DesktopTools.window.setTitleBarStyle( + systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, + ); } - layoutMode = LayoutMode.values.firstWhere( - (mode) => mode.name == map["layoutMode"], - orElse: () => kIsDesktop ? LayoutMode.extended : LayoutMode.compact, - ); - - closeBehavior = map["closeBehavior"] != null - ? CloseBehavior.values[map["closeBehavior"]] - : closeBehavior; - - showSystemTrayIcon = map["showSystemTrayIcon"] ?? showSystemTrayIcon; - - final localeMap = map["locale"] != null ? jsonDecode(map["locale"]) : null; - locale = - localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale; - - pipedInstance = map["pipedInstance"] ?? pipedInstance; - - searchMode = SearchMode.values.firstWhere( - (mode) => mode.name == map["searchMode"], - orElse: () => SearchMode.youtube, - ); - - skipNonMusic = map["skipNonMusic"] ?? skipNonMusic; - - youtubeApiType = YoutubeApiType.values.firstWhere( - (type) => type.name == map["youtubeApiType"], - orElse: () => YoutubeApiType.youtube, - ); - - systemTitleBar = map["systemTitleBar"] ?? systemTitleBar; - // updates the title bar - setSystemTitleBar(systemTitleBar); - - amoledDarkTheme = map["amoledDarkTheme"] ?? amoledDarkTheme; - - normalizeAudio = map["normalizeAudio"] ?? normalizeAudio; + final normalizeAudio = json["normalizeAudio"] ?? true; audioPlayer.setAudioNormalization(normalizeAudio); - streamMusicCodec = MusicCodec.values.firstWhere( - (codec) => codec.name == map["streamMusicCodec"], - orElse: () => MusicCodec.weba, - ); - - downloadMusicCodec = MusicCodec.values.firstWhere( - (codec) => codec.name == map["downloadMusicCodec"], - orElse: () => MusicCodec.m4a, + return UserPreferences( + accentColorScheme: json["accentColorScheme"] == null + ? null + : SpotubeColor.fromString(json["accentColorScheme"]), + albumColorSync: json["albumColorSync"], + amoledDarkTheme: json["amoledDarkTheme"], + audioQuality: AudioQuality.values[json["audioQuality"]], + checkUpdate: json["checkUpdate"], + closeBehavior: CloseBehavior.values[json["closeBehavior"]], + downloadLocation: + json["downloadLocation"] ?? await _getDefaultDownloadDirectory(), + downloadMusicCodec: MusicCodec.values[json["downloadMusicCodec"]], + layoutMode: LayoutMode.values[json["layoutMode"]], + locale: + localeMap == null ? null : Locale(localeMap?["lc"], localeMap?["cc"]), + normalizeAudio: json["normalizeAudio"], + pipedInstance: json["pipedInstance"], + recommendationMarket: Market.values[json["recommendationMarket"]], + searchMode: SearchMode.values[json["searchMode"]], + showSystemTrayIcon: json["showSystemTrayIcon"], + skipNonMusic: json["skipNonMusic"], + streamMusicCodec: MusicCodec.values[json["streamMusicCodec"]], + systemTitleBar: json["systemTitleBar"], + themeMode: ThemeMode.values[json["themeMode"]], + youtubeApiType: YoutubeApiType.values[json["youtubeApiType"]], ); } - @override - FutureOr> toMap() { + Map toJson() { return { - "recommendationMarket": recommendationMarket.name, + "recommendationMarket": recommendationMarket.index, "themeMode": themeMode.index, "accentColorScheme": accentColorScheme.toString(), "albumColorSync": albumColorSync, "checkUpdate": checkUpdate, "audioQuality": audioQuality.index, "downloadLocation": downloadLocation, - "layoutMode": layoutMode.name, + "layoutMode": layoutMode.index, "closeBehavior": closeBehavior.index, "showSystemTrayIcon": showSystemTrayIcon, "locale": jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}), "pipedInstance": pipedInstance, - "searchMode": searchMode.name, + "searchMode": searchMode.index, "skipNonMusic": skipNonMusic, - "youtubeApiType": youtubeApiType.name, + "youtubeApiType": youtubeApiType.index, 'systemTitleBar': systemTitleBar, "amoledDarkTheme": amoledDarkTheme, "normalizeAudio": normalizeAudio, - "streamMusicCodec": streamMusicCodec.name, - "downloadMusicCodec": downloadMusicCodec.name, + "streamMusicCodec": streamMusicCodec.index, + "downloadMusicCodec": downloadMusicCodec.index, }; } @@ -389,9 +240,13 @@ class UserPreferences extends PersistedChangeNotifier { YoutubeApiType? youtubeApiType, Market? recommendationMarket, bool? saveTrackLyrics, + bool? amoledDarkTheme, + bool? normalizeAudio, + MusicCodec? downloadMusicCodec, + MusicCodec? streamMusicCodec, + bool? systemTitleBar, }) { return UserPreferences( - ref, themeMode: themeMode ?? this.themeMode, accentColorScheme: accentColorScheme ?? this.accentColorScheme, albumColorSync: albumColorSync ?? this.albumColorSync, @@ -407,10 +262,130 @@ class UserPreferences extends PersistedChangeNotifier { skipNonMusic: skipNonMusic ?? this.skipNonMusic, youtubeApiType: youtubeApiType ?? this.youtubeApiType, recommendationMarket: recommendationMarket ?? this.recommendationMarket, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, ); } } -final userPreferencesProvider = ChangeNotifierProvider( - (ref) => UserPreferences(ref), +class UserPreferencesNotifier extends PersistedStateNotifier { + final Ref ref; + + UserPreferencesNotifier(this.ref) + : super(UserPreferences.withDefaults(), "preferences"); + + void reset() { + state = UserPreferences.withDefaults(); + } + + void setStreamMusicCodec(MusicCodec codec) { + state = state.copyWith(streamMusicCodec: codec); + } + + void setDownloadMusicCodec(MusicCodec codec) { + state = state.copyWith(downloadMusicCodec: codec); + } + + void setThemeMode(ThemeMode mode) { + state = state.copyWith(themeMode: mode); + } + + void setRecommendationMarket(Market country) { + state = state.copyWith(recommendationMarket: country); + } + + void setAccentColorScheme(SpotubeColor color) { + state = state.copyWith(accentColorScheme: color); + } + + void setAlbumColorSync(bool sync) { + state = state.copyWith(albumColorSync: sync); + + if (!sync) { + ref.read(paletteProvider.notifier).state = null; + } else { + ref.read(ProxyPlaylistNotifier.notifier).updatePalette(); + } + } + + void setCheckUpdate(bool check) { + state = state.copyWith(checkUpdate: check); + } + + void setAudioQuality(AudioQuality quality) { + state = state.copyWith(audioQuality: quality); + } + + void setDownloadLocation(String downloadDir) { + if (downloadDir.isEmpty) return; + state = state.copyWith(downloadLocation: downloadDir); + } + + void setLayoutMode(LayoutMode mode) { + state = state.copyWith(layoutMode: mode); + } + + void setCloseBehavior(CloseBehavior behavior) { + state = state.copyWith(closeBehavior: behavior); + } + + void setShowSystemTrayIcon(bool show) { + state = state.copyWith(showSystemTrayIcon: show); + } + + void setLocale(Locale locale) { + state = state.copyWith(locale: locale); + } + + void setPipedInstance(String instance) { + state = state.copyWith(pipedInstance: instance); + } + + void setSearchMode(SearchMode mode) { + state = state.copyWith(searchMode: mode); + } + + void setSkipNonMusic(bool skip) { + state = state.copyWith(skipNonMusic: skip); + } + + void setYoutubeApiType(YoutubeApiType type) { + state = state.copyWith(youtubeApiType: type); + } + + void setSystemTitleBar(bool isSystemTitleBar) { + state = state.copyWith(systemTitleBar: isSystemTitleBar); + if (DesktopTools.platform.isDesktop) { + DesktopTools.window.setTitleBarStyle( + isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, + ); + } + } + + void setAmoledDarkTheme(bool isAmoled) { + state = state.copyWith(amoledDarkTheme: isAmoled); + } + + void setNormalizeAudio(bool normalize) { + state = state.copyWith(normalizeAudio: normalize); + audioPlayer.setAudioNormalization(normalize); + } + + @override + FutureOr fromJson(Map json) { + return UserPreferences.fromJson(json); + } + + @override + Map toJson() { + return state.toJson(); + } +} + +final userPreferencesProvider = + StateNotifierProvider( + (ref) => UserPreferencesNotifier(ref), );