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( Widget? control = Select<T>(
color: theme.colorScheme.secondaryContainer, itemBuilder: (context, item) {
borderRadius: BorderRadius.circular(10), return options.firstWhere((element) => element.value == item).child;
), },
child: DropdownButton<T>(
items: options,
value: value, value: value,
onChanged: onChanged, onChanged: onChanged,
menuMaxHeight: mediaQuery.size.height * 0.6, children: options,
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 if (mediaQuery.smAndDown) {
? rawControl if (showValueWhenUnfolded) {
: showValueWhenUnfolded control = OutlineBadge(
? Container( child: options.firstWhere((element) => element.value == value).child,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), );
decoration: BoxDecoration( } else {
border: Border.all( control = null;
color: theme.colorScheme.primary, }
width: 2, }
),
borderRadius: BorderRadius.circular(10),
),
child: DefaultTextStyle(
style: TextStyle(
color: theme.colorScheme.primary,
),
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,7 +13,21 @@ class SectionCardWithHeading extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return ListTileTheme(
data: ListTileThemeData(
shape: RoundedRectangleBorder(
borderRadius: context.theme.borderRadiusLg,
side: BorderSide(
color: context.theme.colorScheme.border,
width: .5,
),
),
textColor: context.theme.colorScheme.foreground,
iconColor: context.theme.colorScheme.foreground,
selectedColor: context.theme.colorScheme.accent,
subtitleTextStyle: context.theme.typography.xSmall,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -19,19 +35,19 @@ class SectionCardWithHeading extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text( child: Text(
heading, heading,
style: Theme.of(context).textTheme.titleLarge?.copyWith( style: context.theme.typography.large,
fontWeight: FontWeight.w600,
),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Card( child: Column(
clipBehavior: Clip.antiAliasWithSaveLayer, mainAxisSize: MainAxisSize.min,
child: Column(mainAxisSize: MainAxisSize.min, children: children), crossAxisAlignment: CrossAxisAlignment.stretch,
), children: children,
).gap(8.0),
), ),
], ],
),
); );
} }
} }

View File

