fix: changed settings are not persisting after force stop #821

This commit is contained in:
Kingkor Roy Tirtho 2023-11-13 20:59:54 +06:00
parent 694ddf07a3
commit e29a38dfa4
9 changed files with 287 additions and 305 deletions

View File

@ -49,6 +49,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final scheme = preferences.accentColorScheme; final scheme = preferences.accentColorScheme;
final active = useState<String>(colorsMap.firstWhere( final active = useState<String>(colorsMap.firstWhere(
(element) { (element) {
@ -57,7 +58,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
).name); ).name);
onOk() { onOk() {
preferences.setAccentColorScheme( preferencesNotifier.setAccentColorScheme(
colorsMap.firstWhere( colorsMap.firstWhere(
(element) { (element) {
return element.name == active.value; return element.name == active.value;

View File

@ -16,6 +16,7 @@ class SettingsAboutSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
return SectionCardWithHeading( return SectionCardWithHeading(
heading: context.l10n.about, heading: context.l10n.about,
@ -68,7 +69,7 @@ class SettingsAboutSection extends HookConsumerWidget {
secondary: const Icon(SpotubeIcons.update), secondary: const Icon(SpotubeIcons.update),
title: Text(context.l10n.check_for_updates), title: Text(context.l10n.check_for_updates),
value: preferences.checkUpdate, value: preferences.checkUpdate,
onChanged: (checked) => preferences.setCheckUpdate(checked), onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked),
), ),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.info), leading: const Icon(SpotubeIcons.info),

View File

@ -15,6 +15,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final pickColorScheme = useCallback(() { final pickColorScheme = useCallback(() {
return () => showDialog( return () => showDialog(
context: context, context: context,
@ -33,7 +34,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
value: preferences.layoutMode, value: preferences.layoutMode,
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
preferences.setLayoutMode(value); preferencesNotifier.setLayoutMode(value);
} }
}, },
options: [ options: [
@ -71,7 +72,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
], ],
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
preferences.setThemeMode(value); preferencesNotifier.setThemeMode(value);
} }
}, },
), ),
@ -80,7 +81,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
title: Text(context.l10n.use_amoled_mode), title: Text(context.l10n.use_amoled_mode),
subtitle: Text(context.l10n.pitch_dark_theme), subtitle: Text(context.l10n.pitch_dark_theme),
value: preferences.amoledDarkTheme, value: preferences.amoledDarkTheme,
onChanged: preferences.setAmoledDarkTheme, onChanged: preferencesNotifier.setAmoledDarkTheme,
), ),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.palette), leading: const Icon(SpotubeIcons.palette),
@ -101,7 +102,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
title: Text(context.l10n.sync_album_color), title: Text(context.l10n.sync_album_color),
subtitle: Text(context.l10n.sync_album_color_description), subtitle: Text(context.l10n.sync_album_color_description),
value: preferences.albumColorSync, value: preferences.albumColorSync,
onChanged: preferences.setAlbumColorSync, onChanged: preferencesNotifier.setAlbumColorSync,
), ),
], ],
); );

View File

@ -12,6 +12,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
return SectionCardWithHeading( return SectionCardWithHeading(
heading: context.l10n.desktop, heading: context.l10n.desktop,
@ -32,7 +33,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
], ],
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
preferences.setCloseBehavior(value); preferencesNotifier.setCloseBehavior(value);
} }
}, },
), ),
@ -40,13 +41,13 @@ class SettingsDesktopSection extends HookConsumerWidget {
secondary: const Icon(SpotubeIcons.tray), secondary: const Icon(SpotubeIcons.tray),
title: Text(context.l10n.show_tray_icon), title: Text(context.l10n.show_tray_icon),
value: preferences.showSystemTrayIcon, value: preferences.showSystemTrayIcon,
onChanged: preferences.setShowSystemTrayIcon, onChanged: preferencesNotifier.setShowSystemTrayIcon,
), ),
SwitchListTile( SwitchListTile(
secondary: const Icon(SpotubeIcons.window), secondary: const Icon(SpotubeIcons.window),
title: Text(context.l10n.use_system_title_bar), title: Text(context.l10n.use_system_title_bar),
value: preferences.systemTitleBar, value: preferences.systemTitleBar,
onChanged: preferences.setSystemTitleBar, onChanged: preferencesNotifier.setSystemTitleBar,
), ),
], ],
); );

