refactor: settings using shadcn components

This commit is contained in:
Kingkor Roy Tirtho 2024-12-22 11:25:01 +06:00
parent 7ee071f2e3
commit 2fefd65f51
20 changed files with 421 additions and 396 deletions

View File

@ -128,4 +128,6 @@ abstract class SpotubeIcons {
static const export = Icons.file_open_outlined; static const export = Icons.file_open_outlined;
static const delete = FeatherIcons.trash2; static const delete = FeatherIcons.trash2;
static const open = FeatherIcons.externalLink; static const open = FeatherIcons.externalLink;
static const radioChecked = Icons.radio_button_on_rounded;
static const radioUnchecked = Icons.radio_button_off_rounded;
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile, ListTileControlAffinity;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
@ -11,7 +12,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
final T value; final T value;
final ValueChanged<T?>? onChanged; final ValueChanged<T?>? onChanged;
final List<DropdownMenuItem<T>> options; final List<SelectItemButton<T>> options;
/// Show the smaller value when the breakpoint is reached /// Show the smaller value when the breakpoint is reached
/// ///
@ -39,55 +40,25 @@ class AdaptiveSelectTile<T> extends HookWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
final rawControl = DecoratedBox(
decoration: BoxDecoration(
color: theme.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton<T>(
items: options,
value: value,
onChanged: onChanged,
menuMaxHeight: mediaQuery.size.height * 0.6,
underline: const SizedBox.shrink(),
padding: const EdgeInsets.symmetric(horizontal: 10),
borderRadius: BorderRadius.circular(10),
icon: const Icon(SpotubeIcons.angleDown),
dropdownColor: theme.colorScheme.secondaryContainer,
),
);
final controlPlaceholder = useMemoized(
() => options
.firstWhere(
(element) => element.value == value,
orElse: () => DropdownMenuItem<T>(
value: null,
child: Container(),
),
)
.child,
[value, options]);
final control = breakLayout ?? mediaQuery.mdAndUp Widget? control = Select<T>(
? rawControl itemBuilder: (context, item) {
: showValueWhenUnfolded return options.firstWhere((element) => element.value == item).child;
? Container( },
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), value: value,
decoration: BoxDecoration( onChanged: onChanged,
border: Border.all( children: options,
color: theme.colorScheme.primary, );
width: 2,
), if (mediaQuery.smAndDown) {
borderRadius: BorderRadius.circular(10), if (showValueWhenUnfolded) {
), control = OutlineBadge(
child: DefaultTextStyle( child: options.firstWhere((element) => element.value == value).child,
style: TextStyle( );
color: theme.colorScheme.primary, } else {
), control = null;
child: controlPlaceholder, }
), }
)
: const SizedBox.shrink();
return ListTile( return ListTile(
title: title, title: title,
@ -104,20 +75,26 @@ class AdaptiveSelectTile<T> extends HookWidget {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return SimpleDialog( return AlertDialog(
title: title, content: ListView.builder(
children: [ shrinkWrap: true,
for (final option in options) itemCount: options.length,
RadioListTile<T>( itemBuilder: (context, index) {
title: option.child, final item = options[index];
value: option.value as T,
groupValue: value, return ListTile(
onChanged: (v) { iconColor: theme.colorScheme.primary,
Navigator.pop(context); leading: item.value == value
onChanged?.call(v); ? const Icon(SpotubeIcons.radioChecked)
: const Icon(SpotubeIcons.radioUnchecked),
title: item.child,
onTap: () {
onChanged?.call(item.value);
Navigator.of(context).pop();
}, },
), );
], },
),
); );
}, },
); );

View File

@ -7,6 +7,7 @@ class BackButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return IconButton.ghost( return IconButton.ghost(
size: const ButtonSize(.9),
icon: const Icon(SpotubeIcons.angleLeft), icon: const Icon(SpotubeIcons.angleLeft),
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
); );

View File

