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 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;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show ListTile, ListTileControlAffinity;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
final T value;
|
final T value;
|
||||||
final ValueChanged<T?>? onChanged;
|
final ValueChanged<T?>? onChanged;
|
||||||
|
|
||||||
final List<DropdownMenuItem<T>> options;
|
final List<SelectItemButton<T>> options;
|
||||||
|
|
||||||
/// Show the smaller value when the breakpoint is reached
|
/// Show the smaller value when the breakpoint is reached
|
||||||
///
|
///
|
||||||
@ -39,55 +40,25 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final rawControl = DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.secondaryContainer,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: DropdownButton<T>(
|
|
||||||
items: options,
|
|
||||||
value: value,
|
|
||||||
onChanged: onChanged,
|
|
||||||
menuMaxHeight: mediaQuery.size.height * 0.6,
|
|
||||||
underline: const SizedBox.shrink(),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
icon: const Icon(SpotubeIcons.angleDown),
|
|
||||||
dropdownColor: theme.colorScheme.secondaryContainer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final controlPlaceholder = useMemoized(
|
|
||||||
() => options
|
|
||||||
.firstWhere(
|
|
||||||
(element) => element.value == value,
|
|
||||||
orElse: () => DropdownMenuItem<T>(
|
|
||||||
value: null,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child,
|
|
||||||
[value, options]);
|
|
||||||
|
|
||||||
final control = breakLayout ?? mediaQuery.mdAndUp
|
Widget? control = Select<T>(
|
||||||
? rawControl
|
itemBuilder: (context, item) {
|
||||||
: showValueWhenUnfolded
|
return options.firstWhere((element) => element.value == item).child;
|
||||||
? Container(
|
},
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
value: value,
|
||||||
decoration: BoxDecoration(
|
onChanged: onChanged,
|
||||||
border: Border.all(
|
children: options,
|
||||||
color: theme.colorScheme.primary,
|
);
|
||||||
width: 2,
|
|
||||||
),
|
if (mediaQuery.smAndDown) {
|
||||||
borderRadius: BorderRadius.circular(10),
|
if (showValueWhenUnfolded) {
|
||||||
),
|
control = OutlineBadge(
|
||||||
child: DefaultTextStyle(
|
child: options.firstWhere((element) => element.value == value).child,
|
||||||
style: TextStyle(
|
);
|
||||||
color: theme.colorScheme.primary,
|
} else {
|
||||||
),
|
control = null;
|
||||||
child: controlPlaceholder,
|
}
|
||||||
),
|
}
|
||||||
)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: title,
|
title: title,
|
||||||
@ -104,20 +75,26 @@ class AdaptiveSelectTile<T> extends HookWidget {
|
|||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SimpleDialog(
|
return AlertDialog(
|
||||||
title: title,
|
content: ListView.builder(
|
||||||
children: [
|
shrinkWrap: true,
|
||||||
for (final option in options)
|
itemCount: options.length,
|
||||||
RadioListTile<T>(
|
itemBuilder: (context, index) {
|
||||||
title: option.child,
|
final item = options[index];
|
||||||
value: option.value as T,
|
|
||||||
groupValue: value,
|
return ListTile(
|
||||||
onChanged: (v) {
|
iconColor: theme.colorScheme.primary,
|
||||||
Navigator.pop(context);
|
leading: item.value == value
|
||||||
onChanged?.call(v);
|
? const Icon(SpotubeIcons.radioChecked)
|
||||||
|
: const Icon(SpotubeIcons.radioUnchecked),
|
||||||
|
title: item.child,
|
||||||
|
onTap: () {
|
||||||
|
onChanged?.call(item.value);
|
||||||
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show ListTileTheme, ListTileThemeData;
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
|
|
||||||
class SectionCardWithHeading extends StatelessWidget {
|
class SectionCardWithHeading extends StatelessWidget {
|
||||||
final String heading;
|
final String heading;
|
||||||
@ -11,27 +13,41 @@ class SectionCardWithHeading extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return ListTileTheme(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
data: ListTileThemeData(
|
||||||
mainAxisSize: MainAxisSize.min,
|
shape: RoundedRectangleBorder(
|
||||||
children: [
|
borderRadius: context.theme.borderRadiusLg,
|
||||||
Padding(
|
side: BorderSide(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
color: context.theme.colorScheme.border,
|
||||||
child: Text(
|
width: .5,
|
||||||
heading,
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
textColor: context.theme.colorScheme.foreground,
|
||||||
padding: const EdgeInsets.all(8.0),
|
iconColor: context.theme.colorScheme.foreground,
|
||||||
child: Card(
|
selectedColor: context.theme.colorScheme.accent,
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
subtitleTextStyle: context.theme.typography.xSmall,
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text(
|
||||||
|
heading,
|
||||||
|
style: context.theme.typography.large,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
],
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: children,
|
||||||
|
).gap(8.0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,12 +145,21 @@ class SearchPage extends HookConsumerWidget {
|
|||||||
leading: const Icon(SpotubeIcons.search),
|
leading: const Icon(SpotubeIcons.search),
|
||||||
textInputAction: TextInputAction.search,
|
textInputAction: TextInputAction.search,
|
||||||
placeholder: Text(context.l10n.search),
|
placeholder: Text(context.l10n.search),
|
||||||
trailing: IconButton.ghost(
|
trailing: AnimatedCrossFade(
|
||||||
size: ButtonSize.small,
|
duration:
|
||||||
icon: const Icon(SpotubeIcons.close),
|
const Duration(milliseconds: 300),
|
||||||
onPressed: () {
|
crossFadeState: controller.text.isNotEmpty
|
||||||
controller.clear();
|
? CrossFadeState.showFirst
|
||||||
},
|
: CrossFadeState.showSecond,
|
||||||
|
firstChild: IconButton.ghost(
|
||||||
|
size: ButtonSize.small,
|
||||||
|
icon: const Icon(SpotubeIcons.close),
|
||||||
|
onPressed: () {
|
||||||
|
controller.clear();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
secondChild:
|
||||||
|
const SizedBox.square(dimension: 28),
|
||||||
),
|
),
|
||||||
onAcceptSuggestion: (index) {
|
onAcceptSuggestion: (index) {
|
||||||
controller.text =
|
controller.text =
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'package:auto_size_text/auto_size_text.dart';
|
import 'package:auto_size_text/auto_size_text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show FilledButton, ButtonStyle, ListTile;
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart' hide ButtonStyle;
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:spotube/collections/env.dart';
|
import 'package:spotube/collections/env.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||||
@ -45,9 +47,13 @@ class SettingsAboutSection extends HookConsumerWidget {
|
|||||||
trailing: (context, update) => FilledButton(
|
trailing: (context, update) => FilledButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: WidgetStatePropertyAll(Colors.red[100]),
|
backgroundColor: WidgetStatePropertyAll(Colors.red[100]),
|
||||||
foregroundColor:
|
foregroundColor: const WidgetStatePropertyAll(Colors.pink),
|
||||||
const WidgetStatePropertyAll(Colors.pinkAccent),
|
|
||||||
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
|
padding: const WidgetStatePropertyAll(EdgeInsets.all(15)),
|
||||||
|
shape: WidgetStatePropertyAll(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: context.theme.borderRadiusLg,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
launchUrlString(
|
launchUrlString(
|
||||||
@ -66,11 +72,14 @@ class SettingsAboutSection extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (Env.enableUpdateChecker)
|
if (Env.enableUpdateChecker)
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.update),
|
leading: const Icon(SpotubeIcons.update),
|
||||||
title: Text(context.l10n.check_for_updates),
|
title: Text(context.l10n.check_for_updates),
|
||||||
value: preferences.checkUpdate,
|
trailing: Switch(
|
||||||
onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked),
|
value: preferences.checkUpdate,
|
||||||
|
onChanged: (checked) =>
|
||||||
|
preferencesNotifier.setCheckUpdate(checked),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(SpotubeIcons.info),
|
leading: const Icon(SpotubeIcons.info),
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart' hide ThemeMode;
|
import 'package:flutter/material.dart' show ListTile;
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart' show ThemeMode;
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
@ -42,15 +41,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: LayoutMode.adaptive,
|
value: LayoutMode.adaptive,
|
||||||
child: Text(context.l10n.adaptive),
|
child: Text(context.l10n.adaptive),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: LayoutMode.compact,
|
value: LayoutMode.compact,
|
||||||
child: Text(context.l10n.compact),
|
child: Text(context.l10n.compact),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: LayoutMode.extended,
|
value: LayoutMode.extended,
|
||||||
child: Text(context.l10n.extended),
|
child: Text(context.l10n.extended),
|
||||||
),
|
),
|
||||||
@ -61,15 +60,15 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
|||||||
title: Text(context.l10n.theme),
|
title: Text(context.l10n.theme),
|
||||||
value: preferences.themeMode,
|
value: preferences.themeMode,
|
||||||
options: [
|
options: [
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: ThemeMode.dark,
|
value: ThemeMode.dark,
|
||||||
child: Text(context.l10n.dark),
|
child: Text(context.l10n.dark),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: ThemeMode.light,
|
value: ThemeMode.light,
|
||||||
child: Text(context.l10n.light),
|
child: Text(context.l10n.light),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: ThemeMode.system,
|
value: ThemeMode.system,
|
||||||
child: Text(context.l10n.system),
|
child: Text(context.l10n.system),
|
||||||
),
|
),
|
||||||
@ -80,13 +79,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.amoled),
|
leading: const Icon(SpotubeIcons.amoled),
|
||||||
title: Text(context.l10n.use_amoled_mode),
|
title: Text(context.l10n.use_amoled_mode),
|
||||||
subtitle: Text(context.l10n.pitch_dark_theme),
|
subtitle: Text(context.l10n.pitch_dark_theme),
|
||||||
value: preferences.amoledDarkTheme,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setAmoledDarkTheme,
|
value: preferences.amoledDarkTheme,
|
||||||
),
|
onChanged: preferencesNotifier.setAmoledDarkTheme,
|
||||||
|
)),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(SpotubeIcons.palette),
|
leading: const Icon(SpotubeIcons.palette),
|
||||||
title: Text(context.l10n.accent_color),
|
title: Text(context.l10n.accent_color),
|
||||||
@ -101,13 +101,14 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onTap: pickColorScheme(),
|
onTap: pickColorScheme(),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.colorSync),
|
leading: const Icon(SpotubeIcons.colorSync),
|
||||||
title: Text(context.l10n.sync_album_color),
|
title: Text(context.l10n.sync_album_color),
|
||||||
subtitle: Text(context.l10n.sync_album_color_description),
|
subtitle: Text(context.l10n.sync_album_color_description),
|
||||||
value: preferences.albumColorSync,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setAlbumColorSync,
|
value: preferences.albumColorSync,
|
||||||
),
|
onChanged: preferencesNotifier.setAlbumColorSync,
|
||||||
|
)),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isGettingStarted) {
|
if (isGettingStarted) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show ListTile;
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||||
@ -25,11 +25,11 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
|||||||
title: Text(context.l10n.close_behavior),
|
title: Text(context.l10n.close_behavior),
|
||||||
value: preferences.closeBehavior,
|
value: preferences.closeBehavior,
|
||||||
options: [
|
options: [
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: CloseBehavior.close,
|
value: CloseBehavior.close,
|
||||||
child: Text(context.l10n.close),
|
child: Text(context.l10n.close),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: CloseBehavior.minimizeToTray,
|
value: CloseBehavior.minimizeToTray,
|
||||||
child: Text(context.l10n.minimize_to_tray),
|
child: Text(context.l10n.minimize_to_tray),
|
||||||
),
|
),
|
||||||
@ -40,23 +40,29 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.tray),
|
leading: const Icon(SpotubeIcons.tray),
|
||||||
title: Text(context.l10n.show_tray_icon),
|
title: Text(context.l10n.show_tray_icon),
|
||||||
value: preferences.showSystemTrayIcon,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setShowSystemTrayIcon,
|
value: preferences.showSystemTrayIcon,
|
||||||
|
onChanged: preferencesNotifier.setShowSystemTrayIcon,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.window),
|
leading: const Icon(SpotubeIcons.window),
|
||||||
title: Text(context.l10n.use_system_title_bar),
|
title: Text(context.l10n.use_system_title_bar),
|
||||||
value: preferences.systemTitleBar,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setSystemTitleBar,
|
value: preferences.systemTitleBar,
|
||||||
|
onChanged: preferencesNotifier.setSystemTitleBar,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.discord),
|
leading: const Icon(SpotubeIcons.discord),
|
||||||
title: Text(context.l10n.discord_rich_presence),
|
title: Text(context.l10n.discord_rich_presence),
|
||||||
value: preferences.discordPresence,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setDiscordPresence,
|
value: preferences.discordPresence,
|
||||||
|
onChanged: preferencesNotifier.setDiscordPresence,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -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';
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show ListTile;
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:piped_client/piped_client.dart';
|
import 'package:piped_client/piped_client.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||||
@ -30,21 +31,20 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
return SectionCardWithHeading(
|
return SectionCardWithHeading(
|
||||||
heading: context.l10n.playback,
|
heading: context.l10n.playback,
|
||||||
children: [
|
children: [
|
||||||
const Gap(10),
|
|
||||||
AdaptiveSelectTile<SourceQualities>(
|
AdaptiveSelectTile<SourceQualities>(
|
||||||
secondary: const Icon(SpotubeIcons.audioQuality),
|
secondary: const Icon(SpotubeIcons.audioQuality),
|
||||||
title: Text(context.l10n.audio_quality),
|
title: Text(context.l10n.audio_quality),
|
||||||
value: preferences.audioQuality,
|
value: preferences.audioQuality,
|
||||||
options: [
|
options: [
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: SourceQualities.high,
|
value: SourceQualities.high,
|
||||||
child: Text(context.l10n.high),
|
child: Text(context.l10n.high),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: SourceQualities.medium,
|
value: SourceQualities.medium,
|
||||||
child: Text(context.l10n.medium),
|
child: Text(context.l10n.medium),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
SelectItemButton(
|
||||||
value: SourceQualities.low,
|
value: SourceQualities.low,
|
||||||
child: Text(context.l10n.low),
|
child: Text(context.l10n.low),
|
||||||
),
|
),
|
||||||
@ -55,13 +55,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(5),
|
|
||||||
AdaptiveSelectTile<AudioSource>(
|
AdaptiveSelectTile<AudioSource>(
|
||||||
secondary: const Icon(SpotubeIcons.api),
|
secondary: const Icon(SpotubeIcons.api),
|
||||||
title: Text(context.l10n.audio_source),
|
title: Text(context.l10n.audio_source),
|
||||||
value: preferences.audioSource,
|
value: preferences.audioSource,
|
||||||
options: AudioSource.values
|
options: AudioSource.values
|
||||||
.map((e) => DropdownMenuItem(
|
.map((e) => SelectItemButton(
|
||||||
value: e,
|
value: e,
|
||||||
child: Text(e.label),
|
child: Text(e.label),
|
||||||
))
|
))
|
||||||
@ -71,177 +70,173 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
preferencesNotifier.setAudioSource(value);
|
preferencesNotifier.setAudioSource(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedCrossFade(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: preferences.audioSource != AudioSource.piped
|
crossFadeState: preferences.audioSource != AudioSource.piped
|
||||||
? const SizedBox.shrink()
|
? CrossFadeState.showFirst
|
||||||
: Consumer(builder: (context, ref, child) {
|
: CrossFadeState.showSecond,
|
||||||
final instanceList = ref.watch(pipedInstancesFutureProvider);
|
firstChild: const SizedBox.shrink(),
|
||||||
|
secondChild: Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final instanceList = ref.watch(pipedInstancesFutureProvider);
|
||||||
|
|
||||||
return instanceList.when(
|
return instanceList.when(
|
||||||
data: (data) {
|
data: (data) {
|
||||||
return AdaptiveSelectTile<String>(
|
return AdaptiveSelectTile<String>(
|
||||||
secondary: const Icon(SpotubeIcons.piped),
|
secondary: const Icon(SpotubeIcons.piped),
|
||||||
title: Text(context.l10n.piped_instance),
|
title: Text(context.l10n.piped_instance),
|
||||||
subtitle: RichText(
|
subtitle: Text(
|
||||||
text: TextSpan(
|
"${context.l10n.piped_description}\n"
|
||||||
children: [
|
"${context.l10n.piped_warning}",
|
||||||
TextSpan(
|
|
||||||
text: context.l10n.piped_description,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const TextSpan(text: "\n"),
|
|
||||||
TextSpan(
|
|
||||||
text: context.l10n.piped_warning,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
value: preferences.pipedInstance,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: data
|
|
||||||
.sortedBy((e) => e.name)
|
|
||||||
.map(
|
|
||||||
(e) => DropdownMenuItem(
|
|
||||||
value: e.apiUrl,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: "${e.name.trim()}\n",
|
|
||||||
style: theme.textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: e.locations
|
|
||||||
.map(countryCodeToEmoji)
|
|
||||||
.join(""),
|
|
||||||
style: GoogleFonts.notoColorEmoji(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferencesNotifier.setPipedInstance(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loading: () => const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
),
|
||||||
error: (error, stackTrace) => Text(error.toString()),
|
value: preferences.pipedInstance,
|
||||||
);
|
showValueWhenUnfolded: false,
|
||||||
}),
|
options: data
|
||||||
),
|
.sortedBy((e) => e.name)
|
||||||
AnimatedSwitcher(
|
.map(
|
||||||
duration: const Duration(milliseconds: 300),
|
(e) => SelectItemButton(
|
||||||
child: preferences.audioSource != AudioSource.invidious
|
value: e.apiUrl,
|
||||||
? const SizedBox.shrink()
|
child: RichText(
|
||||||
: Consumer(builder: (context, ref, child) {
|
text: TextSpan(
|
||||||
final instanceList = ref.watch(invidiousInstancesProvider);
|
style: theme.typography.normal.copyWith(
|
||||||
|
color: theme.colorScheme.foreground,
|
||||||
return instanceList.when(
|
|
||||||
data: (data) {
|
|
||||||
return AdaptiveSelectTile<String>(
|
|
||||||
secondary: const Icon(SpotubeIcons.piped),
|
|
||||||
title: Text(context.l10n.invidious_instance),
|
|
||||||
subtitle: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: context.l10n.invidious_description,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const TextSpan(text: "\n"),
|
|
||||||
TextSpan(
|
|
||||||
text: context.l10n.invidious_warning,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
value: preferences.invidiousInstance,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: data
|
|
||||||
.sortedBy((e) => e.name)
|
|
||||||
.map(
|
|
||||||
(e) => DropdownMenuItem(
|
|
||||||
value: e.details.uri,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: "${e.name.trim()}\n",
|
|
||||||
style: theme.textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: countryCodeToEmoji(
|
|
||||||
e.details.region,
|
|
||||||
),
|
|
||||||
style: GoogleFonts.notoColorEmoji(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "${e.name.trim()}\n",
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: e.locations
|
||||||
|
.map(countryCodeToEmoji)
|
||||||
|
.join(""),
|
||||||
|
style: GoogleFonts.notoColorEmoji(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
),
|
||||||
onChanged: (value) {
|
)
|
||||||
if (value != null) {
|
.toList(),
|
||||||
preferencesNotifier.setInvidiousInstance(value);
|
onChanged: (value) {
|
||||||
}
|
if (value != null) {
|
||||||
},
|
preferencesNotifier.setPipedInstance(value);
|
||||||
);
|
}
|
||||||
},
|
},
|
||||||
loading: () => const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
error: (error, stackTrace) => Text(error.toString()),
|
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
),
|
loading: () => const Center(
|
||||||
AnimatedSwitcher(
|
child: CircularProgressIndicator(),
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.audioSource != AudioSource.piped
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: AdaptiveSelectTile<SearchMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.search),
|
|
||||||
title: Text(context.l10n.search_mode),
|
|
||||||
value: preferences.searchMode,
|
|
||||||
options: SearchMode.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferencesNotifier.setSearchMode(value);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
error: (error, stackTrace) => Text(error.toString()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
AnimatedSwitcher(
|
AnimatedCrossFade(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: preferences.searchMode == SearchMode.youtube &&
|
crossFadeState: preferences.audioSource != AudioSource.invidious
|
||||||
|
? CrossFadeState.showFirst
|
||||||
|
: CrossFadeState.showSecond,
|
||||||
|
firstChild: const SizedBox.shrink(),
|
||||||
|
secondChild: Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final instanceList = ref.watch(invidiousInstancesProvider);
|
||||||
|
|
||||||
|
return instanceList.when(
|
||||||
|
data: (data) {
|
||||||
|
return AdaptiveSelectTile<String>(
|
||||||
|
secondary: const Icon(SpotubeIcons.piped),
|
||||||
|
title: Text(context.l10n.invidious_instance),
|
||||||
|
subtitle: Text(
|
||||||
|
"${context.l10n.invidious_description}\n"
|
||||||
|
"${context.l10n.invidious_warning}",
|
||||||
|
),
|
||||||
|
value: preferences.invidiousInstance,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: data
|
||||||
|
.sortedBy((e) => e.name)
|
||||||
|
.map(
|
||||||
|
(e) => SelectItemButton(
|
||||||
|
value: e.details.uri,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: theme.typography.normal.copyWith(
|
||||||
|
color: theme.colorScheme.foreground,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "${e.name.trim()}\n",
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: countryCodeToEmoji(
|
||||||
|
e.details.region,
|
||||||
|
),
|
||||||
|
style: GoogleFonts.notoColorEmoji(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferencesNotifier.setInvidiousInstance(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) => Text(error.toString()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedCrossFade(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
crossFadeState: preferences.audioSource != AudioSource.youtube
|
||||||
|
? CrossFadeState.showFirst
|
||||||
|
: CrossFadeState.showSecond,
|
||||||
|
firstChild: const SizedBox.shrink(),
|
||||||
|
secondChild: AdaptiveSelectTile<SearchMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.search),
|
||||||
|
title: Text(context.l10n.search_mode),
|
||||||
|
value: preferences.searchMode,
|
||||||
|
options: SearchMode.values
|
||||||
|
.map((e) => SelectItemButton(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferencesNotifier.setSearchMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedCrossFade(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
crossFadeState: preferences.searchMode == SearchMode.youtube &&
|
||||||
(preferences.audioSource == AudioSource.piped ||
|
(preferences.audioSource == AudioSource.piped ||
|
||||||
preferences.audioSource == AudioSource.youtube ||
|
preferences.audioSource == AudioSource.youtube ||
|
||||||
preferences.audioSource == AudioSource.invidious)
|
preferences.audioSource == AudioSource.invidious)
|
||||||
? SwitchListTile(
|
? CrossFadeState.showFirst
|
||||||
secondary: const Icon(SpotubeIcons.skip),
|
: CrossFadeState.showSecond,
|
||||||
title: Text(context.l10n.skip_non_music),
|
firstChild: ListTile(
|
||||||
value: preferences.skipNonMusic,
|
leading: const Icon(SpotubeIcons.skip),
|
||||||
onChanged: (state) {
|
title: Text(context.l10n.skip_non_music),
|
||||||
preferencesNotifier.setSkipNonMusic(state);
|
trailing: Switch(
|
||||||
},
|
value: preferences.skipNonMusic,
|
||||||
)
|
onChanged: (state) {
|
||||||
: const SizedBox.shrink(),
|
preferencesNotifier.setSkipNonMusic(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
secondChild: const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
title: Text(context.l10n.cache_music),
|
title: Text(context.l10n.cache_music),
|
||||||
subtitle: kIsMobile
|
subtitle: kIsMobile
|
||||||
? null
|
? null
|
||||||
@ -253,7 +248,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
text: context.l10n.cache_folder.toLowerCase(),
|
text: context.l10n.cache_folder.toLowerCase(),
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = preferencesNotifier.openCacheFolder,
|
..onTap = preferencesNotifier.openCacheFolder,
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.typography.normal.copyWith(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
@ -261,9 +256,11 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
secondary: const Icon(SpotubeIcons.cache),
|
leading: const Icon(SpotubeIcons.cache),
|
||||||
value: preferences.cacheMusic,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setCacheMusic,
|
value: preferences.cacheMusic,
|
||||||
|
onChanged: preferencesNotifier.setCacheMusic,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||||
@ -274,25 +271,26 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.normalize),
|
leading: const Icon(SpotubeIcons.normalize),
|
||||||
title: Text(context.l10n.normalize_audio),
|
title: Text(context.l10n.normalize_audio),
|
||||||
value: preferences.normalizeAudio,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setNormalizeAudio,
|
value: preferences.normalizeAudio,
|
||||||
|
onChanged: preferencesNotifier.setNormalizeAudio,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (preferences.audioSource != AudioSource.jiosaavn) ...[
|
if (preferences.audioSource != AudioSource.jiosaavn) ...[
|
||||||
const Gap(5),
|
|
||||||
AdaptiveSelectTile<SourceCodecs>(
|
AdaptiveSelectTile<SourceCodecs>(
|
||||||
secondary: const Icon(SpotubeIcons.stream),
|
secondary: const Icon(SpotubeIcons.stream),
|
||||||
title: Text(context.l10n.streaming_music_codec),
|
title: Text(context.l10n.streaming_music_codec),
|
||||||
value: preferences.streamMusicCodec,
|
value: preferences.streamMusicCodec,
|
||||||
showValueWhenUnfolded: false,
|
showValueWhenUnfolded: false,
|
||||||
options: SourceCodecs.values
|
options: SourceCodecs.values
|
||||||
.map((e) => DropdownMenuItem(
|
.map((e) => SelectItemButton(
|
||||||
value: e,
|
value: e,
|
||||||
child: Text(
|
child: Text(
|
||||||
e.label,
|
e.label,
|
||||||
style: theme.textTheme.labelMedium,
|
style: theme.typography.small,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
@ -301,18 +299,17 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
preferencesNotifier.setStreamMusicCodec(value);
|
preferencesNotifier.setStreamMusicCodec(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(5),
|
|
||||||
AdaptiveSelectTile<SourceCodecs>(
|
AdaptiveSelectTile<SourceCodecs>(
|
||||||
secondary: const Icon(SpotubeIcons.file),
|
secondary: const Icon(SpotubeIcons.file),
|
||||||
title: Text(context.l10n.download_music_codec),
|
title: Text(context.l10n.download_music_codec),
|
||||||
value: preferences.downloadMusicCodec,
|
value: preferences.downloadMusicCodec,
|
||||||
showValueWhenUnfolded: false,
|
showValueWhenUnfolded: false,
|
||||||
options: SourceCodecs.values
|
options: SourceCodecs.values
|
||||||
.map((e) => DropdownMenuItem(
|
.map((e) => SelectItemButton(
|
||||||
value: e,
|
value: e,
|
||||||
child: Text(
|
child: Text(
|
||||||
e.label,
|
e.label,
|
||||||
style: theme.textTheme.labelMedium,
|
style: theme.typography.small,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
@ -320,20 +317,23 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
preferencesNotifier.setDownloadMusicCodec(value);
|
preferencesNotifier.setDownloadMusicCodec(value);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
SwitchListTile(
|
ListTile(
|
||||||
secondary: const Icon(SpotubeIcons.repeat),
|
leading: const Icon(SpotubeIcons.repeat),
|
||||||
title: Text(context.l10n.endless_playback),
|
title: Text(context.l10n.endless_playback),
|
||||||
value: preferences.endlessPlayback,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setEndlessPlayback,
|
value: preferences.endlessPlayback,
|
||||||
),
|
onChanged: preferencesNotifier.setEndlessPlayback,
|
||||||
SwitchListTile(
|
)),
|
||||||
|
ListTile(
|
||||||
title: Text(context.l10n.enable_connect),
|
title: Text(context.l10n.enable_connect),
|
||||||
subtitle: Text(context.l10n.enable_connect_description),
|
subtitle: Text(context.l10n.enable_connect_description),
|
||||||
secondary: const Icon(SpotubeIcons.connect),
|
leading: const Icon(SpotubeIcons.connect),
|
||||||
value: preferences.enableConnect,
|
trailing: Switch(
|
||||||
onChanged: preferencesNotifier.setEnableConnect,
|
value: preferences.enableConnect,
|
||||||
|
onChanged: preferencesNotifier.setEnableConnect,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' show Material, MaterialType;
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotube/components/titlebar/titlebar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/settings/sections/about.dart';
|
import 'package:spotube/pages/settings/sections/about.dart';
|
||||||
@ -28,36 +29,41 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: TitleBar(
|
headers: [
|
||||||
title: Text(context.l10n.settings),
|
TitleBar(
|
||||||
automaticallyImplyLeading: true,
|
title: Text(context.l10n.settings),
|
||||||
),
|
automaticallyImplyLeading: true,
|
||||||
body: Scrollbar(
|
)
|
||||||
|
],
|
||||||
|
child: Scrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 1366),
|
constraints: const BoxConstraints(maxWidth: 1366),
|
||||||
child: ScrollConfiguration(
|
child: ScrollConfiguration(
|
||||||
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
behavior: const ScrollBehavior().copyWith(scrollbars: false),
|
||||||
child: ListView(
|
child: Material(
|
||||||
controller: controller,
|
type: MaterialType.transparency,
|
||||||
children: [
|
child: ListView(
|
||||||
const SettingsAccountSection(),
|
controller: controller,
|
||||||
const SettingsLanguageRegionSection(),
|
children: [
|
||||||
const SettingsAppearanceSection(),
|
const SettingsAccountSection(),
|
||||||
const SettingsPlaybackSection(),
|
const SettingsLanguageRegionSection(),
|
||||||
const SettingsDownloadsSection(),
|
const SettingsAppearanceSection(),
|
||||||
if (kIsDesktop) const SettingsDesktopSection(),
|
const SettingsPlaybackSection(),
|
||||||
if (!kIsWeb) const SettingsDevelopersSection(),
|
const SettingsDownloadsSection(),
|
||||||
const SettingsAboutSection(),
|
if (kIsDesktop) const SettingsDesktopSection(),
|
||||||
Center(
|
if (!kIsWeb) const SettingsDevelopersSection(),
|
||||||
child: FilledButton(
|
const SettingsAboutSection(),
|
||||||
onPressed: preferencesNotifier.reset,
|
Center(
|
||||||
child: Text(context.l10n.restore_defaults),
|
child: Button.destructive(
|
||||||
|
onPressed: preferencesNotifier.reset,
|
||||||
|
child: Text(context.l10n.restore_defaults),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 200),
|
||||||
const SizedBox(height: 10),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user