@ -145,13 +145,22 @@ 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(
duration:
const Duration(milliseconds: 300),
crossFadeState: controller.text.isNotEmpty
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: IconButton.ghost(
size: ButtonSize.small, size: ButtonSize.small,
icon: const Icon(SpotubeIcons.close), icon: const Icon(SpotubeIcons.close),
onPressed: () { onPressed: () {
controller.clear(); controller.clear();
}, },
), ),
secondChild:
const SizedBox.square(dimension: 28),
),
onAcceptSuggestion: (index) { onAcceptSuggestion: (index) {
controller.text = controller.text =
KVStoreService.recentSearches[index]; KVStoreService.recentSearches[index];

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),
trailing: Switch(
value: preferences.checkUpdate, value: preferences.checkUpdate,
onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked), 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),
trailing: Switch(
value: preferences.amoledDarkTheme, value: preferences.amoledDarkTheme,
onChanged: preferencesNotifier.setAmoledDarkTheme, 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),
trailing: Switch(
value: preferences.albumColorSync, value: preferences.albumColorSync,
onChanged: preferencesNotifier.setAlbumColorSync, 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,24 +40,30 @@ 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),
trailing: Switch(
value: preferences.showSystemTrayIcon, value: preferences.showSystemTrayIcon,
onChanged: preferencesNotifier.setShowSystemTrayIcon, onChanged: preferencesNotifier.setShowSystemTrayIcon,
), ),
SwitchListTile( ),
secondary: const Icon(SpotubeIcons.window), ListTile(
leading: const Icon(SpotubeIcons.window),
title: Text(context.l10n.use_system_title_bar), title: Text(context.l10n.use_system_title_bar),
trailing: Switch(
value: preferences.systemTitleBar, value: preferences.systemTitleBar,
onChanged: preferencesNotifier.setSystemTitleBar, onChanged: preferencesNotifier.setSystemTitleBar,
), ),
SwitchListTile( ),
secondary: const Icon(SpotubeIcons.discord), ListTile(
leading: const Icon(SpotubeIcons.discord),
title: Text(context.l10n.discord_rich_presence), title: Text(context.l10n.discord_rich_presence),
trailing: Switch(
value: preferences.discordPresence, value: preferences.discordPresence,
onChanged: preferencesNotifier.setDiscordPresence, 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,11 +70,14 @@ 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,
firstChild: const SizedBox.shrink(),
secondChild: Consumer(
builder: (context, ref, child) {
final instanceList = ref.watch(pipedInstancesFutureProvider); final instanceList = ref.watch(pipedInstancesFutureProvider);
return instanceList.when( return instanceList.when(
@ -83,34 +85,25 @@ class SettingsPlaybackSection extends HookConsumerWidget {
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, value: preferences.pipedInstance,
showValueWhenUnfolded: false, showValueWhenUnfolded: false,
options: data options: data
.sortedBy((e) => e.name) .sortedBy((e) => e.name)
.map( .map(
(e) => DropdownMenuItem( (e) => SelectItemButton(
value: e.apiUrl, value: e.apiUrl,
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
style: theme.typography.normal.copyWith(
color: theme.colorScheme.foreground,
),
children: [ children: [
TextSpan( TextSpan(
text: "${e.name.trim()}\n", text: "${e.name.trim()}\n",
style: theme.textTheme.labelLarge,
), ),
TextSpan( TextSpan(
text: e.locations text: e.locations
@ -136,13 +129,17 @@ class SettingsPlaybackSection extends HookConsumerWidget {
), ),
error: (error, stackTrace) => Text(error.toString()), error: (error, stackTrace) => Text(error.toString()),
); );
}), },
), ),
AnimatedSwitcher( ),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: preferences.audioSource != AudioSource.invidious crossFadeState: preferences.audioSource != AudioSource.invidious
? const SizedBox.shrink() ? CrossFadeState.showFirst
: Consumer(builder: (context, ref, child) { : CrossFadeState.showSecond,
firstChild: const SizedBox.shrink(),
secondChild: Consumer(
builder: (context, ref, child) {
final instanceList = ref.watch(invidiousInstancesProvider); final instanceList = ref.watch(invidiousInstancesProvider);
return instanceList.when( return instanceList.when(
@ -150,34 +147,25 @@ class SettingsPlaybackSection extends HookConsumerWidget {
return AdaptiveSelectTile<String>( return AdaptiveSelectTile<String>(
secondary: const Icon(SpotubeIcons.piped), secondary: const Icon(SpotubeIcons.piped),
title: Text(context.l10n.invidious_instance), title: Text(context.l10n.invidious_instance),
subtitle: RichText( subtitle: Text(
text: TextSpan( "${context.l10n.invidious_description}\n"
children: [ "${context.l10n.invidious_warning}",
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, value: preferences.invidiousInstance,
showValueWhenUnfolded: false, showValueWhenUnfolded: false,
options: data options: data
.sortedBy((e) => e.name) .sortedBy((e) => e.name)
.map( .map(
(e) => DropdownMenuItem( (e) => SelectItemButton(
value: e.details.uri, value: e.details.uri,
child: RichText( child: RichText(
text: TextSpan( text: TextSpan(
style: theme.typography.normal.copyWith(
color: theme.colorScheme.foreground,
),
children: [ children: [
TextSpan( TextSpan(
text: "${e.name.trim()}\n", text: "${e.name.trim()}\n",
style: theme.textTheme.labelLarge,
), ),
TextSpan( TextSpan(
text: countryCodeToEmoji( text: countryCodeToEmoji(
@ -203,18 +191,21 @@ class SettingsPlaybackSection extends HookConsumerWidget {
), ),
error: (error, stackTrace) => Text(error.toString()), error: (error, stackTrace) => Text(error.toString()),
); );
}), },
), ),
AnimatedSwitcher( ),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: preferences.audioSource != AudioSource.piped crossFadeState: preferences.audioSource != AudioSource.youtube
? const SizedBox.shrink() ? CrossFadeState.showFirst
: AdaptiveSelectTile<SearchMode>( : CrossFadeState.showSecond,
firstChild: const SizedBox.shrink(),
secondChild: AdaptiveSelectTile<SearchMode>(
secondary: const Icon(SpotubeIcons.search), secondary: const Icon(SpotubeIcons.search),
title: Text(context.l10n.search_mode), title: Text(context.l10n.search_mode),
value: preferences.searchMode, value: preferences.searchMode,
options: SearchMode.values options: SearchMode.values
.map((e) => DropdownMenuItem( .map((e) => SelectItemButton(
value: e, value: e,
child: Text(e.label), child: Text(e.label),
)) ))
@ -225,23 +216,27 @@ class SettingsPlaybackSection extends HookConsumerWidget {
}, },
), ),
), ),
AnimatedSwitcher( AnimatedCrossFade(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: preferences.searchMode == SearchMode.youtube && 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,
firstChild: ListTile(
leading: const Icon(SpotubeIcons.skip),
title: Text(context.l10n.skip_non_music), title: Text(context.l10n.skip_non_music),
trailing: Switch(
value: preferences.skipNonMusic, value: preferences.skipNonMusic,
onChanged: (state) { onChanged: (state) {
preferencesNotifier.setSkipNonMusic(state); preferencesNotifier.setSkipNonMusic(state);
}, },
)
: const SizedBox.shrink(),
), ),
SwitchListTile( ),
secondChild: const SizedBox.shrink(),
),
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,10 +256,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
], ],
), ),
), ),
secondary: const Icon(SpotubeIcons.cache), leading: const Icon(SpotubeIcons.cache),
trailing: Switch(
value: preferences.cacheMusic, value: preferences.cacheMusic,
onChanged: preferencesNotifier.setCacheMusic, onChanged: preferencesNotifier.setCacheMusic,
), ),
),
ListTile( ListTile(
leading: const Icon(SpotubeIcons.playlistRemove), leading: const Icon(SpotubeIcons.playlistRemove),
title: Text(context.l10n.blacklist), title: Text(context.l10n.blacklist),
@ -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),
trailing: Switch(
value: preferences.normalizeAudio, value: preferences.normalizeAudio,
onChanged: preferencesNotifier.setNormalizeAudio, 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,21 +317,24 @@ 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),
trailing: Switch(
value: preferences.endlessPlayback, value: preferences.endlessPlayback,
onChanged: preferencesNotifier.setEndlessPlayback, 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),
trailing: Switch(
value: preferences.enableConnect, value: preferences.enableConnect,
onChanged: preferencesNotifier.setEnableConnect, 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,17 +29,21 @@ class SettingsPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: TitleBar( headers: [
TitleBar(
title: Text(context.l10n.settings), title: Text(context.l10n.settings),
automaticallyImplyLeading: true, 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: Material(
type: MaterialType.transparency,
child: ListView( child: ListView(
controller: controller, controller: controller,
children: [ children: [
@ -51,12 +56,12 @@ class SettingsPage extends HookConsumerWidget {
if (!kIsWeb) const SettingsDevelopersSection(), if (!kIsWeb) const SettingsDevelopersSection(),
const SettingsAboutSection(), const SettingsAboutSection(),
Center( Center(
child: FilledButton( child: Button.destructive(
onPressed: preferencesNotifier.reset, onPressed: preferencesNotifier.reset,
child: Text(context.l10n.restore_defaults), child: Text(context.l10n.restore_defaults),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 200),
], ],
), ),
), ),
@ -64,6 +69,7 @@ class SettingsPage extends HookConsumerWidget {
), ),
), ),
), ),
),
); );
} }
} }

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