@ -4,6 +4,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/extensions/string.dart'; import 'package:spotube/extensions/string.dart';
import 'package:spotube/utils/platform.dart';
class PlaybuttonCard extends HookWidget { class PlaybuttonCard extends HookWidget {
final void Function()? onTap; final void Function()? onTap;
@ -55,10 +56,15 @@ class PlaybuttonCard extends HookWidget {
AnimatedScale( AnimatedScale(
curve: Curves.easeOutBack, curve: Curves.easeOutBack,
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
scale: states.contains(WidgetState.hovered) ? 1 : 0.7, scale: states.contains(WidgetState.hovered) || kIsMobile
? 1
: 0.7,
child: AnimatedOpacity( child: AnimatedOpacity(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
opacity: states.contains(WidgetState.hovered) ? 1 : 0, opacity:
states.contains(WidgetState.hovered) || kIsMobile
? 1
: 0,
child: IconButton.secondary( child: IconButton.secondary(
icon: const Icon(SpotubeIcons.queueAdd), icon: const Icon(SpotubeIcons.queueAdd),
onPressed: onAddToQueuePressed, onPressed: onAddToQueuePressed,
@ -70,10 +76,15 @@ class PlaybuttonCard extends HookWidget {
AnimatedScale( AnimatedScale(
curve: Curves.easeOutBack, curve: Curves.easeOutBack,
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
scale: states.contains(WidgetState.hovered) ? 1 : 0.7, scale: states.contains(WidgetState.hovered) || kIsMobile
? 1
: 0.7,
child: AnimatedOpacity( child: AnimatedOpacity(
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
opacity: states.contains(WidgetState.hovered) ? 1 : 0, opacity:
states.contains(WidgetState.hovered) || kIsMobile
? 1
: 0,
child: IconButton.secondary( child: IconButton.secondary(
icon: const Icon(SpotubeIcons.play), icon: const Icon(SpotubeIcons.play),
onPressed: onPlaybuttonPressed, onPressed: onPlaybuttonPressed,

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart' hide AppBar;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' import 'package:shadcn_flutter/shadcn_flutter.dart';
show AppBar, WidgetExtension; import 'package:spotube/components/button/back_button.dart';
import 'package:spotube/components/titlebar/titlebar_buttons.dart'; import 'package:spotube/components/titlebar/titlebar_buttons.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';

View File

@ -237,7 +237,7 @@ class PlayerQueue extends HookConsumerWidget {
right: 20, right: 20,
bottom: 20, bottom: 20,
child: IconButton.secondary( child: IconButton.secondary(
icon: const Icon(SpotubeIcons.open), icon: const Icon(SpotubeIcons.angleDown),
onPressed: () { onPressed: () {
controller.scrollToIndex( controller.scrollToIndex(
playlist.playlist.index, playlist.playlist.index,

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTileTheme, ListTileThemeData;
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
class SectionCardWithHeading extends StatelessWidget { class SectionCardWithHeading extends StatelessWidget {
final String heading; final String heading;
@ -11,27 +13,41 @@ class SectionCardWithHeading extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return ListTileTheme(
crossAxisAlignment: CrossAxisAlignment.start, data: ListTileThemeData(
mainAxisSize: MainAxisSize.min, shape: RoundedRectangleBorder(
children: [ borderRadius: context.theme.borderRadiusLg,
Padding( side: BorderSide(
padding: const EdgeInsets.symmetric(horizontal: 8.0), color: context.theme.colorScheme.border,
child: Text( width: .5,
heading,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
), ),
), ),
Padding( textColor: context.theme.colorScheme.foreground,
padding: const EdgeInsets.all(8.0), iconColor: context.theme.colorScheme.foreground,
child: Card( selectedColor: context.theme.colorScheme.accent,
clipBehavior: Clip.antiAliasWithSaveLayer, subtitleTextStyle: context.theme.typography.xSmall,
child: Column(mainAxisSize: MainAxisSize.min, children: children), ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
heading,
style: context.theme.typography.large,
),
), ),
), Padding(
], padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
).gap(8.0),
),
],
),
); );
} }
} }

View File

@ -145,12 +145,21 @@ class SearchPage extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.search), leading: const Icon(SpotubeIcons.search),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
placeholder: Text(context.l10n.search), placeholder: Text(context.l10n.search),
trailing: IconButton.ghost( trailing: AnimatedCrossFade(
size: ButtonSize.small, duration:
icon: const Icon(SpotubeIcons.close), const Duration(milliseconds: 300),
onPressed: () { crossFadeState: controller.text.isNotEmpty
controller.clear(); ? CrossFadeState.showFirst
}, : CrossFadeState.showSecond,
firstChild: IconButton.ghost(
size: ButtonSize.small,
icon: const Icon(SpotubeIcons.close),
onPressed: () {
controller.clear();
},
),
secondChild:
const SizedBox.square(dimension: 28),
), ),
onAcceptSuggestion: (index) { onAcceptSuggestion: (index) {
controller.text = controller.text =

View File

@ -1,7 +1,9 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show FilledButton, ButtonStyle, ListTile;
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' hide ButtonStyle;
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/env.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
@ -45,9 +47,13 @@ class SettingsAboutSection extends HookConsumerWidget {
trailing: (context, update) => FilledButton( trailing: (context, update) => FilledButton(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Colors.red[100]), backgroundColor: WidgetStatePropertyAll(Colors.red[100]),
foregroundColor: foregroundColor: const WidgetStatePropertyAll(Colors.pink),
const WidgetStatePropertyAll(Colors.pinkAccent),
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)), padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius: context.theme.borderRadiusLg,
),
),
), ),
onPressed: () { onPressed: () {
launchUrlString( launchUrlString(
@ -66,11 +72,14 @@ class SettingsAboutSection extends HookConsumerWidget {
), ),
), ),
if (Env.enableUpdateChecker) if (Env.enableUpdateChecker)
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.update), leading: const Icon(SpotubeIcons.update),
title: Text(context.l10n.check_for_updates), title: Text(context.l10n.check_for_updates),
value: preferences.checkUpdate, trailing: Switch(
onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked), value: preferences.checkUpdate,
onChanged: (checked) =>
preferencesNotifier.setCheckUpdate(checked),
),
), ),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.info), leading: const Icon(SpotubeIcons.info),

View File

@ -1,7 +1,8 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile;
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/image/universal_image.dart';
@ -28,11 +29,6 @@ class SettingsAccountSection extends HookConsumerWidget {
final me = ref.watch(meProvider); final me = ref.watch(meProvider);
final meData = me.asData?.value; final meData = me.asData?.value;
final logoutBtnStyle = FilledButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
);
final onLogin = useLoginCallback(ref); final onLogin = useLoginCallback(ref);
return SectionCardWithHeading( return SectionCardWithHeading(
@ -44,8 +40,9 @@ class SettingsAccountSection extends HookConsumerWidget {
title: Text(context.l10n.user_profile), title: Text(context.l10n.user_profile),
trailing: Padding( trailing: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: CircleAvatar( child: Avatar(
backgroundImage: UniversalImage.imageProvider( initials: Avatar.getInitials(meData?.displayName ?? "User"),
provider: UniversalImage.imageProvider(
(meData?.images).asUrlString( (meData?.images).asUrlString(
placeholder: ImagePlaceholder.artist, placeholder: ImagePlaceholder.artist,
), ),
@ -76,15 +73,8 @@ class SettingsAccountSection extends HookConsumerWidget {
onTap: constrains.mdAndUp ? null : onLogin, onTap: constrains.mdAndUp ? null : onLogin,
trailing: constrains.smAndDown trailing: constrains.smAndDown
? null ? null
: FilledButton( : Button.primary(
onPressed: onLogin, onPressed: onLogin,
style: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0),
),
),
),
child: Text( child: Text(
context.l10n.connect_with_spotify.toUpperCase(), context.l10n.connect_with_spotify.toUpperCase(),
), ),
@ -106,8 +96,7 @@ class SettingsAccountSection extends HookConsumerWidget {
), ),
), ),
), ),
trailing: FilledButton( trailing: Button.destructive(
style: logoutBtnStyle,
onPressed: () async { onPressed: () async {
ref.read(authenticationProvider.notifier).logout(); ref.read(authenticationProvider.notifier).logout();
GoRouter.of(context).pop(); GoRouter.of(context).pop();
@ -121,27 +110,22 @@ class SettingsAccountSection extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.lastFm), leading: const Icon(SpotubeIcons.lastFm),
title: Text(context.l10n.login_with_lastfm), title: Text(context.l10n.login_with_lastfm),
subtitle: Text(context.l10n.scrobble_to_lastfm), subtitle: Text(context.l10n.scrobble_to_lastfm),
trailing: FilledButton.icon( trailing: Button.secondary(
icon: const Icon(SpotubeIcons.lastFm), leading: const Icon(SpotubeIcons.lastFm),
label: Text(context.l10n.connect),
onPressed: () { onPressed: () {
router.push("/lastfm-login"); router.push("/lastfm-login");
}, },
style: FilledButton.styleFrom( child: Text(context.l10n.connect),
backgroundColor: const Color.fromARGB(255, 186, 0, 0),
foregroundColor: Colors.white,
),
), ),
) )
else else
ListTile( ListTile(
leading: const Icon(SpotubeIcons.lastFm), leading: const Icon(SpotubeIcons.lastFm),
title: Text(context.l10n.disconnect_lastfm), title: Text(context.l10n.disconnect_lastfm),
trailing: FilledButton( trailing: Button.destructive(
onPressed: () { onPressed: () {
ref.read(scrobblerProvider.notifier).logout(); ref.read(scrobblerProvider.notifier).logout();
}, },
style: logoutBtnStyle,
child: Text(context.l10n.disconnect), child: Text(context.l10n.disconnect),
), ),
), ),

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart' hide ThemeMode; import 'package:flutter/material.dart' show ListTile;
import 'package:shadcn_flutter/shadcn_flutter.dart' show ThemeMode; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
@ -42,15 +41,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
} }
}, },
options: [ options: [
DropdownMenuItem( SelectItemButton(
value: LayoutMode.adaptive, value: LayoutMode.adaptive,
child: Text(context.l10n.adaptive), child: Text(context.l10n.adaptive),
), ),
DropdownMenuItem( SelectItemButton(
value: LayoutMode.compact, value: LayoutMode.compact,
child: Text(context.l10n.compact), child: Text(context.l10n.compact),
), ),
DropdownMenuItem( SelectItemButton(
value: LayoutMode.extended, value: LayoutMode.extended,
child: Text(context.l10n.extended), child: Text(context.l10n.extended),
), ),
@ -61,15 +60,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
title: Text(context.l10n.theme), title: Text(context.l10n.theme),
value: preferences.themeMode, value: preferences.themeMode,
options: [ options: [
DropdownMenuItem( SelectItemButton(
value: ThemeMode.dark, value: ThemeMode.dark,
child: Text(context.l10n.dark), child: Text(context.l10n.dark),
), ),
DropdownMenuItem( SelectItemButton(
value: ThemeMode.light, value: ThemeMode.light,
child: Text(context.l10n.light), child: Text(context.l10n.light),
), ),
DropdownMenuItem( SelectItemButton(
value: ThemeMode.system, value: ThemeMode.system,
child: Text(context.l10n.system), child: Text(context.l10n.system),
), ),
@ -80,13 +79,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
} }
}, },
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.amoled), leading: const Icon(SpotubeIcons.amoled),
title: Text(context.l10n.use_amoled_mode), title: Text(context.l10n.use_amoled_mode),
subtitle: Text(context.l10n.pitch_dark_theme), subtitle: Text(context.l10n.pitch_dark_theme),
value: preferences.amoledDarkTheme, trailing: Switch(
onChanged: preferencesNotifier.setAmoledDarkTheme, value: preferences.amoledDarkTheme,
), onChanged: preferencesNotifier.setAmoledDarkTheme,
)),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.palette), leading: const Icon(SpotubeIcons.palette),
title: Text(context.l10n.accent_color), title: Text(context.l10n.accent_color),
@ -101,13 +101,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
), ),
onTap: pickColorScheme(), onTap: pickColorScheme(),
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.colorSync), leading: const Icon(SpotubeIcons.colorSync),
title: Text(context.l10n.sync_album_color), title: Text(context.l10n.sync_album_color),
subtitle: Text(context.l10n.sync_album_color_description), subtitle: Text(context.l10n.sync_album_color_description),
value: preferences.albumColorSync, trailing: Switch(
onChanged: preferencesNotifier.setAlbumColorSync, value: preferences.albumColorSync,
), onChanged: preferencesNotifier.setAlbumColorSync,
)),
]; ];
if (isGettingStarted) { if (isGettingStarted) {

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile;
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
@ -25,11 +25,11 @@ class SettingsDesktopSection extends HookConsumerWidget {
title: Text(context.l10n.close_behavior), title: Text(context.l10n.close_behavior),
value: preferences.closeBehavior, value: preferences.closeBehavior,
options: [ options: [
DropdownMenuItem( SelectItemButton(
value: CloseBehavior.close, value: CloseBehavior.close,
child: Text(context.l10n.close), child: Text(context.l10n.close),
), ),
DropdownMenuItem( SelectItemButton(
value: CloseBehavior.minimizeToTray, value: CloseBehavior.minimizeToTray,
child: Text(context.l10n.minimize_to_tray), child: Text(context.l10n.minimize_to_tray),
), ),
@ -40,23 +40,29 @@ class SettingsDesktopSection extends HookConsumerWidget {
} }
}, },
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.tray), leading: const Icon(SpotubeIcons.tray),
title: Text(context.l10n.show_tray_icon), title: Text(context.l10n.show_tray_icon),
value: preferences.showSystemTrayIcon, trailing: Switch(
onChanged: preferencesNotifier.setShowSystemTrayIcon, value: preferences.showSystemTrayIcon,
onChanged: preferencesNotifier.setShowSystemTrayIcon,
),
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.window), leading: const Icon(SpotubeIcons.window),
title: Text(context.l10n.use_system_title_bar), title: Text(context.l10n.use_system_title_bar),
value: preferences.systemTitleBar, trailing: Switch(
onChanged: preferencesNotifier.setSystemTitleBar, value: preferences.systemTitleBar,
onChanged: preferencesNotifier.setSystemTitleBar,
),
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.discord), leading: const Icon(SpotubeIcons.discord),
title: Text(context.l10n.discord_rich_presence), title: Text(context.l10n.discord_rich_presence),
value: preferences.discordPresence, trailing: Switch(
onChanged: preferencesNotifier.setDiscordPresence, value: preferences.discordPresence,
onChanged: preferencesNotifier.setDiscordPresence,
),
), ),
], ],
); );

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';

