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 delete = FeatherIcons.trash2;
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: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();
},
);
},
),
],
);
},
);

View File

@ -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(),
);

View File

@ -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,

View File

@ -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';

View File

@ -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,

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 {
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),
),
],
),
);
}
}

View File

@ -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];

View File

@ -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),

View File

@ -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),
),
),

View File

@ -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) {

View File

@ -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,
),
),
],
);
}

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: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';

View File

@ -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,
),

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: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),
),

View File

@ -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,
),
),
],
);
}

View File

@ -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 {
),
),
),
),
);
}
}

View File

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

View File

@ -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:

View File

@ -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