View File

@ -14,6 +14,7 @@ class SettingsDownloadsSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final pickDownloadLocation = useCallback(() async { final pickDownloadLocation = useCallback(() async {
@ -22,13 +23,13 @@ class SettingsDownloadsSection extends HookConsumerWidget {
initialDirectory: preferences.downloadLocation, initialDirectory: preferences.downloadLocation,
); );
if (dirStr == null) return; if (dirStr == null) return;
preferences.setDownloadLocation(dirStr); preferencesNotifier.setDownloadLocation(dirStr);
} else { } else {
String? dirStr = await getDirectoryPath( String? dirStr = await getDirectoryPath(
initialDirectory: preferences.downloadLocation, initialDirectory: preferences.downloadLocation,
); );
if (dirStr == null) return; if (dirStr == null) return;
preferences.setDownloadLocation(dirStr); preferencesNotifier.setDownloadLocation(dirStr);
} }
}, [preferences.downloadLocation]); }, [preferences.downloadLocation]);

View File

@ -17,6 +17,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
return SectionCardWithHeading( return SectionCardWithHeading(
@ -26,7 +27,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
value: preferences.locale, value: preferences.locale,
onChanged: (locale) { onChanged: (locale) {
if (locale == null) return; if (locale == null) return;
preferences.setLocale(locale); preferencesNotifier.setLocale(locale);
}, },
title: Text(context.l10n.language), title: Text(context.l10n.language),
secondary: const Icon(SpotubeIcons.language), secondary: const Icon(SpotubeIcons.language),
@ -57,7 +58,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
value: preferences.recommendationMarket, value: preferences.recommendationMarket,
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
preferences.setRecommendationMarket(value); preferencesNotifier.setRecommendationMarket(value);
}, },
options: spotifyMarkets options: spotifyMarkets
.map( .map(

View File

@ -18,6 +18,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final theme = Theme.of(context); final theme = Theme.of(context);
return SectionCardWithHeading( return SectionCardWithHeading(
@ -39,7 +40,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
], ],
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
preferences.setAudioQuality(value); preferencesNotifier.setAudioQuality(value);
} }
}, },
), ),
@ -55,7 +56,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
preferences.setYoutubeApiType(value); preferencesNotifier.setYoutubeApiType(value);
}, },
), ),
AnimatedSwitcher( AnimatedSwitcher(
@ -113,7 +114,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
preferences.setPipedInstance(value); preferencesNotifier.setPipedInstance(value);
} }
}, },
); );
@ -141,7 +142,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
if (value == null) return; 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), title: Text(context.l10n.skip_non_music),
value: preferences.skipNonMusic, value: preferences.skipNonMusic,
onChanged: (state) { onChanged: (state) {
preferences.setSkipNonMusic(state); preferencesNotifier.setSkipNonMusic(state);
}, },
), ),
), ),
@ -172,7 +173,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
secondary: const Icon(SpotubeIcons.normalize), secondary: const Icon(SpotubeIcons.normalize),
title: Text(context.l10n.normalize_audio), title: Text(context.l10n.normalize_audio),
value: preferences.normalizeAudio, value: preferences.normalizeAudio,
onChanged: preferences.setNormalizeAudio, onChanged: preferencesNotifier.setNormalizeAudio,
), ),
AdaptiveSelectTile<MusicCodec>( AdaptiveSelectTile<MusicCodec>(
secondary: const Icon(SpotubeIcons.stream), secondary: const Icon(SpotubeIcons.stream),
@ -190,7 +191,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
preferences.setStreamMusicCodec(value); preferencesNotifier.setStreamMusicCodec(value);
}, },
), ),
AdaptiveSelectTile<MusicCodec>( AdaptiveSelectTile<MusicCodec>(
@ -209,7 +210,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
if (value == null) return; if (value == null) return;
preferences.setDownloadMusicCodec(value); preferencesNotifier.setDownloadMusicCodec(value);
}, },
), ),
], ],

