mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: use json serializer for preferences
This commit is contained in:
parent
dc980b024e
commit
57c03ad045
@ -22,7 +22,7 @@ import 'package:spotube/components/shared/track_table/track_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FfiException;
|
||||
|
@ -15,7 +15,8 @@ import 'package:spotube/hooks/utils/use_debounce.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/provider/youtube_provider.dart';
|
||||
import 'package:spotube/services/youtube/youtube.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
@ -19,7 +19,8 @@ import 'package:spotube/models/logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
|
@ -16,7 +16,8 @@ import 'package:spotube/hooks/controllers/use_sidebarx_controller.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
@ -11,7 +11,8 @@ import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
|
||||
final navigationPanelHeight = StateProvider<double>((ref) => 50);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
|
||||
class SpotubeColor extends Color {
|
||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class PipedDownDialog extends HookConsumerWidget {
|
||||
const PipedDownDialog({Key? key}) : super(key: key);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:titlebar_buttons/titlebar_buttons.dart';
|
||||
import 'dart:math';
|
||||
|
@ -22,7 +22,8 @@ import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/download_manager_provider.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
|
||||
final trackCollectionSortState =
|
||||
|
@ -7,7 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/intents.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
void useInitSysTray(WidgetRef ref) {
|
||||
final context = useContext();
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/collections/env.dart';
|
||||
|
||||
import 'package:spotube/components/shared/links/anchor_button.dart';
|
||||
import 'package:spotube/hooks/controllers/use_package_info.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:version/version.dart';
|
||||
|
||||
|
@ -21,7 +21,7 @@ import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/models/skip_segment.dart';
|
||||
import 'package:spotube/provider/palette_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/cli/cli.dart';
|
||||
import 'package:spotube/services/connectivity_adapter.dart';
|
||||
|
@ -3,7 +3,7 @@ import 'dart:async';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/extensions/track.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/services/youtube/youtube.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
@ -10,7 +10,7 @@ import 'package:spotube/components/shared/expandable_search/expandable_search.da
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||
import 'package:spotube/components/shared/waypoint.dart';
|
||||
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
|
||||
|
@ -17,7 +17,7 @@ import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/queries/queries.dart';
|
||||
import 'package:spotube/utils/type_conversion_utils.dart';
|
||||
|
||||
|
@ -7,7 +7,7 @@ import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SettingsAboutSection extends HookConsumerWidget {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -7,7 +6,8 @@ import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
|
||||
class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
const SettingsAppearanceSection({Key? key}) : super(key: key);
|
||||
|
@ -4,7 +4,8 @@ import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
|
||||
class SettingsDesktopSection extends HookConsumerWidget {
|
||||
const SettingsDesktopSection({Key? key}) : super(key: key);
|
||||
|
@ -7,7 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class SettingsDownloadsSection extends HookConsumerWidget {
|
||||
const SettingsDownloadsSection({Key? key}) : super(key: key);
|
||||
|
@ -9,7 +9,7 @@ import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/l10n/l10n.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
const SettingsLanguageRegionSection({Key? key}) : super(key: key);
|
||||
|
@ -10,7 +10,8 @@ import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/provider/piped_instances_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
|
||||
class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
const SettingsPlaybackSection({Key? key}) : super(key: key);
|
||||
|
@ -13,7 +13,7 @@ import 'package:spotube/pages/settings/sections/developers.dart';
|
||||
import 'package:spotube/pages/settings/sections/downloads.dart';
|
||||
import 'package:spotube/pages/settings/sections/language_region.dart';
|
||||
import 'package:spotube/pages/settings/sections/playback.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class SettingsPage extends HookConsumerWidget {
|
||||
const SettingsPage({Key? key}) : super(key: key);
|
||||
|
@ -10,7 +10,8 @@ import 'package:metadata_god/metadata_god.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/provider/youtube_provider.dart';
|
||||
import 'package:spotube/services/download_manager/download_manager.dart';
|
||||
import 'package:spotube/services/youtube/youtube.dart';
|
||||
|
@ -6,7 +6,7 @@ import 'package:spotube/models/logger.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/services/supabase.dart';
|
||||
import 'package:spotube/services/youtube/youtube.dart';
|
||||
|
||||
|
@ -20,7 +20,8 @@ import 'package:spotube/provider/palette_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/next_fetcher_mixin.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/provider/youtube_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/audio_services/audio_services.dart';
|
||||
|
165
lib/provider/user_preferences/user_preferences_provider.dart
Normal file
165
lib/provider/user_preferences/user_preferences_provider.dart
Normal file
@ -0,0 +1,165 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/provider/palette_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
|
||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
||||
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);
|
||||
}
|
||||
|
||||
Future<String> _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<void> onInit() async {
|
||||
if (state.downloadLocation.isEmpty) {
|
||||
state = state.copyWith(
|
||||
downloadLocation: await _getDefaultDownloadDirectory(),
|
||||
);
|
||||
}
|
||||
|
||||
if (DesktopTools.platform.isDesktop) {
|
||||
await DesktopTools.window.setTitleBarStyle(
|
||||
state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||
);
|
||||
}
|
||||
|
||||
await audioPlayer.setAudioNormalization(state.normalizeAudio);
|
||||
}
|
||||
|
||||
@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),
|
||||
);
|
273
lib/provider/user_preferences/user_preferences_state.dart
Normal file
273
lib/provider/user_preferences/user_preferences_state.dart
Normal file
@ -0,0 +1,273 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
|
||||
part 'user_preferences_state.g.dart';
|
||||
|
||||
@JsonEnum()
|
||||
enum LayoutMode {
|
||||
compact,
|
||||
extended,
|
||||
adaptive,
|
||||
}
|
||||
|
||||
@JsonEnum()
|
||||
enum AudioQuality {
|
||||
high,
|
||||
low,
|
||||
}
|
||||
|
||||
@JsonEnum()
|
||||
enum CloseBehavior {
|
||||
minimizeToTray,
|
||||
close,
|
||||
}
|
||||
|
||||
@JsonEnum()
|
||||
enum YoutubeApiType {
|
||||
youtube,
|
||||
piped;
|
||||
|
||||
String get label => name[0].toUpperCase() + name.substring(1);
|
||||
}
|
||||
|
||||
@JsonEnum()
|
||||
enum MusicCodec {
|
||||
m4a._("M4a (Best for downloaded music)"),
|
||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||
|
||||
final String label;
|
||||
const MusicCodec._(this.label);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
final class UserPreferences {
|
||||
@JsonKey(
|
||||
defaultValue: AudioQuality.high,
|
||||
unknownEnumValue: AudioQuality.high,
|
||||
)
|
||||
final AudioQuality audioQuality;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
final bool albumColorSync;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool amoledDarkTheme;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
final bool checkUpdate;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool normalizeAudio;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
final bool showSystemTrayIcon;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
final bool skipNonMusic;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool systemTitleBar;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: CloseBehavior.minimizeToTray,
|
||||
unknownEnumValue: CloseBehavior.minimizeToTray,
|
||||
)
|
||||
final CloseBehavior closeBehavior;
|
||||
|
||||
static SpotubeColor _accentColorSchemeFromJson(Map<String, dynamic> json) {
|
||||
return SpotubeColor.fromString(json["color"]);
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _accentColorSchemeReadValue(
|
||||
Map<dynamic, dynamic> json, String key) {
|
||||
if (json[key] is String) {
|
||||
return {"color": json[key]};
|
||||
}
|
||||
|
||||
return json[key] as Map<String, dynamic>?;
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _accentColorSchemeToJson(SpotubeColor color) {
|
||||
return {"color": color.toString()};
|
||||
}
|
||||
|
||||
static SpotubeColor _defaultAccentColorScheme() =>
|
||||
const SpotubeColor(0xFF2196F3, name: "Blue");
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: UserPreferences._defaultAccentColorScheme,
|
||||
fromJson: UserPreferences._accentColorSchemeFromJson,
|
||||
toJson: UserPreferences._accentColorSchemeToJson,
|
||||
readValue: UserPreferences._accentColorSchemeReadValue,
|
||||
)
|
||||
final SpotubeColor accentColorScheme;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: LayoutMode.adaptive,
|
||||
unknownEnumValue: LayoutMode.adaptive,
|
||||
)
|
||||
final LayoutMode layoutMode;
|
||||
|
||||
static Locale _localeFromJson(Map<String, dynamic> json) {
|
||||
return Locale(json["languageCode"], json["countryCode"]);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> _localeToJson(Locale locale) {
|
||||
return {
|
||||
"languageCode": locale.languageCode,
|
||||
"countryCode": locale.countryCode,
|
||||
};
|
||||
}
|
||||
|
||||
static Map<String, dynamic>? _localeReadValue(
|
||||
Map<dynamic, dynamic> json, String key) {
|
||||
if (json[key] is String) {
|
||||
final map = jsonDecode(json[key]);
|
||||
return {
|
||||
"languageCode": map["lc"],
|
||||
"countryCode": map["cc"],
|
||||
};
|
||||
}
|
||||
|
||||
return json[key] as Map<String, dynamic>?;
|
||||
}
|
||||
|
||||
static Locale _defaultLocaleValue() => const Locale("system", "system");
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: UserPreferences._defaultLocaleValue,
|
||||
toJson: UserPreferences._localeToJson,
|
||||
fromJson: UserPreferences._localeFromJson,
|
||||
readValue: UserPreferences._localeReadValue,
|
||||
)
|
||||
final Locale locale;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: Market.US,
|
||||
unknownEnumValue: Market.US,
|
||||
)
|
||||
final Market recommendationMarket;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: SearchMode.youtube,
|
||||
unknownEnumValue: SearchMode.youtube,
|
||||
)
|
||||
final SearchMode searchMode;
|
||||
|
||||
@JsonKey(defaultValue: "")
|
||||
final String downloadLocation;
|
||||
|
||||
@JsonKey(defaultValue: "https://pipedapi.kavin.rocks")
|
||||
final String pipedInstance;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: ThemeMode.system,
|
||||
unknownEnumValue: ThemeMode.system,
|
||||
)
|
||||
final ThemeMode themeMode;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: YoutubeApiType.youtube,
|
||||
unknownEnumValue: YoutubeApiType.youtube,
|
||||
)
|
||||
final YoutubeApiType youtubeApiType;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: MusicCodec.weba,
|
||||
unknownEnumValue: MusicCodec.weba,
|
||||
)
|
||||
final MusicCodec streamMusicCodec;
|
||||
|
||||
@JsonKey(
|
||||
defaultValue: MusicCodec.m4a,
|
||||
unknownEnumValue: MusicCodec.m4a,
|
||||
)
|
||||
final MusicCodec downloadMusicCodec;
|
||||
|
||||
UserPreferences({
|
||||
required this.audioQuality,
|
||||
required this.albumColorSync,
|
||||
required this.amoledDarkTheme,
|
||||
required this.checkUpdate,
|
||||
required this.normalizeAudio,
|
||||
required this.showSystemTrayIcon,
|
||||
required this.skipNonMusic,
|
||||
required this.systemTitleBar,
|
||||
required this.closeBehavior,
|
||||
required this.accentColorScheme,
|
||||
required this.layoutMode,
|
||||
required this.locale,
|
||||
required this.recommendationMarket,
|
||||
required this.searchMode,
|
||||
required this.downloadLocation,
|
||||
required this.pipedInstance,
|
||||
required this.themeMode,
|
||||
required this.youtubeApiType,
|
||||
required this.streamMusicCodec,
|
||||
required this.downloadMusicCodec,
|
||||
});
|
||||
|
||||
factory UserPreferences.withDefaults() {
|
||||
return UserPreferences.fromJson({});
|
||||
}
|
||||
|
||||
factory UserPreferences.fromJson(Map<String, dynamic> json) {
|
||||
return _$UserPreferencesFromJson(json);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$UserPreferencesToJson(this);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
381
lib/provider/user_preferences/user_preferences_state.g.dart
Normal file
381
lib/provider/user_preferences/user_preferences_state.g.dart
Normal file
@ -0,0 +1,381 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_preferences_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
UserPreferences _$UserPreferencesFromJson(Map<String, dynamic> json) =>
|
||||
UserPreferences(
|
||||
audioQuality: $enumDecodeNullable(
|
||||
_$AudioQualityEnumMap, json['audioQuality'],
|
||||
unknownValue: AudioQuality.high) ??
|
||||
AudioQuality.high,
|
||||
albumColorSync: json['albumColorSync'] as bool? ?? true,
|
||||
amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false,
|
||||
checkUpdate: json['checkUpdate'] as bool? ?? true,
|
||||
normalizeAudio: json['normalizeAudio'] as bool? ?? false,
|
||||
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? true,
|
||||
skipNonMusic: json['skipNonMusic'] as bool? ?? true,
|
||||
systemTitleBar: json['systemTitleBar'] as bool? ?? false,
|
||||
closeBehavior: $enumDecodeNullable(
|
||||
_$CloseBehaviorEnumMap, json['closeBehavior'],
|
||||
unknownValue: CloseBehavior.minimizeToTray) ??
|
||||
CloseBehavior.minimizeToTray,
|
||||
accentColorScheme: UserPreferences._accentColorSchemeReadValue(
|
||||
json, 'accentColorScheme') ==
|
||||
null
|
||||
? UserPreferences._defaultAccentColorScheme()
|
||||
: UserPreferences._accentColorSchemeFromJson(
|
||||
UserPreferences._accentColorSchemeReadValue(
|
||||
json, 'accentColorScheme') as Map<String, dynamic>),
|
||||
layoutMode: $enumDecodeNullable(_$LayoutModeEnumMap, json['layoutMode'],
|
||||
unknownValue: LayoutMode.adaptive) ??
|
||||
LayoutMode.adaptive,
|
||||
locale: UserPreferences._localeReadValue(json, 'locale') == null
|
||||
? UserPreferences._defaultLocaleValue()
|
||||
: UserPreferences._localeFromJson(
|
||||
UserPreferences._localeReadValue(json, 'locale')
|
||||
as Map<String, dynamic>),
|
||||
recommendationMarket: $enumDecodeNullable(
|
||||
_$MarketEnumMap, json['recommendationMarket'],
|
||||
unknownValue: Market.US) ??
|
||||
Market.US,
|
||||
searchMode: $enumDecodeNullable(_$SearchModeEnumMap, json['searchMode'],
|
||||
unknownValue: SearchMode.youtube) ??
|
||||
SearchMode.youtube,
|
||||
downloadLocation: json['downloadLocation'] as String? ?? '',
|
||||
pipedInstance:
|
||||
json['pipedInstance'] as String? ?? 'https://pipedapi.kavin.rocks',
|
||||
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode'],
|
||||
unknownValue: ThemeMode.system) ??
|
||||
ThemeMode.system,
|
||||
youtubeApiType: $enumDecodeNullable(
|
||||
_$YoutubeApiTypeEnumMap, json['youtubeApiType'],
|
||||
unknownValue: YoutubeApiType.youtube) ??
|
||||
YoutubeApiType.youtube,
|
||||
streamMusicCodec: $enumDecodeNullable(
|
||||
_$MusicCodecEnumMap, json['streamMusicCodec'],
|
||||
unknownValue: MusicCodec.weba) ??
|
||||
MusicCodec.weba,
|
||||
downloadMusicCodec: $enumDecodeNullable(
|
||||
_$MusicCodecEnumMap, json['downloadMusicCodec'],
|
||||
unknownValue: MusicCodec.m4a) ??
|
||||
MusicCodec.m4a,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserPreferencesToJson(UserPreferences instance) =>
|
||||
<String, dynamic>{
|
||||
'audioQuality': _$AudioQualityEnumMap[instance.audioQuality]!,
|
||||
'albumColorSync': instance.albumColorSync,
|
||||
'amoledDarkTheme': instance.amoledDarkTheme,
|
||||
'checkUpdate': instance.checkUpdate,
|
||||
'normalizeAudio': instance.normalizeAudio,
|
||||
'showSystemTrayIcon': instance.showSystemTrayIcon,
|
||||
'skipNonMusic': instance.skipNonMusic,
|
||||
'systemTitleBar': instance.systemTitleBar,
|
||||
'closeBehavior': _$CloseBehaviorEnumMap[instance.closeBehavior]!,
|
||||
'accentColorScheme':
|
||||
UserPreferences._accentColorSchemeToJson(instance.accentColorScheme),
|
||||
'layoutMode': _$LayoutModeEnumMap[instance.layoutMode]!,
|
||||
'locale': UserPreferences._localeToJson(instance.locale),
|
||||
'recommendationMarket': _$MarketEnumMap[instance.recommendationMarket]!,
|
||||
'searchMode': _$SearchModeEnumMap[instance.searchMode]!,
|
||||
'downloadLocation': instance.downloadLocation,
|
||||
'pipedInstance': instance.pipedInstance,
|
||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||
'youtubeApiType': _$YoutubeApiTypeEnumMap[instance.youtubeApiType]!,
|
||||
'streamMusicCodec': _$MusicCodecEnumMap[instance.streamMusicCodec]!,
|
||||
'downloadMusicCodec': _$MusicCodecEnumMap[instance.downloadMusicCodec]!,
|
||||
};
|
||||
|
||||
const _$AudioQualityEnumMap = {
|
||||
AudioQuality.high: 'high',
|
||||
AudioQuality.low: 'low',
|
||||
};
|
||||
|
||||
const _$CloseBehaviorEnumMap = {
|
||||
CloseBehavior.minimizeToTray: 'minimizeToTray',
|
||||
CloseBehavior.close: 'close',
|
||||
};
|
||||
|
||||
const _$LayoutModeEnumMap = {
|
||||
LayoutMode.compact: 'compact',
|
||||
LayoutMode.extended: 'extended',
|
||||
LayoutMode.adaptive: 'adaptive',
|
||||
};
|
||||
|
||||
const _$MarketEnumMap = {
|
||||
Market.AD: 'AD',
|
||||
Market.AE: 'AE',
|
||||
Market.AF: 'AF',
|
||||
Market.AG: 'AG',
|
||||
Market.AI: 'AI',
|
||||
Market.AL: 'AL',
|
||||
Market.AM: 'AM',
|
||||
Market.AO: 'AO',
|
||||
Market.AQ: 'AQ',
|
||||
Market.AR: 'AR',
|
||||
Market.AS: 'AS',
|
||||
Market.AT: 'AT',
|
||||
Market.AU: 'AU',
|
||||
Market.AW: 'AW',
|
||||
Market.AX: 'AX',
|
||||
Market.AZ: 'AZ',
|
||||
Market.BA: 'BA',
|
||||
Market.BB: 'BB',
|
||||
Market.BD: 'BD',
|
||||
Market.BE: 'BE',
|
||||
Market.BF: 'BF',
|
||||
Market.BG: 'BG',
|
||||
Market.BH: 'BH',
|
||||
Market.BI: 'BI',
|
||||
Market.BJ: 'BJ',
|
||||
Market.BL: 'BL',
|
||||
Market.BM: 'BM',
|
||||
Market.BN: 'BN',
|
||||
Market.BO: 'BO',
|
||||
Market.BQ: 'BQ',
|
||||
Market.BR: 'BR',
|
||||
Market.BS: 'BS',
|
||||
Market.BT: 'BT',
|
||||
Market.BV: 'BV',
|
||||
Market.BW: 'BW',
|
||||
Market.BY: 'BY',
|
||||
Market.BZ: 'BZ',
|
||||
Market.CA: 'CA',
|
||||
Market.CC: 'CC',
|
||||
Market.CD: 'CD',
|
||||
Market.CF: 'CF',
|
||||
Market.CG: 'CG',
|
||||
Market.CH: 'CH',
|
||||
Market.CI: 'CI',
|
||||
Market.CK: 'CK',
|
||||
Market.CL: 'CL',
|
||||
Market.CM: 'CM',
|
||||
Market.CN: 'CN',
|
||||
Market.CO: 'CO',
|
||||
Market.CR: 'CR',
|
||||
Market.CU: 'CU',
|
||||
Market.CV: 'CV',
|
||||
Market.CW: 'CW',
|
||||
Market.CX: 'CX',
|
||||
Market.CY: 'CY',
|
||||
Market.CZ: 'CZ',
|
||||
Market.DE: 'DE',
|
||||
Market.DJ: 'DJ',
|
||||
Market.DK: 'DK',
|
||||
Market.DM: 'DM',
|
||||
Market.DO: 'DO',
|
||||
Market.DZ: 'DZ',
|
||||
Market.EC: 'EC',
|
||||
Market.EE: 'EE',
|
||||
Market.EG: 'EG',
|
||||
Market.EH: 'EH',
|
||||
Market.ER: 'ER',
|
||||
Market.ES: 'ES',
|
||||
Market.ET: 'ET',
|
||||
Market.FI: 'FI',
|
||||
Market.FJ: 'FJ',
|
||||
Market.FK: 'FK',
|
||||
Market.FM: 'FM',
|
||||
Market.FO: 'FO',
|
||||
Market.FR: 'FR',
|
||||
Market.GA: 'GA',
|
||||
Market.GB: 'GB',
|
||||
Market.GD: 'GD',
|
||||
Market.GE: 'GE',
|
||||
Market.GF: 'GF',
|
||||
Market.GG: 'GG',
|
||||
Market.GH: 'GH',
|
||||
Market.GI: 'GI',
|
||||
Market.GL: 'GL',
|
||||
Market.GM: 'GM',
|
||||
Market.GN: 'GN',
|
||||
Market.GP: 'GP',
|
||||
Market.GQ: 'GQ',
|
||||
Market.GR: 'GR',
|
||||
Market.GS: 'GS',
|
||||
Market.GT: 'GT',
|
||||
Market.GU: 'GU',
|
||||
Market.GW: 'GW',
|
||||
Market.GY: 'GY',
|
||||
Market.HK: 'HK',
|
||||
Market.HM: 'HM',
|
||||
Market.HN: 'HN',
|
||||
Market.HR: 'HR',
|
||||
Market.HT: 'HT',
|
||||
Market.HU: 'HU',
|
||||
Market.ID: 'ID',
|
||||
Market.IE: 'IE',
|
||||
Market.IL: 'IL',
|
||||
Market.IM: 'IM',
|
||||
Market.IN: 'IN',
|
||||
Market.IO: 'IO',
|
||||
Market.IQ: 'IQ',
|
||||
Market.IR: 'IR',
|
||||
Market.IS: 'IS',
|
||||
Market.IT: 'IT',
|
||||
Market.JE: 'JE',
|
||||
Market.JM: 'JM',
|
||||
Market.JO: 'JO',
|
||||
Market.JP: 'JP',
|
||||
Market.KE: 'KE',
|
||||
Market.KG: 'KG',
|
||||
Market.KH: 'KH',
|
||||
Market.KI: 'KI',
|
||||
Market.KM: 'KM',
|
||||
Market.KN: 'KN',
|
||||
Market.KP: 'KP',
|
||||
Market.KR: 'KR',
|
||||
Market.KW: 'KW',
|
||||
Market.KY: 'KY',
|
||||
Market.KZ: 'KZ',
|
||||
Market.LA: 'LA',
|
||||
Market.LB: 'LB',
|
||||
Market.LC: 'LC',
|
||||
Market.LI: 'LI',
|
||||
Market.LK: 'LK',
|
||||
Market.LR: 'LR',
|
||||
Market.LS: 'LS',
|
||||
Market.LT: 'LT',
|
||||
Market.LU: 'LU',
|
||||
Market.LV: 'LV',
|
||||
Market.LY: 'LY',
|
||||
Market.MA: 'MA',
|
||||
Market.MC: 'MC',
|
||||
Market.MD: 'MD',
|
||||
Market.ME: 'ME',
|
||||
Market.MF: 'MF',
|
||||
Market.MG: 'MG',
|
||||
Market.MH: 'MH',
|
||||
Market.MK: 'MK',
|
||||
Market.ML: 'ML',
|
||||
Market.MM: 'MM',
|
||||
Market.MN: 'MN',
|
||||
Market.MO: 'MO',
|
||||
Market.MP: 'MP',
|
||||
Market.MQ: 'MQ',
|
||||
Market.MR: 'MR',
|
||||
Market.MS: 'MS',
|
||||
Market.MT: 'MT',
|
||||
Market.MU: 'MU',
|
||||
Market.MV: 'MV',
|
||||
Market.MW: 'MW',
|
||||
Market.MX: 'MX',
|
||||
Market.MY: 'MY',
|
||||
Market.MZ: 'MZ',
|
||||
Market.NA: 'NA',
|
||||
Market.NC: 'NC',
|
||||
Market.NE: 'NE',
|
||||
Market.NF: 'NF',
|
||||
Market.NG: 'NG',
|
||||
Market.NI: 'NI',
|
||||
Market.NL: 'NL',
|
||||
Market.NO: 'NO',
|
||||
Market.NP: 'NP',
|
||||
Market.NR: 'NR',
|
||||
Market.NU: 'NU',
|
||||
Market.NZ: 'NZ',
|
||||
Market.OM: 'OM',
|
||||
Market.PA: 'PA',
|
||||
Market.PE: 'PE',
|
||||
Market.PF: 'PF',
|
||||
Market.PG: 'PG',
|
||||
Market.PH: 'PH',
|
||||
Market.PK: 'PK',
|
||||
Market.PL: 'PL',
|
||||
Market.PM: 'PM',
|
||||
Market.PN: 'PN',
|
||||
Market.PR: 'PR',
|
||||
Market.PS: 'PS',
|
||||
Market.PT: 'PT',
|
||||
Market.PW: 'PW',
|
||||
Market.PY: 'PY',
|
||||
Market.QA: 'QA',
|
||||
Market.RE: 'RE',
|
||||
Market.RO: 'RO',
|
||||
Market.RS: 'RS',
|
||||
Market.RU: 'RU',
|
||||
Market.RW: 'RW',
|
||||
Market.SA: 'SA',
|
||||
Market.SB: 'SB',
|
||||
Market.SC: 'SC',
|
||||
Market.SD: 'SD',
|
||||
Market.SE: 'SE',
|
||||
Market.SG: 'SG',
|
||||
Market.SH: 'SH',
|
||||
Market.SI: 'SI',
|
||||
Market.SJ: 'SJ',
|
||||
Market.SK: 'SK',
|
||||
Market.SL: 'SL',
|
||||
Market.SM: 'SM',
|
||||
Market.SN: 'SN',
|
||||
Market.SO: 'SO',
|
||||
Market.SR: 'SR',
|
||||
Market.SS: 'SS',
|
||||
Market.ST: 'ST',
|
||||
Market.SV: 'SV',
|
||||
Market.SX: 'SX',
|
||||
Market.SY: 'SY',
|
||||
Market.SZ: 'SZ',
|
||||
Market.TC: 'TC',
|
||||
Market.TD: 'TD',
|
||||
Market.TF: 'TF',
|
||||
Market.TG: 'TG',
|
||||
Market.TH: 'TH',
|
||||
Market.TJ: 'TJ',
|
||||
Market.TK: 'TK',
|
||||
Market.TL: 'TL',
|
||||
Market.TM: 'TM',
|
||||
Market.TN: 'TN',
|
||||
Market.TO: 'TO',
|
||||
Market.TR: 'TR',
|
||||
Market.TT: 'TT',
|
||||
Market.TV: 'TV',
|
||||
Market.TW: 'TW',
|
||||
Market.TZ: 'TZ',
|
||||
Market.UA: 'UA',
|
||||
Market.UG: 'UG',
|
||||
Market.UM: 'UM',
|
||||
Market.US: 'US',
|
||||
Market.UY: 'UY',
|
||||
Market.UZ: 'UZ',
|
||||
Market.VA: 'VA',
|
||||
Market.VC: 'VC',
|
||||
Market.VE: 'VE',
|
||||
Market.VG: 'VG',
|
||||
Market.VI: 'VI',
|
||||
Market.VN: 'VN',
|
||||
Market.VU: 'VU',
|
||||
Market.WF: 'WF',
|
||||
Market.WS: 'WS',
|
||||
Market.XK: 'XK',
|
||||
Market.YE: 'YE',
|
||||
Market.YT: 'YT',
|
||||
Market.ZA: 'ZA',
|
||||
Market.ZM: 'ZM',
|
||||
Market.ZW: 'ZW',
|
||||
};
|
||||
|
||||
const _$SearchModeEnumMap = {
|
||||
SearchMode.youtube: 'youtube',
|
||||
SearchMode.youtubeMusic: 'youtubeMusic',
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
ThemeMode.system: 'system',
|
||||
ThemeMode.light: 'light',
|
||||
ThemeMode.dark: 'dark',
|
||||
};
|
||||
|
||||
const _$YoutubeApiTypeEnumMap = {
|
||||
YoutubeApiType.youtube: 'youtube',
|
||||
YoutubeApiType.piped: 'piped',
|
||||
};
|
||||
|
||||
const _$MusicCodecEnumMap = {
|
||||
MusicCodec.m4a: 'm4a',
|
||||
MusicCodec.weba: 'weba',
|
||||
};
|
@ -1,391 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
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_state_notifier.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
enum LayoutMode {
|
||||
compact,
|
||||
extended,
|
||||
adaptive,
|
||||
}
|
||||
|
||||
enum AudioQuality {
|
||||
high,
|
||||
low,
|
||||
}
|
||||
|
||||
enum CloseBehavior {
|
||||
minimizeToTray,
|
||||
close,
|
||||
}
|
||||
|
||||
enum YoutubeApiType {
|
||||
youtube,
|
||||
piped;
|
||||
|
||||
String get label => name[0].toUpperCase() + name.substring(1);
|
||||
}
|
||||
|
||||
enum MusicCodec {
|
||||
m4a._("M4a (Best for downloaded music)"),
|
||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||
|
||||
final String label;
|
||||
const MusicCodec._(this.label);
|
||||
}
|
||||
|
||||
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;
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<String> _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<UserPreferences> fromJson(Map<String, dynamic> 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<String, dynamic> 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<UserPreferences> {
|
||||
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),
|
||||
);
|
@ -1,5 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/youtube/youtube.dart';
|
||||
|
||||
final youtubeProvider = Provider<YoutubeEndpoints>((ref) {
|
||||
|
@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class AlbumQueries {
|
||||
const AlbumQueries();
|
||||
|
@ -6,7 +6,7 @@ import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class CategoryQueries {
|
||||
const CategoryQueries();
|
||||
|
@ -11,7 +11,7 @@ import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart';
|
||||
import 'package:spotube/hooks/spotify/use_spotify_query.dart';
|
||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
typedef RecommendationParameters = ({
|
||||
RecommendationAttribute acousticness,
|
||||
|
@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
import 'package:spotube/provider/custom_spotify_endpoint_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
|
||||
class ViewsQueries {
|
||||
const ViewsQueries();
|
||||
|
@ -4,7 +4,7 @@ import 'package:piped_client/piped_client.dart';
|
||||
import 'package:spotube/collections/routes.dart';
|
||||
import 'package:spotube/components/shared/dialogs/piped_down_dialog.dart';
|
||||
import 'package:spotube/models/matched_track.dart';
|
||||
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||
import 'package:spotube/utils/primitive_utils.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user