mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: settings using shadcn components
This commit is contained in:
parent
7ee071f2e3
commit
2fefd65f51
@ -128,4 +128,6 @@ abstract class SpotubeIcons {
|
||||
static const export = Icons.file_open_outlined;
|
||||
static const delete = FeatherIcons.trash2;
|
||||
static const open = FeatherIcons.externalLink;
|
||||
static const radioChecked = Icons.radio_button_on_rounded;
|
||||
static const radioUnchecked = Icons.radio_button_off_rounded;
|
||||
}
|
||||
|
@ -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:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
|
||||
@ -11,7 +12,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
final T value;
|
||||
final ValueChanged<T?>? onChanged;
|
||||
|
||||
final List<DropdownMenuItem<T>> options;
|
||||
final List<SelectItemButton<T>> options;
|
||||
|
||||
/// Show the smaller value when the breakpoint is reached
|
||||
///
|
||||
@ -39,55 +40,25 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.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,
|
||||
|
||||
Widget? control = Select<T>(
|
||||
itemBuilder: (context, item) {
|
||||
return options.firstWhere((element) => element.value == item).child;
|
||||
},
|
||||
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,
|
||||
),
|
||||
children: options,
|
||||
);
|
||||
final controlPlaceholder = useMemoized(
|
||||
() => options
|
||||
.firstWhere(
|
||||
(element) => element.value == value,
|
||||
orElse: () => DropdownMenuItem<T>(
|
||||
value: null,
|
||||
child: Container(),
|
||||
),
|
||||
)
|
||||
.child,
|
||||
[value, options]);
|
||||
|
||||
final control = breakLayout ?? mediaQuery.mdAndUp
|
||||
? rawControl
|
||||
: showValueWhenUnfolded
|
||||
? Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.primary,
|
||||
width: 2,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
child: controlPlaceholder,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
if (mediaQuery.smAndDown) {
|
||||
if (showValueWhenUnfolded) {
|
||||
control = OutlineBadge(
|
||||
child: options.firstWhere((element) => element.value == value).child,
|
||||
);
|
||||
} else {
|
||||
control = null;
|
||||
}
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: title,
|
||||
@ -104,20 +75,26 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: title,
|
||||
children: [
|
||||
for (final option in options)
|
||||
RadioListTile<T>(
|
||||
title: option.child,
|
||||
value: option.value as T,
|
||||
groupValue: value,
|
||||
onChanged: (v) {
|
||||
Navigator.pop(context);
|
||||
onChanged?.call(v);
|
||||
return AlertDialog(
|
||||
content: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = options[index];
|
||||
|
||||
return ListTile(
|
||||
iconColor: theme.colorScheme.primary,
|
||||
leading: item.value == value
|
||||
? const Icon(SpotubeIcons.radioChecked)
|
||||
: const Icon(SpotubeIcons.radioUnchecked),
|
||||
title: item.child,
|
||||
onTap: () {
|
||||
onChanged?.call(item.value);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ class BackButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton.ghost(
|
||||
size: const ButtonSize(.9),
|
||||
icon: const Icon(SpotubeIcons.angleLeft),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/string.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class PlaybuttonCard extends HookWidget {
|
||||
final void Function()? onTap;
|
||||
@ -55,10 +56,15 @@ class PlaybuttonCard extends HookWidget {
|
||||
AnimatedScale(
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
scale: states.contains(WidgetState.hovered) ? 1 : 0.7,
|
||||
scale: states.contains(WidgetState.hovered) || kIsMobile
|
||||
? 1
|
||||
: 0.7,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
opacity: states.contains(WidgetState.hovered) ? 1 : 0,
|
||||
opacity:
|
||||
states.contains(WidgetState.hovered) || kIsMobile
|
||||
? 1
|
||||
: 0,
|
||||
child: IconButton.secondary(
|
||||
icon: const Icon(SpotubeIcons.queueAdd),
|
||||
onPressed: onAddToQueuePressed,
|
||||
@ -70,10 +76,15 @@ class PlaybuttonCard extends HookWidget {
|
||||
AnimatedScale(
|
||||
curve: Curves.easeOutBack,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
scale: states.contains(WidgetState.hovered) ? 1 : 0.7,
|
||||
scale: states.contains(WidgetState.hovered) || kIsMobile
|
||||
? 1
|
||||
: 0.7,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
opacity: states.contains(WidgetState.hovered) ? 1 : 0,
|
||||
opacity:
|
||||
states.contains(WidgetState.hovered) || kIsMobile
|
||||
? 1
|
||||
: 0,
|
||||
child: IconButton.secondary(
|
||||
icon: const Icon(SpotubeIcons.play),
|
||||
onPressed: onPlaybuttonPressed,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart' hide AppBar;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart'
|
||||
show AppBar, WidgetExtension;
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/components/button/back_button.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar_buttons.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
@ -237,7 +237,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
child: IconButton.secondary(
|
||||
icon: const Icon(SpotubeIcons.open),
|
||||
icon: const Icon(SpotubeIcons.angleDown),
|
||||
onPressed: () {
|
||||
controller.scrollToIndex(
|
||||
playlist.playlist.index,
|
||||
|
@ -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 {
|
||||
final String heading;
|
||||
@ -11,7 +13,21 @@ class SectionCardWithHeading extends StatelessWidget {
|
||||
|
||||
@override
|
||||
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,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -19,19 +35,19 @@ class SectionCardWithHeading extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
heading,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.theme.typography.large,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: children,
|
||||
).gap(8.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -145,13 +145,22 @@ class SearchPage extends HookConsumerWidget {
|
||||
leading: const Icon(SpotubeIcons.search),
|
||||
textInputAction: TextInputAction.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,
|
||||
icon: const Icon(SpotubeIcons.close),
|
||||
onPressed: () {
|
||||
controller.clear();
|
||||
},
|
||||
),
|
||||
secondChild:
|
||||
const SizedBox.square(dimension: 28),
|
||||
),
|
||||
onAcceptSuggestion: (index) {
|
||||
controller.text =
|
||||
KVStoreService.recentSearches[index];
|
||||
|
@ -1,7 +1,9 @@
|
||||
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: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/spotube_icons.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
@ -45,9 +47,13 @@ class SettingsAboutSection extends HookConsumerWidget {
|
||||
trailing: (context, update) => FilledButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(Colors.red[100]),
|
||||
foregroundColor:
|
||||
const WidgetStatePropertyAll(Colors.pinkAccent),
|
||||
foregroundColor: const WidgetStatePropertyAll(Colors.pink),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
|
||||
shape: WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: context.theme.borderRadiusLg,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
launchUrlString(
|
||||
@ -66,11 +72,14 @@ class SettingsAboutSection extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
if (Env.enableUpdateChecker)
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.update),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.update),
|
||||
title: Text(context.l10n.check_for_updates),
|
||||
trailing: Switch(
|
||||
value: preferences.checkUpdate,
|
||||
onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked),
|
||||
onChanged: (checked) =>
|
||||
preferencesNotifier.setCheckUpdate(checked),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.info),
|
||||
|
@ -1,7 +1,8 @@
|
||||
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:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/components/image/universal_image.dart';
|
||||
@ -28,11 +29,6 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
final me = ref.watch(meProvider);
|
||||
final meData = me.asData?.value;
|
||||
|
||||
final logoutBtnStyle = FilledButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
|
||||
final onLogin = useLoginCallback(ref);
|
||||
|
||||
return SectionCardWithHeading(
|
||||
@ -44,8 +40,9 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.user_profile),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CircleAvatar(
|
||||
backgroundImage: UniversalImage.imageProvider(
|
||||
child: Avatar(
|
||||
initials: Avatar.getInitials(meData?.displayName ?? "User"),
|
||||
provider: UniversalImage.imageProvider(
|
||||
(meData?.images).asUrlString(
|
||||
placeholder: ImagePlaceholder.artist,
|
||||
),
|
||||
@ -76,15 +73,8 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
onTap: constrains.mdAndUp ? null : onLogin,
|
||||
trailing: constrains.smAndDown
|
||||
? null
|
||||
: FilledButton(
|
||||
: Button.primary(
|
||||
onPressed: onLogin,
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(25.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.connect_with_spotify.toUpperCase(),
|
||||
),
|
||||
@ -106,8 +96,7 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: FilledButton(
|
||||
style: logoutBtnStyle,
|
||||
trailing: Button.destructive(
|
||||
onPressed: () async {
|
||||
ref.read(authenticationProvider.notifier).logout();
|
||||
GoRouter.of(context).pop();
|
||||
@ -121,27 +110,22 @@ class SettingsAccountSection extends HookConsumerWidget {
|
||||
leading: const Icon(SpotubeIcons.lastFm),
|
||||
title: Text(context.l10n.login_with_lastfm),
|
||||
subtitle: Text(context.l10n.scrobble_to_lastfm),
|
||||
trailing: FilledButton.icon(
|
||||
icon: const Icon(SpotubeIcons.lastFm),
|
||||
label: Text(context.l10n.connect),
|
||||
trailing: Button.secondary(
|
||||
leading: const Icon(SpotubeIcons.lastFm),
|
||||
onPressed: () {
|
||||
router.push("/lastfm-login");
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color.fromARGB(255, 186, 0, 0),
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(context.l10n.connect),
|
||||
),
|
||||
)
|
||||
else
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.lastFm),
|
||||
title: Text(context.l10n.disconnect_lastfm),
|
||||
trailing: FilledButton(
|
||||
trailing: Button.destructive(
|
||||
onPressed: () {
|
||||
ref.read(scrobblerProvider.notifier).logout();
|
||||
},
|
||||
style: logoutBtnStyle,
|
||||
child: Text(context.l10n.disconnect),
|
||||
),
|
||||
),
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart' hide ThemeMode;
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' show ThemeMode;
|
||||
import 'package:flutter/material.dart' show ListTile;
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
@ -42,15 +41,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
options: [
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: LayoutMode.adaptive,
|
||||
child: Text(context.l10n.adaptive),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: LayoutMode.compact,
|
||||
child: Text(context.l10n.compact),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: LayoutMode.extended,
|
||||
child: Text(context.l10n.extended),
|
||||
),
|
||||
@ -61,15 +60,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.theme),
|
||||
value: preferences.themeMode,
|
||||
options: [
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: ThemeMode.dark,
|
||||
child: Text(context.l10n.dark),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: ThemeMode.light,
|
||||
child: Text(context.l10n.light),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: ThemeMode.system,
|
||||
child: Text(context.l10n.system),
|
||||
),
|
||||
@ -80,13 +79,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.amoled),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.amoled),
|
||||
title: Text(context.l10n.use_amoled_mode),
|
||||
subtitle: Text(context.l10n.pitch_dark_theme),
|
||||
trailing: Switch(
|
||||
value: preferences.amoledDarkTheme,
|
||||
onChanged: preferencesNotifier.setAmoledDarkTheme,
|
||||
),
|
||||
)),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.palette),
|
||||
title: Text(context.l10n.accent_color),
|
||||
@ -101,13 +101,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
),
|
||||
onTap: pickColorScheme(),
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.colorSync),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.colorSync),
|
||||
title: Text(context.l10n.sync_album_color),
|
||||
subtitle: Text(context.l10n.sync_album_color_description),
|
||||
trailing: Switch(
|
||||
value: preferences.albumColorSync,
|
||||
onChanged: preferencesNotifier.setAlbumColorSync,
|
||||
),
|
||||
)),
|
||||
];
|
||||
|
||||
if (isGettingStarted) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:flutter/material.dart' show ListTile;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/models/database/database.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),
|
||||
value: preferences.closeBehavior,
|
||||
options: [
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: CloseBehavior.close,
|
||||
child: Text(context.l10n.close),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: CloseBehavior.minimizeToTray,
|
||||
child: Text(context.l10n.minimize_to_tray),
|
||||
),
|
||||
@ -40,24 +40,30 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.tray),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.tray),
|
||||
title: Text(context.l10n.show_tray_icon),
|
||||
trailing: Switch(
|
||||
value: preferences.showSystemTrayIcon,
|
||||
onChanged: preferencesNotifier.setShowSystemTrayIcon,
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.window),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.window),
|
||||
title: Text(context.l10n.use_system_title_bar),
|
||||
trailing: Switch(
|
||||
value: preferences.systemTitleBar,
|
||||
onChanged: preferencesNotifier.setSystemTitleBar,
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.discord),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.discord),
|
||||
title: Text(context.l10n.discord_rich_presence),
|
||||
trailing: Switch(
|
||||
value: preferences.discordPresence,
|
||||
onChanged: preferencesNotifier.setDiscordPresence,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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:go_router/go_router.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
|
@ -1,8 +1,9 @@
|
||||
import 'package:file_picker/file_picker.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:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
@ -40,9 +41,9 @@ class SettingsDownloadsSection extends HookConsumerWidget {
|
||||
leading: const Icon(SpotubeIcons.download),
|
||||
title: Text(context.l10n.download_location),
|
||||
subtitle: Text(preferences.downloadLocation),
|
||||
trailing: FilledButton(
|
||||
trailing: IconButton.secondary(
|
||||
onPressed: pickDownloadLocation,
|
||||
child: const Icon(SpotubeIcons.folder),
|
||||
icon: const Icon(SpotubeIcons.folder),
|
||||
),
|
||||
onTap: pickDownloadLocation,
|
||||
),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/language_codes.dart';
|
||||
import 'package:spotube/collections/spotify_markets.dart';
|
||||
@ -24,7 +23,6 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
return SectionCardWithHeading(
|
||||
heading: context.l10n.language_region,
|
||||
children: [
|
||||
const Gap(10),
|
||||
AdaptiveSelectTile<Locale>(
|
||||
value: preferences.locale,
|
||||
onChanged: (locale) {
|
||||
@ -34,12 +32,12 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.language),
|
||||
secondary: const Icon(SpotubeIcons.language),
|
||||
options: [
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: const Locale("system", "system"),
|
||||
child: Text(context.l10n.system_default),
|
||||
),
|
||||
for (final locale in L10n.all)
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: locale,
|
||||
child: Builder(builder: (context) {
|
||||
final isoCodeName = LanguageLocals.getDisplayLanguage(
|
||||
@ -64,7 +62,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
},
|
||||
options: spotifyMarkets
|
||||
.map(
|
||||
(country) => DropdownMenuItem(
|
||||
(country) => SelectItemButton(
|
||||
value: country.$1,
|
||||
child: Text(country.$2),
|
||||
),
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:flutter/material.dart' show ListTile;
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:piped_client/piped_client.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||
@ -30,21 +31,20 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
return SectionCardWithHeading(
|
||||
heading: context.l10n.playback,
|
||||
children: [
|
||||
const Gap(10),
|
||||
AdaptiveSelectTile<SourceQualities>(
|
||||
secondary: const Icon(SpotubeIcons.audioQuality),
|
||||
title: Text(context.l10n.audio_quality),
|
||||
value: preferences.audioQuality,
|
||||
options: [
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: SourceQualities.high,
|
||||
child: Text(context.l10n.high),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: SourceQualities.medium,
|
||||
child: Text(context.l10n.medium),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
SelectItemButton(
|
||||
value: SourceQualities.low,
|
||||
child: Text(context.l10n.low),
|
||||
),
|
||||
@ -55,13 +55,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
const Gap(5),
|
||||
AdaptiveSelectTile<AudioSource>(
|
||||
secondary: const Icon(SpotubeIcons.api),
|
||||
title: Text(context.l10n.audio_source),
|
||||
value: preferences.audioSource,
|
||||
options: AudioSource.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
.map((e) => SelectItemButton(
|
||||
value: e,
|
||||
child: Text(e.label),
|
||||
))
|
||||
@ -71,11 +70,14 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
preferencesNotifier.setAudioSource(value);
|
||||
},
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.audioSource != AudioSource.piped
|
||||
? const SizedBox.shrink()
|
||||
: Consumer(builder: (context, ref, child) {
|
||||
crossFadeState: preferences.audioSource != AudioSource.piped
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: const SizedBox.shrink(),
|
||||
secondChild: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final instanceList = ref.watch(pipedInstancesFutureProvider);
|
||||
|
||||
return instanceList.when(
|
||||
@ -83,34 +85,25 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
return AdaptiveSelectTile<String>(
|
||||
secondary: const Icon(SpotubeIcons.piped),
|
||||
title: Text(context.l10n.piped_instance),
|
||||
subtitle: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: context.l10n.piped_description,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const TextSpan(text: "\n"),
|
||||
TextSpan(
|
||||
text: context.l10n.piped_warning,
|
||||
style: theme.textTheme.labelMedium,
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
"${context.l10n.piped_description}\n"
|
||||
"${context.l10n.piped_warning}",
|
||||
),
|
||||
value: preferences.pipedInstance,
|
||||
showValueWhenUnfolded: false,
|
||||
options: data
|
||||
.sortedBy((e) => e.name)
|
||||
.map(
|
||||
(e) => DropdownMenuItem(
|
||||
(e) => SelectItemButton(
|
||||
value: e.apiUrl,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: theme.typography.normal.copyWith(
|
||||
color: theme.colorScheme.foreground,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "${e.name.trim()}\n",
|
||||
style: theme.textTheme.labelLarge,
|
||||
),
|
||||
TextSpan(
|
||||
text: e.locations
|
||||
@ -136,13 +129,17 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
error: (error, stackTrace) => Text(error.toString()),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
),
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.audioSource != AudioSource.invidious
|
||||
? const SizedBox.shrink()
|
||||
: Consumer(builder: (context, ref, child) {
|
||||
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(
|
||||
@ -150,34 +147,25 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
"${context.l10n.invidious_description}\n"
|
||||
"${context.l10n.invidious_warning}",
|
||||
),
|
||||
value: preferences.invidiousInstance,
|
||||
showValueWhenUnfolded: false,
|
||||
options: data
|
||||
.sortedBy((e) => e.name)
|
||||
.map(
|
||||
(e) => DropdownMenuItem(
|
||||
(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",
|
||||
style: theme.textTheme.labelLarge,
|
||||
),
|
||||
TextSpan(
|
||||
text: countryCodeToEmoji(
|
||||
@ -203,18 +191,21 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
),
|
||||
error: (error, stackTrace) => Text(error.toString()),
|
||||
);
|
||||
}),
|
||||
},
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
),
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.audioSource != AudioSource.piped
|
||||
? const SizedBox.shrink()
|
||||
: AdaptiveSelectTile<SearchMode>(
|
||||
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) => DropdownMenuItem(
|
||||
.map((e) => SelectItemButton(
|
||||
value: e,
|
||||
child: Text(e.label),
|
||||
))
|
||||
@ -225,23 +216,27 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: preferences.searchMode == SearchMode.youtube &&
|
||||
crossFadeState: preferences.searchMode == SearchMode.youtube &&
|
||||
(preferences.audioSource == AudioSource.piped ||
|
||||
preferences.audioSource == AudioSource.youtube ||
|
||||
preferences.audioSource == AudioSource.invidious)
|
||||
? SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.skip),
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: ListTile(
|
||||
leading: const Icon(SpotubeIcons.skip),
|
||||
title: Text(context.l10n.skip_non_music),
|
||||
trailing: Switch(
|
||||
value: preferences.skipNonMusic,
|
||||
onChanged: (state) {
|
||||
preferencesNotifier.setSkipNonMusic(state);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
SwitchListTile(
|
||||
),
|
||||
secondChild: const SizedBox.shrink(),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.l10n.cache_music),
|
||||
subtitle: kIsMobile
|
||||
? null
|
||||
@ -253,7 +248,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
text: context.l10n.cache_folder.toLowerCase(),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = preferencesNotifier.openCacheFolder,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
style: theme.typography.normal.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
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,
|
||||
onChanged: preferencesNotifier.setCacheMusic,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||
title: Text(context.l10n.blacklist),
|
||||
@ -274,25 +271,26 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
},
|
||||
trailing: const Icon(SpotubeIcons.angleRight),
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.normalize),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.normalize),
|
||||
title: Text(context.l10n.normalize_audio),
|
||||
trailing: Switch(
|
||||
value: preferences.normalizeAudio,
|
||||
onChanged: preferencesNotifier.setNormalizeAudio,
|
||||
),
|
||||
),
|
||||
if (preferences.audioSource != AudioSource.jiosaavn) ...[
|
||||
const Gap(5),
|
||||
AdaptiveSelectTile<SourceCodecs>(
|
||||
secondary: const Icon(SpotubeIcons.stream),
|
||||
title: Text(context.l10n.streaming_music_codec),
|
||||
value: preferences.streamMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: SourceCodecs.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
.map((e) => SelectItemButton(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
style: theme.typography.small,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
@ -301,18 +299,17 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
preferencesNotifier.setStreamMusicCodec(value);
|
||||
},
|
||||
),
|
||||
const Gap(5),
|
||||
AdaptiveSelectTile<SourceCodecs>(
|
||||
secondary: const Icon(SpotubeIcons.file),
|
||||
title: Text(context.l10n.download_music_codec),
|
||||
value: preferences.downloadMusicCodec,
|
||||
showValueWhenUnfolded: false,
|
||||
options: SourceCodecs.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
.map((e) => SelectItemButton(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.label,
|
||||
style: theme.textTheme.labelMedium,
|
||||
style: theme.typography.small,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
@ -320,21 +317,24 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
if (value == null) return;
|
||||
preferencesNotifier.setDownloadMusicCodec(value);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.repeat),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.repeat),
|
||||
title: Text(context.l10n.endless_playback),
|
||||
trailing: Switch(
|
||||
value: preferences.endlessPlayback,
|
||||
onChanged: preferencesNotifier.setEndlessPlayback,
|
||||
),
|
||||
SwitchListTile(
|
||||
)),
|
||||
ListTile(
|
||||
title: Text(context.l10n.enable_connect),
|
||||
subtitle: Text(context.l10n.enable_connect_description),
|
||||
secondary: const Icon(SpotubeIcons.connect),
|
||||
leading: const Icon(SpotubeIcons.connect),
|
||||
trailing: Switch(
|
||||
value: preferences.enableConnect,
|
||||
onChanged: preferencesNotifier.setEnableConnect,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
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:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/pages/settings/sections/about.dart';
|
||||
@ -28,17 +29,21 @@ class SettingsPage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
appBar: TitleBar(
|
||||
headers: [
|
||||
TitleBar(
|
||||
title: Text(context.l10n.settings),
|
||||
automaticallyImplyLeading: true,
|
||||
),
|
||||
body: Scrollbar(
|
||||
)
|
||||
],
|
||||
child: Scrollbar(
|
||||
controller: controller,
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 1366),
|
||||
child: ScrollConfiguration(
|
||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
@ -51,12 +56,12 @@ class SettingsPage extends HookConsumerWidget {
|
||||
if (!kIsWeb) const SettingsDevelopersSection(),
|
||||
const SettingsAboutSection(),
|
||||
Center(
|
||||
child: FilledButton(
|
||||
child: Button.destructive(
|
||||
onPressed: preferencesNotifier.reset,
|
||||
child: Text(context.l10n.restore_defaults),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 200),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -64,6 +69,7 @@ class SettingsPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -1336,10 +1336,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: invidious
|
||||
sha256: "7cb879c0b4b99aa06ec720af84f6988ff0080bb0434d041f6fb0c4add680ee36"
|
||||
sha256: "27ef3a001df875665de15535dbc9099f44d12a59480018fb1e17377d4af0308d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
version: "0.1.1"
|
||||
io:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -78,7 +78,7 @@ dependencies:
|
||||
http: ^1.2.1
|
||||
image_picker: ^1.1.0
|
||||
intl: any
|
||||
invidious: ^0.1.0
|
||||
invidious: ^0.1.1
|
||||
jiosaavn: ^0.1.0
|
||||
json_annotation: ^4.8.1
|
||||
local_notifier: ^0.1.6
|
||||
|
Loading…
Reference in New Issue
Block a user