View File

@ -20,7 +20,7 @@ class SettingsPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, ref) { Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider); final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
return SafeArea( return SafeArea(
bottom: false, bottom: false,
@ -49,7 +49,7 @@ class SettingsPage extends HookConsumerWidget {
const SettingsAboutSection(), const SettingsAboutSection(),
Center( Center(
child: FilledButton( child: FilledButton(
onPressed: preferences.reset, onPressed: preferencesNotifier.reset,
child: Text(context.l10n.restore_defaults), child: Text(context.l10n.restore_defaults),
), ),
), ),

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/audio_player/audio_player.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:spotube/utils/platform.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -48,220 +47,103 @@ enum MusicCodec {
const MusicCodec._(this.label); const MusicCodec._(this.label);
} }
class UserPreferences extends PersistedChangeNotifier { class UserPreferences {
AudioQuality audioQuality; final AudioQuality audioQuality;
bool albumColorSync; final bool albumColorSync;
bool amoledDarkTheme; final bool amoledDarkTheme;
bool checkUpdate; final bool checkUpdate;
bool normalizeAudio; final bool normalizeAudio;
bool showSystemTrayIcon; final bool showSystemTrayIcon;
bool skipNonMusic; final bool skipNonMusic;
bool systemTitleBar; final bool systemTitleBar;
CloseBehavior closeBehavior; final CloseBehavior closeBehavior;
late SpotubeColor accentColorScheme; final SpotubeColor accentColorScheme;
LayoutMode layoutMode; final LayoutMode layoutMode;
Locale locale; final Locale locale;
Market recommendationMarket; final Market recommendationMarket;
SearchMode searchMode; final SearchMode searchMode;
String downloadLocation; String downloadLocation;
String pipedInstance; final String pipedInstance;
ThemeMode themeMode; final ThemeMode themeMode;
YoutubeApiType youtubeApiType; final YoutubeApiType youtubeApiType;
MusicCodec streamMusicCodec; final MusicCodec streamMusicCodec;
MusicCodec downloadMusicCodec; final MusicCodec downloadMusicCodec;
final Ref ref; UserPreferences({
required AudioQuality? audioQuality,
UserPreferences( required bool? albumColorSync,
this.ref, { required bool? amoledDarkTheme,
this.recommendationMarket = Market.US, required bool? checkUpdate,
this.themeMode = ThemeMode.system, required bool? normalizeAudio,
this.layoutMode = LayoutMode.adaptive, required bool? showSystemTrayIcon,
this.albumColorSync = true, required bool? skipNonMusic,
this.checkUpdate = true, required bool? systemTitleBar,
this.audioQuality = AudioQuality.high, required CloseBehavior? closeBehavior,
this.downloadLocation = "", required SpotubeColor? accentColorScheme,
this.closeBehavior = CloseBehavior.close, required LayoutMode? layoutMode,
this.showSystemTrayIcon = true, required Locale? locale,
this.locale = const Locale("system", "system"), required Market? recommendationMarket,
this.pipedInstance = "https://pipedapi.kavin.rocks", required SearchMode? searchMode,
this.searchMode = SearchMode.youtube, required String? downloadLocation,
this.skipNonMusic = true, required String? pipedInstance,
this.youtubeApiType = YoutubeApiType.youtube, required ThemeMode? themeMode,
this.systemTitleBar = false, required YoutubeApiType? youtubeApiType,
this.amoledDarkTheme = false, required MusicCodec? streamMusicCodec,
this.normalizeAudio = true, required MusicCodec? downloadMusicCodec,
this.streamMusicCodec = MusicCodec.weba, }) : accentColorScheme =
this.downloadMusicCodec = MusicCodec.m4a, accentColorScheme ?? const SpotubeColor(0xFF2196F3, name: "Blue"),
SpotubeColor? accentColorScheme, albumColorSync = albumColorSync ?? true,
}) : super() { amoledDarkTheme = amoledDarkTheme ?? false,
this.accentColorScheme = audioQuality = audioQuality ?? AudioQuality.high,
accentColorScheme ?? SpotubeColor(Colors.blue.value, name: "Blue"); checkUpdate = checkUpdate ?? true,
if (downloadLocation.isEmpty && !kIsWeb) { 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( _getDefaultDownloadDirectory().then(
(value) { (value) => this.downloadLocation = value,
downloadLocation = value;
},
); );
} }
} }
void reset() { factory UserPreferences.withDefaults() {
setRecommendationMarket(Market.US); return UserPreferences(
setThemeMode(ThemeMode.system); audioQuality: null,
setLayoutMode(LayoutMode.adaptive); albumColorSync: null,
setAlbumColorSync(true); amoledDarkTheme: null,
setCheckUpdate(true); checkUpdate: null,
setAudioQuality(AudioQuality.high); normalizeAudio: null,
setDownloadLocation(""); showSystemTrayIcon: null,
setCloseBehavior(CloseBehavior.close); skipNonMusic: null,
setShowSystemTrayIcon(true); systemTitleBar: null,
setLocale(const Locale("system", "system")); closeBehavior: null,
setPipedInstance("https://pipedapi.kavin.rocks"); accentColorScheme: null,
setSearchMode(SearchMode.youtube); layoutMode: null,
setSkipNonMusic(true); locale: null,
setYoutubeApiType(YoutubeApiType.youtube); recommendationMarket: null,
setSystemTitleBar(false); searchMode: null,
setAmoledDarkTheme(false); downloadLocation: null,
setNormalizeAudio(true); pipedInstance: null,
setAccentColorScheme(SpotubeColor(Colors.blue.value, name: "Blue")); themeMode: null,
setStreamMusicCodec(MusicCodec.weba); youtubeApiType: null,
setDownloadMusicCodec(MusicCodec.m4a); streamMusicCodec: null,
downloadMusicCodec: null,
);
} }
void setStreamMusicCodec(MusicCodec codec) { static Future<String> _getDefaultDownloadDirectory() async {
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<String> _getDefaultDownloadDirectory() async {
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube"; if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
if (kIsMacOS) { if (kIsMacOS) {
@ -273,102 +155,71 @@ class UserPreferences extends PersistedChangeNotifier {
}); });
} }
@override static Future<UserPreferences> fromJson(Map<String, dynamic> json) async {
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async { final localeMap =
recommendationMarket = Market.values.firstWhere( json["locale"] != null ? jsonDecode(json["locale"]) : null;
(market) =>
market.name == (map["recommendationMarket"] ?? recommendationMarket),
orElse: () => Market.US,
);
checkUpdate = map["checkUpdate"] ?? checkUpdate;
themeMode = ThemeMode.values[map["themeMode"] ?? 0]; final systemTitleBar = json["systemTitleBar"] ?? false;
accentColorScheme = map["accentColorScheme"] != null if (DesktopTools.platform.isDesktop) {
? SpotubeColor.fromString(map["accentColorScheme"]) await DesktopTools.window.setTitleBarStyle(
: accentColorScheme; systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
albumColorSync = map["albumColorSync"] ?? albumColorSync; );
audioQuality = map["audioQuality"] != null
? AudioQuality.values[map["audioQuality"]]
: audioQuality;
if (!kIsWeb) {
downloadLocation =
map["downloadLocation"] ?? await _getDefaultDownloadDirectory();
} }
layoutMode = LayoutMode.values.firstWhere( final normalizeAudio = json["normalizeAudio"] ?? true;
(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); audioPlayer.setAudioNormalization(normalizeAudio);
streamMusicCodec = MusicCodec.values.firstWhere( return UserPreferences(
(codec) => codec.name == map["streamMusicCodec"], accentColorScheme: json["accentColorScheme"] == null
orElse: () => MusicCodec.weba, ? null
); : SpotubeColor.fromString(json["accentColorScheme"]),
albumColorSync: json["albumColorSync"],
downloadMusicCodec = MusicCodec.values.firstWhere( amoledDarkTheme: json["amoledDarkTheme"],
(codec) => codec.name == map["downloadMusicCodec"], audioQuality: AudioQuality.values[json["audioQuality"]],
orElse: () => MusicCodec.m4a, 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 Map<String, dynamic> toJson() {
FutureOr<Map<String, dynamic>> toMap() {
return { return {
"recommendationMarket": recommendationMarket.name, "recommendationMarket": recommendationMarket.index,
"themeMode": themeMode.index, "themeMode": themeMode.index,
"accentColorScheme": accentColorScheme.toString(), "accentColorScheme": accentColorScheme.toString(),
"albumColorSync": albumColorSync, "albumColorSync": albumColorSync,
"checkUpdate": checkUpdate, "checkUpdate": checkUpdate,
"audioQuality": audioQuality.index, "audioQuality": audioQuality.index,
"downloadLocation": downloadLocation, "downloadLocation": downloadLocation,
"layoutMode": layoutMode.name, "layoutMode": layoutMode.index,
"closeBehavior": closeBehavior.index, "closeBehavior": closeBehavior.index,
"showSystemTrayIcon": showSystemTrayIcon, "showSystemTrayIcon": showSystemTrayIcon,
"locale": "locale":
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}), jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
"pipedInstance": pipedInstance, "pipedInstance": pipedInstance,
"searchMode": searchMode.name, "searchMode": searchMode.index,
"skipNonMusic": skipNonMusic, "skipNonMusic": skipNonMusic,
"youtubeApiType": youtubeApiType.name, "youtubeApiType": youtubeApiType.index,
'systemTitleBar': systemTitleBar, 'systemTitleBar': systemTitleBar,
"amoledDarkTheme": amoledDarkTheme, "amoledDarkTheme": amoledDarkTheme,
"normalizeAudio": normalizeAudio, "normalizeAudio": normalizeAudio,
"streamMusicCodec": streamMusicCodec.name, "streamMusicCodec": streamMusicCodec.index,
"downloadMusicCodec": downloadMusicCodec.name, "downloadMusicCodec": downloadMusicCodec.index,
}; };
} }
@ -389,9 +240,13 @@ class UserPreferences extends PersistedChangeNotifier {
YoutubeApiType? youtubeApiType, YoutubeApiType? youtubeApiType,
Market? recommendationMarket, Market? recommendationMarket,
bool? saveTrackLyrics, bool? saveTrackLyrics,
bool? amoledDarkTheme,
bool? normalizeAudio,
MusicCodec? downloadMusicCodec,
MusicCodec? streamMusicCodec,
bool? systemTitleBar,
}) { }) {
return UserPreferences( return UserPreferences(
ref,
themeMode: themeMode ?? this.themeMode, themeMode: themeMode ?? this.themeMode,
accentColorScheme: accentColorScheme ?? this.accentColorScheme, accentColorScheme: accentColorScheme ?? this.accentColorScheme,
albumColorSync: albumColorSync ?? this.albumColorSync, albumColorSync: albumColorSync ?? this.albumColorSync,
@ -407,10 +262,130 @@ class UserPreferences extends PersistedChangeNotifier {
skipNonMusic: skipNonMusic ?? this.skipNonMusic, skipNonMusic: skipNonMusic ?? this.skipNonMusic,
youtubeApiType: youtubeApiType ?? this.youtubeApiType, youtubeApiType: youtubeApiType ?? this.youtubeApiType,
recommendationMarket: recommendationMarket ?? this.recommendationMarket, 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( class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
(ref) => UserPreferences(ref), 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<UserPreferences> fromJson(Map<String, dynamic> json) {
return UserPreferences.fromJson(json);
}
@override
Map<String, dynamic> toJson() {
return state.toJson();
}
}
final userPreferencesProvider =
StateNotifierProvider<UserPreferencesNotifier, UserPreferences>(
(ref) => UserPreferencesNotifier(ref),
); );