mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-06 15:39:41 +00:00
refactor: extract settings section to separate files
This commit is contained in:
parent
1334a62aae
commit
ac0e2e74d8
@ -74,7 +74,8 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
searchController: searchController,
|
searchController: searchController,
|
||||||
searchFocus: searchFocus,
|
searchFocus: searchFocus,
|
||||||
),
|
),
|
||||||
if (!categoriesQuery.hasPageData)
|
if (!categoriesQuery.hasPageData &&
|
||||||
|
!categoriesQuery.isLoadingNextPage)
|
||||||
const ShimmerCategories()
|
const ShimmerCategories()
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
84
lib/pages/settings/sections/about.dart
Normal file
84
lib/pages/settings/sections/about.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/collections/env.dart';
|
||||||
|
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:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class SettingsAboutSection extends HookConsumerWidget {
|
||||||
|
const SettingsAboutSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.about,
|
||||||
|
children: [
|
||||||
|
AdaptiveListTile(
|
||||||
|
leading: const Icon(
|
||||||
|
SpotubeIcons.heart,
|
||||||
|
color: Colors.pink,
|
||||||
|
),
|
||||||
|
title: SizedBox(
|
||||||
|
height: 50,
|
||||||
|
width: 200,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: AutoSizeText(
|
||||||
|
context.l10n.u_love_spotube,
|
||||||
|
maxLines: 1,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.pink,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: (context, update) => FilledButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStatePropertyAll(Colors.red[100]),
|
||||||
|
foregroundColor:
|
||||||
|
const MaterialStatePropertyAll(Colors.pinkAccent),
|
||||||
|
padding: const MaterialStatePropertyAll(EdgeInsets.all(15)),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
launchUrlString(
|
||||||
|
"https://opencollective.com/spotube",
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(SpotubeIcons.heart),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(context.l10n.please_sponsor),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (Env.enableUpdateChecker)
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.update),
|
||||||
|
title: Text(context.l10n.check_for_updates),
|
||||||
|
value: preferences.checkUpdate,
|
||||||
|
onChanged: (checked) => preferences.setCheckUpdate(checked),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.info),
|
||||||
|
title: Text(context.l10n.about_spotube),
|
||||||
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push("/settings/about");
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
lib/pages/settings/sections/appearance.dart
Normal file
109
lib/pages/settings/sections/appearance.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
class SettingsAppearanceSection extends HookConsumerWidget {
|
||||||
|
const SettingsAppearanceSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
final pickColorScheme = useCallback(() {
|
||||||
|
return () => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return const ColorSchemePickerDialog();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.appearance,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<LayoutMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.dashboard),
|
||||||
|
title: Text(context.l10n.layout_mode),
|
||||||
|
subtitle: Text(context.l10n.override_layout_settings),
|
||||||
|
value: preferences.layoutMode,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setLayoutMode(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: LayoutMode.adaptive,
|
||||||
|
child: Text(context.l10n.adaptive),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: LayoutMode.compact,
|
||||||
|
child: Text(context.l10n.compact),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: LayoutMode.extended,
|
||||||
|
child: Text(context.l10n.extended),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<ThemeMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.darkMode),
|
||||||
|
title: Text(context.l10n.theme),
|
||||||
|
value: preferences.themeMode,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.dark,
|
||||||
|
child: Text(context.l10n.dark),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.light,
|
||||||
|
child: Text(context.l10n.light),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.system,
|
||||||
|
child: Text(context.l10n.system),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setThemeMode(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.amoled),
|
||||||
|
title: Text(context.l10n.use_amoled_mode),
|
||||||
|
subtitle: Text(context.l10n.pitch_dark_theme),
|
||||||
|
value: preferences.amoledDarkTheme,
|
||||||
|
onChanged: preferences.setAmoledDarkTheme,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.palette),
|
||||||
|
title: Text(context.l10n.accent_color),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
trailing: ColorTile.compact(
|
||||||
|
color: preferences.accentColorScheme,
|
||||||
|
onPressed: pickColorScheme(),
|
||||||
|
isActive: true,
|
||||||
|
),
|
||||||
|
onTap: pickColorScheme(),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.colorSync),
|
||||||
|
title: Text(context.l10n.sync_album_color),
|
||||||
|
subtitle: Text(context.l10n.sync_album_color_description),
|
||||||
|
value: preferences.albumColorSync,
|
||||||
|
onChanged: preferences.setAlbumColorSync,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
lib/pages/settings/sections/desktop.dart
Normal file
54
lib/pages/settings/sections/desktop.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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/components/shared/adaptive/adaptive_select_tile.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
|
|
||||||
|
class SettingsDesktopSection extends HookConsumerWidget {
|
||||||
|
const SettingsDesktopSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.desktop,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<CloseBehavior>(
|
||||||
|
secondary: const Icon(SpotubeIcons.close),
|
||||||
|
title: Text(context.l10n.close_behavior),
|
||||||
|
value: preferences.closeBehavior,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: CloseBehavior.close,
|
||||||
|
child: Text(context.l10n.close),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: CloseBehavior.minimizeToTray,
|
||||||
|
child: Text(context.l10n.minimize_to_tray),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setCloseBehavior(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.tray),
|
||||||
|
title: Text(context.l10n.show_tray_icon),
|
||||||
|
value: preferences.showSystemTrayIcon,
|
||||||
|
onChanged: preferences.setShowSystemTrayIcon,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.window),
|
||||||
|
title: Text(context.l10n.use_system_title_bar),
|
||||||
|
value: preferences.systemTitleBar,
|
||||||
|
onChanged: preferences.setSystemTitleBar,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
lib/pages/settings/sections/developers.dart
Normal file
27
lib/pages/settings/sections/developers.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
|
class SettingsDevelopersSection extends HookWidget {
|
||||||
|
const SettingsDevelopersSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.developers,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.logs),
|
||||||
|
title: Text(context.l10n.logs),
|
||||||
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push("/settings/logs");
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
lib/pages/settings/sections/downloads.dart
Normal file
51
lib/pages/settings/sections/downloads.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:file_selector/file_selector.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
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';
|
||||||
|
|
||||||
|
class SettingsDownloadsSection extends HookConsumerWidget {
|
||||||
|
const SettingsDownloadsSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
|
final pickDownloadLocation = useCallback(() async {
|
||||||
|
if (DesktopTools.platform.isMobile) {
|
||||||
|
final dirStr = await FilePicker.platform.getDirectoryPath(
|
||||||
|
initialDirectory: preferences.downloadLocation,
|
||||||
|
);
|
||||||
|
if (dirStr == null) return;
|
||||||
|
preferences.setDownloadLocation(dirStr);
|
||||||
|
} else {
|
||||||
|
String? dirStr = await getDirectoryPath(
|
||||||
|
initialDirectory: preferences.downloadLocation,
|
||||||
|
);
|
||||||
|
if (dirStr == null) return;
|
||||||
|
preferences.setDownloadLocation(dirStr);
|
||||||
|
}
|
||||||
|
}, [preferences.downloadLocation]);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.downloads,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.download),
|
||||||
|
title: Text(context.l10n.download_location),
|
||||||
|
subtitle: Text(preferences.downloadLocation),
|
||||||
|
trailing: FilledButton(
|
||||||
|
onPressed: pickDownloadLocation,
|
||||||
|
child: const Icon(SpotubeIcons.folder),
|
||||||
|
),
|
||||||
|
onTap: pickDownloadLocation,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
lib/pages/settings/sections/language_region.dart
Normal file
74
lib/pages/settings/sections/language_region.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/collections/language_codes.dart';
|
||||||
|
import 'package:spotube/collections/spotify_markets.dart';
|
||||||
|
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/constrains.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/l10n/l10n.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
|
|
||||||
|
class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||||
|
const SettingsLanguageRegionSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.language_region,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<Locale>(
|
||||||
|
value: preferences.locale,
|
||||||
|
onChanged: (locale) {
|
||||||
|
if (locale == null) return;
|
||||||
|
preferences.setLocale(locale);
|
||||||
|
},
|
||||||
|
title: Text(context.l10n.language),
|
||||||
|
secondary: const Icon(SpotubeIcons.language),
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: const Locale("system", "system"),
|
||||||
|
child: Text(context.l10n.system_default),
|
||||||
|
),
|
||||||
|
for (final locale in L10n.all)
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: locale,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
final isoCodeName = LanguageLocals.getDisplayLanguage(
|
||||||
|
locale.languageCode,
|
||||||
|
);
|
||||||
|
return Text(
|
||||||
|
"${isoCodeName.name} (${isoCodeName.nativeName})",
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<Market>(
|
||||||
|
breakLayout: mediaQuery.lgAndUp,
|
||||||
|
secondary: const Icon(SpotubeIcons.shoppingBag),
|
||||||
|
title: Text(context.l10n.market_place_region),
|
||||||
|
subtitle: Text(context.l10n.recommendation_country),
|
||||||
|
value: preferences.recommendationMarket,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setRecommendationMarket(value);
|
||||||
|
},
|
||||||
|
options: spotifyMarkets
|
||||||
|
.map(
|
||||||
|
(country) => DropdownMenuItem(
|
||||||
|
value: country.$1,
|
||||||
|
child: Text(country.$2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
218
lib/pages/settings/sections/playback.dart
Normal file
218
lib/pages/settings/sections/playback.dart
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:piped_client/piped_client.dart';
|
||||||
|
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/models/matched_track.dart';
|
||||||
|
import 'package:spotube/provider/piped_instances_provider.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences_provider.dart';
|
||||||
|
|
||||||
|
class SettingsPlaybackSection extends HookConsumerWidget {
|
||||||
|
const SettingsPlaybackSection({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return SectionCardWithHeading(
|
||||||
|
heading: context.l10n.playback,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<AudioQuality>(
|
||||||
|
secondary: const Icon(SpotubeIcons.audioQuality),
|
||||||
|
title: Text(context.l10n.audio_quality),
|
||||||
|
value: preferences.audioQuality,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: AudioQuality.high,
|
||||||
|
child: Text(context.l10n.high),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: AudioQuality.low,
|
||||||
|
child: Text(context.l10n.low),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setAudioQuality(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<YoutubeApiType>(
|
||||||
|
secondary: const Icon(SpotubeIcons.api),
|
||||||
|
title: Text(context.l10n.youtube_api_type),
|
||||||
|
value: preferences.youtubeApiType,
|
||||||
|
options: YoutubeApiType.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setYoutubeApiType(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.youtubeApiType == YoutubeApiType.youtube
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: Consumer(builder: (context, ref, child) {
|
||||||
|
final instanceList = ref.watch(pipedInstancesFutureProvider);
|
||||||
|
|
||||||
|
return instanceList.when(
|
||||||
|
data: (data) {
|
||||||
|
return AdaptiveSelectTile<String>(
|
||||||
|
secondary: const Icon(SpotubeIcons.piped),
|
||||||
|
title: Text(context.l10n.piped_instance),
|
||||||
|
subtitle: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: context.l10n.piped_description,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const TextSpan(text: "\n"),
|
||||||
|
TextSpan(
|
||||||
|
text: context.l10n.piped_warning,
|
||||||
|
style: theme.textTheme.labelMedium,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
value: preferences.pipedInstance,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: data
|
||||||
|
.sortedBy((e) => e.name)
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: e.apiUrl,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "${e.name.trim()}\n",
|
||||||
|
style: theme.textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: e.locations
|
||||||
|
.map(countryCodeToEmoji)
|
||||||
|
.join(""),
|
||||||
|
style: GoogleFonts.notoColorEmoji(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setPipedInstance(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) => Text(error.toString()),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.youtubeApiType == YoutubeApiType.youtube
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AdaptiveSelectTile<SearchMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.search),
|
||||||
|
title: Text(context.l10n.search_mode),
|
||||||
|
value: preferences.searchMode,
|
||||||
|
options: SearchMode.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setSearchMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.searchMode == SearchMode.youtubeMusic &&
|
||||||
|
preferences.youtubeApiType == YoutubeApiType.piped
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.skip),
|
||||||
|
title: Text(context.l10n.skip_non_music),
|
||||||
|
value: preferences.skipNonMusic,
|
||||||
|
onChanged: (state) {
|
||||||
|
preferences.setSkipNonMusic(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||||
|
title: Text(context.l10n.blacklist),
|
||||||
|
subtitle: Text(context.l10n.blacklist_description),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push("/settings/blacklist");
|
||||||
|
},
|
||||||
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.normalize),
|
||||||
|
title: Text(context.l10n.normalize_audio),
|
||||||
|
value: preferences.normalizeAudio,
|
||||||
|
onChanged: preferences.setNormalizeAudio,
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<MusicCodec>(
|
||||||
|
secondary: const Icon(SpotubeIcons.stream),
|
||||||
|
title: Text(context.l10n.streaming_music_codec),
|
||||||
|
value: preferences.streamMusicCodec,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: MusicCodec.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(
|
||||||
|
e.label,
|
||||||
|
style: theme.textTheme.labelMedium,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setStreamMusicCodec(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<MusicCodec>(
|
||||||
|
secondary: const Icon(SpotubeIcons.file),
|
||||||
|
title: Text(context.l10n.download_music_codec),
|
||||||
|
value: preferences.downloadMusicCodec,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: MusicCodec.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(
|
||||||
|
e.label,
|
||||||
|
style: theme.textTheme.labelMedium,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setDownloadMusicCodec(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,34 +1,19 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:file_selector/file_selector.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
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_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:piped_client/piped_client.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
|
||||||
import 'package:spotube/collections/env.dart';
|
|
||||||
import 'package:spotube/collections/language_codes.dart';
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
|
||||||
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_list_tile.dart';
|
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/collections/spotify_markets.dart';
|
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/l10n/l10n.dart';
|
import 'package:spotube/pages/settings/sections/about.dart';
|
||||||
import 'package:spotube/models/matched_track.dart';
|
|
||||||
import 'package:spotube/pages/settings/sections/accounts.dart';
|
import 'package:spotube/pages/settings/sections/accounts.dart';
|
||||||
|
import 'package:spotube/pages/settings/sections/appearance.dart';
|
||||||
|
import 'package:spotube/pages/settings/sections/desktop.dart';
|
||||||
|
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_provider.dart';
|
||||||
import 'package:spotube/provider/piped_instances_provider.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
class SettingsPage extends HookConsumerWidget {
|
class SettingsPage extends HookConsumerWidget {
|
||||||
const SettingsPage({Key? key}) : super(key: key);
|
const SettingsPage({Key? key}) : super(key: key);
|
||||||
@ -36,32 +21,6 @@ class SettingsPage 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 theme = Theme.of(context);
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
|
||||||
|
|
||||||
final pickColorScheme = useCallback(() {
|
|
||||||
return () => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return const ColorSchemePickerDialog();
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
final pickDownloadLocation = useCallback(() async {
|
|
||||||
if (DesktopTools.platform.isMobile) {
|
|
||||||
final dirStr = await FilePicker.platform.getDirectoryPath(
|
|
||||||
initialDirectory: preferences.downloadLocation,
|
|
||||||
);
|
|
||||||
if (dirStr == null) return;
|
|
||||||
preferences.setDownloadLocation(dirStr);
|
|
||||||
} else {
|
|
||||||
String? dirStr = await getDirectoryPath(
|
|
||||||
initialDirectory: preferences.downloadLocation,
|
|
||||||
);
|
|
||||||
if (dirStr == null) return;
|
|
||||||
preferences.setDownloadLocation(dirStr);
|
|
||||||
}
|
|
||||||
}, [preferences.downloadLocation]);
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
@ -80,486 +39,14 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
const SettingsAccountSection(),
|
const SettingsAccountSection(),
|
||||||
SectionCardWithHeading(
|
const SettingsLanguageRegionSection(),
|
||||||
heading: context.l10n.language_region,
|
const SettingsAppearanceSection(),
|
||||||
children: [
|
const SettingsPlaybackSection(),
|
||||||
AdaptiveSelectTile<Locale>(
|
const SettingsDownloadsSection(),
|
||||||
value: preferences.locale,
|
|
||||||
onChanged: (locale) {
|
|
||||||
if (locale == null) return;
|
|
||||||
preferences.setLocale(locale);
|
|
||||||
},
|
|
||||||
title: Text(context.l10n.language),
|
|
||||||
secondary: const Icon(SpotubeIcons.language),
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: const Locale("system", "system"),
|
|
||||||
child: Text(context.l10n.system_default),
|
|
||||||
),
|
|
||||||
for (final locale in L10n.all)
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: locale,
|
|
||||||
child: Builder(builder: (context) {
|
|
||||||
final isoCodeName =
|
|
||||||
LanguageLocals.getDisplayLanguage(
|
|
||||||
locale.languageCode,
|
|
||||||
);
|
|
||||||
return Text(
|
|
||||||
"${isoCodeName.name} (${isoCodeName.nativeName})",
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<Market>(
|
|
||||||
breakLayout: mediaQuery.lgAndUp,
|
|
||||||
secondary: const Icon(SpotubeIcons.shoppingBag),
|
|
||||||
title: Text(context.l10n.market_place_region),
|
|
||||||
subtitle: Text(context.l10n.recommendation_country),
|
|
||||||
value: preferences.recommendationMarket,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setRecommendationMarket(value);
|
|
||||||
},
|
|
||||||
options: spotifyMarkets
|
|
||||||
.map(
|
|
||||||
(country) => DropdownMenuItem(
|
|
||||||
value: country.$1,
|
|
||||||
child: Text(country.$2),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.appearance,
|
|
||||||
children: [
|
|
||||||
AdaptiveSelectTile<LayoutMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.dashboard),
|
|
||||||
title: Text(context.l10n.layout_mode),
|
|
||||||
subtitle:
|
|
||||||
Text(context.l10n.override_layout_settings),
|
|
||||||
value: preferences.layoutMode,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setLayoutMode(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.adaptive,
|
|
||||||
child: Text(context.l10n.adaptive),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.compact,
|
|
||||||
child: Text(context.l10n.compact),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.extended,
|
|
||||||
child: Text(context.l10n.extended),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<ThemeMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.darkMode),
|
|
||||||
title: Text(context.l10n.theme),
|
|
||||||
value: preferences.themeMode,
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.dark,
|
|
||||||
child: Text(context.l10n.dark),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.light,
|
|
||||||
child: Text(context.l10n.light),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.system,
|
|
||||||
child: Text(context.l10n.system),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setThemeMode(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.amoled),
|
|
||||||
title: Text(context.l10n.use_amoled_mode),
|
|
||||||
subtitle: Text(context.l10n.pitch_dark_theme),
|
|
||||||
value: preferences.amoledDarkTheme,
|
|
||||||
onChanged: preferences.setAmoledDarkTheme,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.palette),
|
|
||||||
title: Text(context.l10n.accent_color),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
vertical: 5,
|
|
||||||
),
|
|
||||||
trailing: ColorTile.compact(
|
|
||||||
color: preferences.accentColorScheme,
|
|
||||||
onPressed: pickColorScheme(),
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
onTap: pickColorScheme(),
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.colorSync),
|
|
||||||
title: Text(context.l10n.sync_album_color),
|
|
||||||
subtitle:
|
|
||||||
Text(context.l10n.sync_album_color_description),
|
|
||||||
value: preferences.albumColorSync,
|
|
||||||
onChanged: preferences.setAlbumColorSync,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.playback,
|
|
||||||
children: [
|
|
||||||
AdaptiveSelectTile<AudioQuality>(
|
|
||||||
secondary: const Icon(SpotubeIcons.audioQuality),
|
|
||||||
title: Text(context.l10n.audio_quality),
|
|
||||||
value: preferences.audioQuality,
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: AudioQuality.high,
|
|
||||||
child: Text(context.l10n.high),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: AudioQuality.low,
|
|
||||||
child: Text(context.l10n.low),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setAudioQuality(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<YoutubeApiType>(
|
|
||||||
secondary: const Icon(SpotubeIcons.api),
|
|
||||||
title: Text(context.l10n.youtube_api_type),
|
|
||||||
value: preferences.youtubeApiType,
|
|
||||||
options: YoutubeApiType.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setYoutubeApiType(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.youtube
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: Consumer(builder: (context, ref, child) {
|
|
||||||
final instanceList =
|
|
||||||
ref.watch(pipedInstancesFutureProvider);
|
|
||||||
|
|
||||||
return instanceList.when(
|
|
||||||
data: (data) {
|
|
||||||
return AdaptiveSelectTile<String>(
|
|
||||||
secondary:
|
|
||||||
const Icon(SpotubeIcons.piped),
|
|
||||||
title:
|
|
||||||
Text(context.l10n.piped_instance),
|
|
||||||
subtitle: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: context
|
|
||||||
.l10n.piped_description,
|
|
||||||
style: theme
|
|
||||||
.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const TextSpan(text: "\n"),
|
|
||||||
TextSpan(
|
|
||||||
text: context
|
|
||||||
.l10n.piped_warning,
|
|
||||||
style: theme
|
|
||||||
.textTheme.labelMedium,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
value: preferences.pipedInstance,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: data
|
|
||||||
.sortedBy((e) => e.name)
|
|
||||||
.map(
|
|
||||||
(e) => DropdownMenuItem(
|
|
||||||
value: e.apiUrl,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
"${e.name.trim()}\n",
|
|
||||||
style: theme.textTheme
|
|
||||||
.labelLarge,
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: e.locations
|
|
||||||
.map(
|
|
||||||
countryCodeToEmoji)
|
|
||||||
.join(""),
|
|
||||||
style: GoogleFonts
|
|
||||||
.notoColorEmoji(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences
|
|
||||||
.setPipedInstance(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loading: () => const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
error: (error, stackTrace) =>
|
|
||||||
Text(error.toString()),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.youtube
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: AdaptiveSelectTile<SearchMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.search),
|
|
||||||
title: Text(context.l10n.search_mode),
|
|
||||||
value: preferences.searchMode,
|
|
||||||
options: SearchMode.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setSearchMode(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.searchMode ==
|
|
||||||
SearchMode.youtubeMusic &&
|
|
||||||
preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.piped
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.skip),
|
|
||||||
title: Text(context.l10n.skip_non_music),
|
|
||||||
value: preferences.skipNonMusic,
|
|
||||||
onChanged: (state) {
|
|
||||||
preferences.setSkipNonMusic(state);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
|
||||||
title: Text(context.l10n.blacklist),
|
|
||||||
subtitle: Text(context.l10n.blacklist_description),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).push("/settings/blacklist");
|
|
||||||
},
|
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.normalize),
|
|
||||||
title: Text(context.l10n.normalize_audio),
|
|
||||||
value: preferences.normalizeAudio,
|
|
||||||
onChanged: preferences.setNormalizeAudio,
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<MusicCodec>(
|
|
||||||
secondary: const Icon(SpotubeIcons.stream),
|
|
||||||
title: Text(context.l10n.streaming_music_codec),
|
|
||||||
value: preferences.streamMusicCodec,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: MusicCodec.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(
|
|
||||||
e.label,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setStreamMusicCodec(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<MusicCodec>(
|
|
||||||
secondary: const Icon(SpotubeIcons.file),
|
|
||||||
title: Text(context.l10n.download_music_codec),
|
|
||||||
value: preferences.downloadMusicCodec,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: MusicCodec.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(
|
|
||||||
e.label,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setDownloadMusicCodec(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.downloads,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.download),
|
|
||||||
title: Text(context.l10n.download_location),
|
|
||||||
subtitle: Text(preferences.downloadLocation),
|
|
||||||
trailing: FilledButton(
|
|
||||||
onPressed: pickDownloadLocation,
|
|
||||||
child: const Icon(SpotubeIcons.folder),
|
|
||||||
),
|
|
||||||
onTap: pickDownloadLocation,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (DesktopTools.platform.isDesktop)
|
if (DesktopTools.platform.isDesktop)
|
||||||
SectionCardWithHeading(
|
const SettingsDesktopSection(),
|
||||||
heading: context.l10n.desktop,
|
if (!kIsWeb) const SettingsDevelopersSection(),
|
||||||
children: [
|
const SettingsAboutSection(),
|
||||||
AdaptiveSelectTile<CloseBehavior>(
|
|
||||||
secondary: const Icon(SpotubeIcons.close),
|
|
||||||
title: Text(context.l10n.close_behavior),
|
|
||||||
value: preferences.closeBehavior,
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: CloseBehavior.close,
|
|
||||||
child: Text(context.l10n.close),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: CloseBehavior.minimizeToTray,
|
|
||||||
child: Text(context.l10n.minimize_to_tray),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setCloseBehavior(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.tray),
|
|
||||||
title: Text(context.l10n.show_tray_icon),
|
|
||||||
value: preferences.showSystemTrayIcon,
|
|
||||||
onChanged: preferences.setShowSystemTrayIcon,
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.window),
|
|
||||||
title: Text(context.l10n.use_system_title_bar),
|
|
||||||
value: preferences.systemTitleBar,
|
|
||||||
onChanged: preferences.setSystemTitleBar,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!kIsWeb)
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.developers,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.logs),
|
|
||||||
title: Text(context.l10n.logs),
|
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).push("/settings/logs");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.about,
|
|
||||||
children: [
|
|
||||||
AdaptiveListTile(
|
|
||||||
leading: const Icon(
|
|
||||||
SpotubeIcons.heart,
|
|
||||||
color: Colors.pink,
|
|
||||||
),
|
|
||||||
title: SizedBox(
|
|
||||||
height: 50,
|
|
||||||
width: 200,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: AutoSizeText(
|
|
||||||
context.l10n.u_love_spotube,
|
|
||||||
maxLines: 1,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.pink,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: (context, update) => FilledButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor:
|
|
||||||
MaterialStatePropertyAll(Colors.red[100]),
|
|
||||||
foregroundColor: const MaterialStatePropertyAll(
|
|
||||||
Colors.pinkAccent),
|
|
||||||
padding: const MaterialStatePropertyAll(
|
|
||||||
EdgeInsets.all(15)),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
launchUrlString(
|
|
||||||
"https://opencollective.com/spotube",
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(SpotubeIcons.heart),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(context.l10n.please_sponsor),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (Env.enableUpdateChecker)
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.update),
|
|
||||||
title: Text(context.l10n.check_for_updates),
|
|
||||||
value: preferences.checkUpdate,
|
|
||||||
onChanged: (checked) =>
|
|
||||||
preferences.setCheckUpdate(checked),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.info),
|
|
||||||
title: Text(context.l10n.about_spotube),
|
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).push("/settings/about");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Center(
|
Center(
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: preferences.reset,
|
onPressed: preferences.reset,
|
||||||
|
|||||||
@ -59,9 +59,10 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
|
|
||||||
static Future<String?> read(String key) async {
|
static Future<String?> read(String key) async {
|
||||||
final localStorage = await SharedPreferences.getInstance();
|
final localStorage = await SharedPreferences.getInstance();
|
||||||
if (kIsMacOS || kIsIOS) {
|
if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
|
||||||
return localStorage.getString(key);
|
return localStorage.getString(key);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await localStorage.setBool(kIsUsingEncryption, true);
|
await localStorage.setBool(kIsUsingEncryption, true);
|
||||||
return await secureStorage.read(key: key);
|
return await secureStorage.read(key: key);
|
||||||
@ -70,14 +71,14 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
return localStorage.getString(key);
|
return localStorage.getString(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> write(String key, String value) async {
|
static Future<void> write(String key, String value) async {
|
||||||
final localStorage = await SharedPreferences.getInstance();
|
final localStorage = await SharedPreferences.getInstance();
|
||||||
if (kIsMacOS || kIsIOS) {
|
if (kIsMacOS || kIsIOS || (kIsLinux && !kIsFlatpak)) {
|
||||||
await localStorage.setString(key, value);
|
await localStorage.setString(key, value);
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await localStorage.setBool(kIsUsingEncryption, true);
|
await localStorage.setBool(kIsUsingEncryption, true);
|
||||||
await secureStorage.write(key: key, value: value);
|
await secureStorage.write(key: key, value: value);
|
||||||
@ -86,7 +87,6 @@ abstract class PersistedStateNotifier<T> extends StateNotifier<T> {
|
|||||||
await localStorage.setString(key, value);
|
await localStorage.setString(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> initializeBoxes({required String? path}) async {
|
static Future<void> initializeBoxes({required String? path}) async {
|
||||||
String? boxName = await read(kKeyBoxName);
|
String? boxName = await read(kKeyBoxName);
|
||||||
|
|||||||
@ -517,10 +517,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620"
|
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.1.1"
|
||||||
file_selector:
|
file_selector:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user