View File

@ -1,8 +1,9 @@
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart'; import 'package:file_selector/file_selector.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
@ -40,9 +41,9 @@ class SettingsDownloadsSection extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.download), leading: const Icon(SpotubeIcons.download),
title: Text(context.l10n.download_location), title: Text(context.l10n.download_location),
subtitle: Text(preferences.downloadLocation), subtitle: Text(preferences.downloadLocation),
trailing: FilledButton( trailing: IconButton.secondary(
onPressed: pickDownloadLocation, onPressed: pickDownloadLocation,
child: const Icon(SpotubeIcons.folder), icon: const Icon(SpotubeIcons.folder),
), ),
onTap: pickDownloadLocation, onTap: pickDownloadLocation,
), ),

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
import 'package:spotube/collections/language_codes.dart'; import 'package:spotube/collections/language_codes.dart';
import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/collections/spotify_markets.dart';
@ -24,7 +23,6 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
return SectionCardWithHeading( return SectionCardWithHeading(
heading: context.l10n.language_region, heading: context.l10n.language_region,
children: [ children: [
const Gap(10),
AdaptiveSelectTile<Locale>( AdaptiveSelectTile<Locale>(
value: preferences.locale, value: preferences.locale,
onChanged: (locale) { onChanged: (locale) {
@ -34,12 +32,12 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
title: Text(context.l10n.language), title: Text(context.l10n.language),
secondary: const Icon(SpotubeIcons.language), secondary: const Icon(SpotubeIcons.language),
options: [ options: [
DropdownMenuItem( SelectItemButton(
value: const Locale("system", "system"), value: const Locale("system", "system"),
child: Text(context.l10n.system_default), child: Text(context.l10n.system_default),
), ),
for (final locale in L10n.all) for (final locale in L10n.all)
DropdownMenuItem( SelectItemButton(
value: locale, value: locale,
child: Builder(builder: (context) { child: Builder(builder: (context) {
final isoCodeName = LanguageLocals.getDisplayLanguage( final isoCodeName = LanguageLocals.getDisplayLanguage(
@ -64,7 +62,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
}, },
options: spotifyMarkets options: spotifyMarkets
.map( .map(
(country) => DropdownMenuItem( (country) => SelectItemButton(
value: country.$1, value: country.$1,
child: Text(country.$2), child: Text(country.$2),
), ),

View File

@ -1,11 +1,12 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show ListTile;
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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:piped_client/piped_client.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart';
@ -30,21 +31,20 @@ class SettingsPlaybackSection extends HookConsumerWidget {
return SectionCardWithHeading( return SectionCardWithHeading(
heading: context.l10n.playback, heading: context.l10n.playback,
children: [ children: [
const Gap(10),
AdaptiveSelectTile<SourceQualities>( AdaptiveSelectTile<SourceQualities>(
secondary: const Icon(SpotubeIcons.audioQuality), secondary: const Icon(SpotubeIcons.audioQuality),
title: Text(context.l10n.audio_quality), title: Text(context.l10n.audio_quality),
value: preferences.audioQuality, value: preferences.audioQuality,
options: [ options: [
DropdownMenuItem( SelectItemButton(
value: SourceQualities.high, value: SourceQualities.high,
child: Text(context.l10n.high), child: Text(context.l10n.high),
), ),
DropdownMenuItem( SelectItemButton(
value: SourceQualities.medium, value: SourceQualities.medium,
child: Text(context.l10n.medium), child: Text(context.l10n.medium),
), ),
DropdownMenuItem( SelectItemButton(
value: SourceQualities.low, value: SourceQualities.low,
child: Text(context.l10n.low), child: Text(context.l10n.low),
), ),
@ -55,13 +55,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
} }
}, },
), ),
const Gap(5),
AdaptiveSelectTile<AudioSource>( AdaptiveSelectTile<AudioSource>(
secondary: const Icon(SpotubeIcons.api), secondary: const Icon(SpotubeIcons.api),
title: Text(context.l10n.audio_source), title: Text(context.l10n.audio_source),
value: preferences.audioSource, value: preferences.audioSource,
options: AudioSource.values options: AudioSource.values
.map((e) => DropdownMenuItem( .map((e) => SelectItemButton(
value: e, value: e,
child: Text(e.label), child: Text(e.label),
)) ))
@ -71,177 +70,173 @@ class SettingsPlaybackSection extends HookConsumerWidget {
preferencesNotifier.setAudioSource(value); preferencesNotifier.setAudioSource(value);
}, },
), ),
AnimatedSwitcher( AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: preferences.audioSource != AudioSource.piped crossFadeState: preferences.audioSource != AudioSource.piped
? const SizedBox.shrink() ? CrossFadeState.showFirst
: Consumer(builder: (context, ref, child) { : CrossFadeState.showSecond,
final instanceList = ref.watch(pipedInstancesFutureProvider); firstChild: const SizedBox.shrink(),
secondChild: Consumer(
builder: (context, ref, child) {
final instanceList = ref.watch(pipedInstancesFutureProvider);
return instanceList.when( return instanceList.when(
data: (data) { data: (data) {
return AdaptiveSelectTile<String>( return AdaptiveSelectTile<String>(
secondary: const Icon(SpotubeIcons.piped), secondary: const Icon(SpotubeIcons.piped),
title: Text(context.l10n.piped_instance), title: Text(context.l10n.piped_instance),
subtitle: RichText( subtitle: Text(
text: TextSpan( "${context.l10n.piped_description}\n"
children: [ "${context.l10n.piped_warning}",
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) {
preferencesNotifier.setPipedInstance(value);
}
},
);
},
loading: () => const Center(
child: CircularProgressIndicator(),
), ),
error: (error, stackTrace) => Text(error.toString()), value: preferences.pipedInstance,
); showValueWhenUnfolded: false,
}), options: data
), .sortedBy((e) => e.name)
AnimatedSwitcher( .map(
duration: const Duration(milliseconds: 300), (e) => SelectItemButton(
child: preferences.audioSource != AudioSource.invidious value: e.apiUrl,
? const SizedBox.shrink() child: RichText(
: Consumer(builder: (context, ref, child) { text: TextSpan(
final instanceList = ref.watch(invidiousInstancesProvider); style: theme.typography.normal.copyWith(
color: theme.colorScheme.foreground,
return instanceList.when(
data: (data) {
return AdaptiveSelectTile<String>(
secondary: const Icon(SpotubeIcons.piped),
title: Text(context.l10n.invidious_instance),
subtitle: RichText(
text: TextSpan(
children: [
TextSpan(
text: context.l10n.invidious_description,
style: theme.textTheme.bodyMedium,
),
const TextSpan(text: "\n"),
TextSpan(
text: context.l10n.invidious_warning,
style: theme.textTheme.labelMedium,
)
],
),
),
value: preferences.invidiousInstance,
showValueWhenUnfolded: false,
options: data
.sortedBy((e) => e.name)
.map(
(e) => DropdownMenuItem(
value: e.details.uri,
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: "${e.name.trim()}\n",
style: theme.textTheme.labelLarge,
),
TextSpan(
text: countryCodeToEmoji(
e.details.region,
),
style: GoogleFonts.notoColorEmoji(),
),
],
),
), ),
children: [
TextSpan(
text: "${e.name.trim()}\n",
),
TextSpan(
text: e.locations
.map(countryCodeToEmoji)
.join(""),
style: GoogleFonts.notoColorEmoji(),
),
],
), ),
) ),
.toList(), ),
onChanged: (value) { )
if (value != null) { .toList(),
preferencesNotifier.setInvidiousInstance(value); onChanged: (value) {
} if (value != null) {
}, preferencesNotifier.setPipedInstance(value);
); }
}, },
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (error, stackTrace) => Text(error.toString()),
); );
}), },
), loading: () => const Center(
AnimatedSwitcher( child: CircularProgressIndicator(),
duration: const Duration(milliseconds: 300),
child: preferences.audioSource != AudioSource.piped
? 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;
preferencesNotifier.setSearchMode(value);
},
), ),
error: (error, stackTrace) => Text(error.toString()),
);
},
),
), ),
AnimatedSwitcher( AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: preferences.searchMode == SearchMode.youtube && crossFadeState: preferences.audioSource != AudioSource.invidious
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: const SizedBox.shrink(),
secondChild: Consumer(
builder: (context, ref, child) {
final instanceList = ref.watch(invidiousInstancesProvider);
return instanceList.when(
data: (data) {
return AdaptiveSelectTile<String>(
secondary: const Icon(SpotubeIcons.piped),
title: Text(context.l10n.invidious_instance),
subtitle: Text(
"${context.l10n.invidious_description}\n"
"${context.l10n.invidious_warning}",
),
value: preferences.invidiousInstance,
showValueWhenUnfolded: false,
options: data
.sortedBy((e) => e.name)
.map(
(e) => SelectItemButton(
value: e.details.uri,
child: RichText(
text: TextSpan(
style: theme.typography.normal.copyWith(
color: theme.colorScheme.foreground,
),
children: [
TextSpan(
text: "${e.name.trim()}\n",
),
TextSpan(
text: countryCodeToEmoji(
e.details.region,
),
style: GoogleFonts.notoColorEmoji(),
),
],
),
),
),
)
.toList(),
onChanged: (value) {
if (value != null) {
preferencesNotifier.setInvidiousInstance(value);
}
},
);
},
loading: () => const Center(
child: CircularProgressIndicator(),
),
error: (error, stackTrace) => Text(error.toString()),
);
},
),
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState: preferences.audioSource != AudioSource.youtube
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: const SizedBox.shrink(),
secondChild: AdaptiveSelectTile<SearchMode>(
secondary: const Icon(SpotubeIcons.search),
title: Text(context.l10n.search_mode),
value: preferences.searchMode,
options: SearchMode.values
.map((e) => SelectItemButton(
value: e,
child: Text(e.label),
))
.toList(),
onChanged: (value) {
if (value == null) return;
preferencesNotifier.setSearchMode(value);
},
),
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState: preferences.searchMode == SearchMode.youtube &&
(preferences.audioSource == AudioSource.piped || (preferences.audioSource == AudioSource.piped ||
preferences.audioSource == AudioSource.youtube || preferences.audioSource == AudioSource.youtube ||
preferences.audioSource == AudioSource.invidious) preferences.audioSource == AudioSource.invidious)
? SwitchListTile( ? CrossFadeState.showFirst
secondary: const Icon(SpotubeIcons.skip), : CrossFadeState.showSecond,
title: Text(context.l10n.skip_non_music), firstChild: ListTile(
value: preferences.skipNonMusic, leading: const Icon(SpotubeIcons.skip),
onChanged: (state) { title: Text(context.l10n.skip_non_music),
preferencesNotifier.setSkipNonMusic(state); trailing: Switch(
}, value: preferences.skipNonMusic,
) onChanged: (state) {
: const SizedBox.shrink(), preferencesNotifier.setSkipNonMusic(state);
},
),
),
secondChild: const SizedBox.shrink(),
), ),
SwitchListTile( ListTile(
title: Text(context.l10n.cache_music), title: Text(context.l10n.cache_music),
subtitle: kIsMobile subtitle: kIsMobile
? null ? null
@ -253,7 +248,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
text: context.l10n.cache_folder.toLowerCase(), text: context.l10n.cache_folder.toLowerCase(),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = preferencesNotifier.openCacheFolder, ..onTap = preferencesNotifier.openCacheFolder,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.typography.normal.copyWith(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
), ),
@ -261,9 +256,11 @@ class SettingsPlaybackSection extends HookConsumerWidget {
], ],
), ),
), ),
secondary: const Icon(SpotubeIcons.cache), leading: const Icon(SpotubeIcons.cache),
value: preferences.cacheMusic, trailing: Switch(
onChanged: preferencesNotifier.setCacheMusic, value: preferences.cacheMusic,
onChanged: preferencesNotifier.setCacheMusic,
),
), ),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.playlistRemove), leading: const Icon(SpotubeIcons.playlistRemove),
@ -274,25 +271,26 @@ class SettingsPlaybackSection extends HookConsumerWidget {
}, },
trailing: const Icon(SpotubeIcons.angleRight), trailing: const Icon(SpotubeIcons.angleRight),
), ),
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.normalize), leading: const Icon(SpotubeIcons.normalize),
title: Text(context.l10n.normalize_audio), title: Text(context.l10n.normalize_audio),
value: preferences.normalizeAudio, trailing: Switch(
onChanged: preferencesNotifier.setNormalizeAudio, value: preferences.normalizeAudio,
onChanged: preferencesNotifier.setNormalizeAudio,
),
), ),
if (preferences.audioSource != AudioSource.jiosaavn) ...[ if (preferences.audioSource != AudioSource.jiosaavn) ...[
const Gap(5),
AdaptiveSelectTile<SourceCodecs>( AdaptiveSelectTile<SourceCodecs>(
secondary: const Icon(SpotubeIcons.stream), secondary: const Icon(SpotubeIcons.stream),
title: Text(context.l10n.streaming_music_codec), title: Text(context.l10n.streaming_music_codec),
value: preferences.streamMusicCodec, value: preferences.streamMusicCodec,
showValueWhenUnfolded: false, showValueWhenUnfolded: false,
options: SourceCodecs.values options: SourceCodecs.values
.map((e) => DropdownMenuItem( .map((e) => SelectItemButton(
value: e, value: e,
child: Text( child: Text(
e.label, e.label,
style: theme.textTheme.labelMedium, style: theme.typography.small,
), ),
)) ))
.toList(), .toList(),
@ -301,18 +299,17 @@ class SettingsPlaybackSection extends HookConsumerWidget {
preferencesNotifier.setStreamMusicCodec(value); preferencesNotifier.setStreamMusicCodec(value);
}, },
), ),
const Gap(5),
AdaptiveSelectTile<SourceCodecs>( AdaptiveSelectTile<SourceCodecs>(
secondary: const Icon(SpotubeIcons.file), secondary: const Icon(SpotubeIcons.file),
title: Text(context.l10n.download_music_codec), title: Text(context.l10n.download_music_codec),
value: preferences.downloadMusicCodec, value: preferences.downloadMusicCodec,
showValueWhenUnfolded: false, showValueWhenUnfolded: false,
options: SourceCodecs.values options: SourceCodecs.values
.map((e) => DropdownMenuItem( .map((e) => SelectItemButton(
value: e, value: e,
child: Text( child: Text(
e.label, e.label,
style: theme.textTheme.labelMedium, style: theme.typography.small,
), ),
)) ))
.toList(), .toList(),
@ -320,20 +317,23 @@ class SettingsPlaybackSection extends HookConsumerWidget {
if (value == null) return; if (value == null) return;
preferencesNotifier.setDownloadMusicCodec(value); preferencesNotifier.setDownloadMusicCodec(value);
}, },
) ),
], ],
SwitchListTile( ListTile(
secondary: const Icon(SpotubeIcons.repeat), leading: const Icon(SpotubeIcons.repeat),
title: Text(context.l10n.endless_playback), title: Text(context.l10n.endless_playback),
value: preferences.endlessPlayback, trailing: Switch(
onChanged: preferencesNotifier.setEndlessPlayback, value: preferences.endlessPlayback,
), onChanged: preferencesNotifier.setEndlessPlayback,
SwitchListTile( )),
ListTile(
title: Text(context.l10n.enable_connect), title: Text(context.l10n.enable_connect),
subtitle: Text(context.l10n.enable_connect_description), subtitle: Text(context.l10n.enable_connect_description),
secondary: const Icon(SpotubeIcons.connect), leading: const Icon(SpotubeIcons.connect),
value: preferences.enableConnect, trailing: Switch(
onChanged: preferencesNotifier.setEnableConnect, value: preferences.enableConnect,
onChanged: preferencesNotifier.setEnableConnect,
),
), ),
], ],
); );

