From e29a38dfa43ddf7a38046d1d40424f01dbe62261 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 13 Nov 2023 20:59:54 +0600 Subject: [PATCH] fix: changed settings are not persisting after force stop #821 --- .../settings/color_scheme_picker_dialog.dart | 3 +- lib/pages/settings/sections/about.dart | 3 +- lib/pages/settings/sections/appearance.dart | 9 +- lib/pages/settings/sections/desktop.dart | 7 +- lib/pages/settings/sections/downloads.dart | 5 +- .../settings/sections/language_region.dart | 5 +- lib/pages/settings/sections/playback.dart | 17 +- lib/pages/settings/settings.dart | 4 +- lib/provider/user_preferences_provider.dart | 533 +++++++++--------- 9 files changed, 284 insertions(+), 302 deletions(-) diff --git a/lib/components/settings/color_scheme_picker_dialog.dart b/lib/components/settings/color_scheme_picker_dialog.dart index 170bae94c..f06a9d84a 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 0340b27cd..85181355f 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 f4b097e8c..5e1ffa502 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 d12bcb41c..1cc2c5c8e 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 1f157037c..ff64cdeab 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 64c562249..ece28455b 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 cf7e33e94..39d9b7c27 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 5b377a1fc..baf245b41 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 3355adb04..80c71de92 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,369 +47,345 @@ 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; + + 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) => this.downloadLocation = value, + ); + } + } - final Ref ref; + 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, + ); + } - 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) { - _getDefaultDownloadDirectory().then( - (value) { - downloadLocation = value; - }, + static Future _getDefaultDownloadDirectory() async { + if (kIsAndroid) return "/storage/emulated/0/Download/Spotube"; + + if (kIsMacOS) { + return path.join((await getLibraryDirectory()).path, "Caches"); + } + + return getDownloadsDirectory().then((dir) { + return path.join(dir!.path, "Spotube"); + }); + } + + static Future fromJson(Map json) async { + final localeMap = + json["locale"] != null ? jsonDecode(json["locale"]) : null; + + final systemTitleBar = json["systemTitleBar"] ?? false; + if (DesktopTools.platform.isDesktop) { + await DesktopTools.window.setTitleBarStyle( + systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, ); } + + final normalizeAudio = json["normalizeAudio"] ?? true; + audioPlayer.setAudioNormalization(normalizeAudio); + + 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"]], + ); } + Map toJson() { + return { + "recommendationMarket": recommendationMarket.index, + "themeMode": themeMode.index, + "accentColorScheme": accentColorScheme.toString(), + "albumColorSync": albumColorSync, + "checkUpdate": checkUpdate, + "audioQuality": audioQuality.index, + "downloadLocation": downloadLocation, + "layoutMode": layoutMode.index, + "closeBehavior": closeBehavior.index, + "showSystemTrayIcon": showSystemTrayIcon, + "locale": + jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}), + "pipedInstance": pipedInstance, + "searchMode": searchMode.index, + "skipNonMusic": skipNonMusic, + "youtubeApiType": youtubeApiType.index, + 'systemTitleBar': systemTitleBar, + "amoledDarkTheme": amoledDarkTheme, + "normalizeAudio": normalizeAudio, + "streamMusicCodec": streamMusicCodec.index, + "downloadMusicCodec": downloadMusicCodec.index, + }; + } + + UserPreferences copyWith({ + ThemeMode? themeMode, + SpotubeColor? accentColorScheme, + bool? albumColorSync, + bool? checkUpdate, + AudioQuality? audioQuality, + String? downloadLocation, + LayoutMode? layoutMode, + CloseBehavior? closeBehavior, + bool? showSystemTrayIcon, + Locale? locale, + String? pipedInstance, + SearchMode? searchMode, + bool? skipNonMusic, + YoutubeApiType? youtubeApiType, + Market? recommendationMarket, + bool? saveTrackLyrics, + bool? amoledDarkTheme, + bool? normalizeAudio, + MusicCodec? downloadMusicCodec, + MusicCodec? streamMusicCodec, + bool? systemTitleBar, + }) { + return UserPreferences( + themeMode: themeMode ?? this.themeMode, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + albumColorSync: albumColorSync ?? this.albumColorSync, + checkUpdate: checkUpdate ?? this.checkUpdate, + audioQuality: audioQuality ?? this.audioQuality, + downloadLocation: downloadLocation ?? this.downloadLocation, + layoutMode: layoutMode ?? this.layoutMode, + closeBehavior: closeBehavior ?? this.closeBehavior, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + locale: locale ?? this.locale, + pipedInstance: pipedInstance ?? this.pipedInstance, + searchMode: searchMode ?? this.searchMode, + 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, + ); + } +} + +class UserPreferencesNotifier extends PersistedStateNotifier { + final Ref ref; + + UserPreferencesNotifier(this.ref) + : super(UserPreferences.withDefaults(), "preferences"); + 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); + state = UserPreferences.withDefaults(); } void setStreamMusicCodec(MusicCodec codec) { - streamMusicCodec = codec; - notifyListeners(); - updatePersistence(); + state = state.copyWith(streamMusicCodec: codec); } void setDownloadMusicCodec(MusicCodec codec) { - downloadMusicCodec = codec; - notifyListeners(); - updatePersistence(); + state = state.copyWith(downloadMusicCodec: codec); } void setThemeMode(ThemeMode mode) { - themeMode = mode; - notifyListeners(); - updatePersistence(); + state = state.copyWith(themeMode: mode); } void setRecommendationMarket(Market country) { - recommendationMarket = country; - notifyListeners(); - updatePersistence(); + state = state.copyWith(recommendationMarket: country); } void setAccentColorScheme(SpotubeColor color) { - accentColorScheme = color; - notifyListeners(); - updatePersistence(); + state = state.copyWith(accentColorScheme: color); } void setAlbumColorSync(bool sync) { - albumColorSync = sync; + state = state.copyWith(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(); + state = state.copyWith(checkUpdate: check); } void setAudioQuality(AudioQuality quality) { - audioQuality = quality; - notifyListeners(); - updatePersistence(); + state = state.copyWith(audioQuality: quality); } void setDownloadLocation(String downloadDir) { if (downloadDir.isEmpty) return; - downloadLocation = downloadDir; - notifyListeners(); - updatePersistence(); + state = state.copyWith(downloadLocation: downloadDir); } void setLayoutMode(LayoutMode mode) { - layoutMode = mode; - notifyListeners(); - updatePersistence(); + state = state.copyWith(layoutMode: mode); } void setCloseBehavior(CloseBehavior behavior) { - closeBehavior = behavior; - notifyListeners(); - updatePersistence(); + state = state.copyWith(closeBehavior: behavior); } void setShowSystemTrayIcon(bool show) { - showSystemTrayIcon = show; - notifyListeners(); - updatePersistence(); + state = state.copyWith(showSystemTrayIcon: show); } void setLocale(Locale locale) { - this.locale = locale; - notifyListeners(); - updatePersistence(); + state = state.copyWith(locale: locale); } void setPipedInstance(String instance) { - pipedInstance = instance; - notifyListeners(); - updatePersistence(); + state = state.copyWith(pipedInstance: instance); } void setSearchMode(SearchMode mode) { - searchMode = mode; - notifyListeners(); - updatePersistence(); + state = state.copyWith(searchMode: mode); } void setSkipNonMusic(bool skip) { - skipNonMusic = skip; - notifyListeners(); - updatePersistence(); + state = state.copyWith(skipNonMusic: skip); } void setYoutubeApiType(YoutubeApiType type) { - youtubeApiType = type; - notifyListeners(); - updatePersistence(); + state = state.copyWith(youtubeApiType: type); } void setSystemTitleBar(bool isSystemTitleBar) { - systemTitleBar = isSystemTitleBar; + state = state.copyWith(systemTitleBar: isSystemTitleBar); if (DesktopTools.platform.isDesktop) { DesktopTools.window.setTitleBarStyle( - systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, + isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, ); } - notifyListeners(); - updatePersistence(); } void setAmoledDarkTheme(bool isAmoled) { - amoledDarkTheme = isAmoled; - notifyListeners(); - updatePersistence(); + state = state.copyWith(amoledDarkTheme: isAmoled); } void setNormalizeAudio(bool normalize) { - normalizeAudio = normalize; + state = state.copyWith(normalizeAudio: normalize); audioPlayer.setAudioNormalization(normalize); - notifyListeners(); - updatePersistence(); - } - - Future _getDefaultDownloadDirectory() async { - if (kIsAndroid) return "/storage/emulated/0/Download/Spotube"; - - if (kIsMacOS) { - return path.join((await getLibraryDirectory()).path, "Caches"); - } - - return getDownloadsDirectory().then((dir) { - return path.join(dir!.path, "Spotube"); - }); } @override - FutureOr loadFromLocal(Map map) async { - recommendationMarket = Market.values.firstWhere( - (market) => - market.name == (map["recommendationMarket"] ?? recommendationMarket), - orElse: () => Market.US, - ); - checkUpdate = map["checkUpdate"] ?? checkUpdate; - - 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(); - } - - 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; - 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, - ); + FutureOr fromJson(Map json) { + return UserPreferences.fromJson(json); } @override - FutureOr> toMap() { - return { - "recommendationMarket": recommendationMarket.name, - "themeMode": themeMode.index, - "accentColorScheme": accentColorScheme.toString(), - "albumColorSync": albumColorSync, - "checkUpdate": checkUpdate, - "audioQuality": audioQuality.index, - "downloadLocation": downloadLocation, - "layoutMode": layoutMode.name, - "closeBehavior": closeBehavior.index, - "showSystemTrayIcon": showSystemTrayIcon, - "locale": - jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}), - "pipedInstance": pipedInstance, - "searchMode": searchMode.name, - "skipNonMusic": skipNonMusic, - "youtubeApiType": youtubeApiType.name, - 'systemTitleBar': systemTitleBar, - "amoledDarkTheme": amoledDarkTheme, - "normalizeAudio": normalizeAudio, - "streamMusicCodec": streamMusicCodec.name, - "downloadMusicCodec": downloadMusicCodec.name, - }; - } - - UserPreferences copyWith({ - ThemeMode? themeMode, - SpotubeColor? accentColorScheme, - bool? albumColorSync, - bool? checkUpdate, - AudioQuality? audioQuality, - String? downloadLocation, - LayoutMode? layoutMode, - CloseBehavior? closeBehavior, - bool? showSystemTrayIcon, - Locale? locale, - String? pipedInstance, - SearchMode? searchMode, - bool? skipNonMusic, - YoutubeApiType? youtubeApiType, - Market? recommendationMarket, - bool? saveTrackLyrics, - }) { - return UserPreferences( - ref, - themeMode: themeMode ?? this.themeMode, - accentColorScheme: accentColorScheme ?? this.accentColorScheme, - albumColorSync: albumColorSync ?? this.albumColorSync, - checkUpdate: checkUpdate ?? this.checkUpdate, - audioQuality: audioQuality ?? this.audioQuality, - downloadLocation: downloadLocation ?? this.downloadLocation, - layoutMode: layoutMode ?? this.layoutMode, - closeBehavior: closeBehavior ?? this.closeBehavior, - showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, - locale: locale ?? this.locale, - pipedInstance: pipedInstance ?? this.pipedInstance, - searchMode: searchMode ?? this.searchMode, - skipNonMusic: skipNonMusic ?? this.skipNonMusic, - youtubeApiType: youtubeApiType ?? this.youtubeApiType, - recommendationMarket: recommendationMarket ?? this.recommendationMarket, - ); + Map toJson() { + return state.toJson(); } } -final userPreferencesProvider = ChangeNotifierProvider( - (ref) => UserPreferences(ref), +final userPreferencesProvider = + StateNotifierProvider( + (ref) => UserPreferencesNotifier(ref), );