View File

@ -1,7 +1,8 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart' show Material, MaterialType;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/settings/sections/about.dart'; import 'package:spotube/pages/settings/sections/about.dart';
@ -28,36 +29,41 @@ class SettingsPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: TitleBar( headers: [
title: Text(context.l10n.settings), TitleBar(
automaticallyImplyLeading: true, title: Text(context.l10n.settings),
), automaticallyImplyLeading: true,
body: Scrollbar( )
],
child: Scrollbar(
controller: controller, controller: controller,
child: Center( child: Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1366), constraints: const BoxConstraints(maxWidth: 1366),
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false), behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: ListView( child: Material(
controller: controller, type: MaterialType.transparency,
children: [ child: ListView(
const SettingsAccountSection(), controller: controller,
const SettingsLanguageRegionSection(), children: [
const SettingsAppearanceSection(), const SettingsAccountSection(),
const SettingsPlaybackSection(), const SettingsLanguageRegionSection(),
const SettingsDownloadsSection(), const SettingsAppearanceSection(),
if (kIsDesktop) const SettingsDesktopSection(), const SettingsPlaybackSection(),
if (!kIsWeb) const SettingsDevelopersSection(), const SettingsDownloadsSection(),
const SettingsAboutSection(), if (kIsDesktop) const SettingsDesktopSection(),
Center( if (!kIsWeb) const SettingsDevelopersSection(),
child: FilledButton( const SettingsAboutSection(),
onPressed: preferencesNotifier.reset, Center(
child: Text(context.l10n.restore_defaults), child: Button.destructive(
onPressed: preferencesNotifier.reset,
child: Text(context.l10n.restore_defaults),
),
), ),
), const SizedBox(height: 200),
const SizedBox(height: 10), ],
], ),
), ),
), ),
), ),

View File

@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return false return false
} }
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
} }

View File

@ -1336,10 +1336,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: invidious name: invidious
sha256: "7cb879c0b4b99aa06ec720af84f6988ff0080bb0434d041f6fb0c4add680ee36" sha256: "27ef3a001df875665de15535dbc9099f44d12a59480018fb1e17377d4af0308d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.0" version: "0.1.1"
io: io:
dependency: "direct dev" dependency: "direct dev"
description: description:

View File

@ -78,7 +78,7 @@ dependencies:
http: ^1.2.1 http: ^1.2.1
image_picker: ^1.1.0 image_picker: ^1.1.0
intl: any intl: any
invidious: ^0.1.0 invidious: ^0.1.1
jiosaavn: ^0.1.0 jiosaavn: ^0.1.0
json_annotation: ^4.8.1 json_annotation: ^4.8.1
local_notifier: ^0.1.6 local_notifier: ^0.1.6