refactor: color scheme support based on shadcn colors

This commit is contained in:
Kingkor Roy Tirtho 2025-01-16 21:13:42 +06:00
parent 696875e4b5
commit 6c005592e3
5 changed files with 108 additions and 187 deletions

View File

@ -26,6 +26,7 @@ import 'package:spotube/hooks/configurators/use_fix_window_stretching.dart';
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
import 'package:spotube/hooks/configurators/use_has_touch.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
import 'package:spotube/provider/audio_player/audio_player_streams.dart';
import 'package:spotube/provider/database/database.dart';
import 'package:spotube/provider/glance/glance.dart';
@ -43,7 +44,6 @@ import 'package:spotube/services/logger/logger.dart';
import 'package:spotube/services/wm_tools/wm_tools.dart';
import 'package:spotube/utils/migrations/sandbox.dart';
import 'package:spotube/utils/platform.dart';
import 'package:system_theme/system_theme.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
@ -83,8 +83,6 @@ Future<void> main(List<String> rawArgs) async {
await windowManager.setPreventClose(true);
}
await SystemTheme.accentColor.load();
if (!kIsWeb) {
MetadataGod.initialize();
}
@ -133,8 +131,8 @@ class Spotube extends HookConsumerWidget {
final themeMode =
ref.watch(userPreferencesProvider.select((s) => s.themeMode));
final locale = ref.watch(userPreferencesProvider.select((s) => s.locale));
// final accentMaterialColor =
// ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme));
final accentMaterialColor =
ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme));
// final isAmoledTheme =
// ref.watch(userPreferencesProvider.select((s) => s.amoledDarkTheme));
// final paletteColor =
@ -217,14 +215,18 @@ class Spotube extends HookConsumerWidget {
theme: ThemeData(
radius: .5,
iconTheme: const IconThemeProperties(),
colorScheme: ColorSchemes.lightOrange(),
colorScheme:
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ??
ColorSchemes.lightOrange(),
surfaceOpacity: .8,
surfaceBlur: 10,
),
darkTheme: ThemeData(
radius: .5,
iconTheme: const IconThemeProperties(),
colorScheme: ColorSchemes.darkOrange(),
colorScheme:
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ??
ColorSchemes.darkOrange(),
surfaceOpacity: .8,
surfaceBlur: 10,
),

View File

@ -1,6 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/home/feed/feed_section.dart';
@ -38,11 +37,8 @@ class HomePageFeedSection extends HookConsumerWidget {
hasNextPage: false,
isLoadingNextPage: false,
onFetchMore: () {},
titleTrailing: Directionality(
textDirection: TextDirection.rtl,
child: Button.link(
leading: const Icon(SpotubeIcons.angleRight),
child: Text(context.l10n.browse_more),
titleTrailing: Button.text(
child: Text(context.l10n.browse_all),
onPressed: () => ServiceUtils.pushNamed(
context,
HomeFeedSectionPage.name,
@ -51,7 +47,6 @@ class HomePageFeedSection extends HookConsumerWidget {
},
),
),
),
);
},
);

View File

@ -74,7 +74,6 @@ class HomeGenresSection extends HookConsumerWidget {
onPressed: () {
context.pushNamed(GenrePage.name);
},
trailing: const Icon(SpotubeIcons.angleRight),
child: Text(
context.l10n.browse_all,
).muted(),

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:system_theme/system_theme.dart';
class SpotubeColor extends Color {
final String name;
@ -25,23 +26,33 @@ class SpotubeColor extends Color {
}
final Set<SpotubeColor> colorsMap = {
SpotubeColor(SystemTheme.accentColor.accent.value, name: "System"),
SpotubeColor(Colors.red.value, name: "Red"),
SpotubeColor(Colors.pink.value, name: "Pink"),
SpotubeColor(Colors.purple.value, name: "Purple"),
SpotubeColor(Colors.deepPurple.value, name: "DeepPurple"),
SpotubeColor(Colors.indigo.value, name: "Indigo"),
SpotubeColor(Colors.blue.value, name: "Blue"),
SpotubeColor(Colors.lightBlue.value, name: "LightBlue"),
SpotubeColor(Colors.cyan.value, name: "Cyan"),
SpotubeColor(Colors.teal.value, name: "Teal"),
SpotubeColor(Colors.green.value, name: "Green"),
SpotubeColor(Colors.lightGreen.value, name: "LightGreen"),
SpotubeColor(Colors.yellow.value, name: "Yellow"),
SpotubeColor(Colors.amber.value, name: "Amber"),
SpotubeColor(Colors.orange.value, name: "Orange"),
SpotubeColor(Colors.deepOrange.value, name: "DeepOrange"),
SpotubeColor(Colors.brown.value, name: "Brown"),
SpotubeColor(Colors.slate.value, name: "slate"),
SpotubeColor(Colors.gray.value, name: "gray"),
SpotubeColor(Colors.zinc.value, name: "zinc"),
SpotubeColor(Colors.neutral.value, name: "neutral"),
SpotubeColor(Colors.stone.value, name: "stone"),
SpotubeColor(Colors.red.value, name: "red"),
SpotubeColor(Colors.orange.value, name: "orange"),
SpotubeColor(Colors.yellow.value, name: "yellow"),
SpotubeColor(Colors.green.value, name: "green"),
SpotubeColor(Colors.blue.value, name: "blue"),
SpotubeColor(Colors.violet.value, name: "violet"),
SpotubeColor(Colors.rose.value, name: "rose"),
};
final colorSchemeMap = {
"slate": ColorSchemes.slate,
"gray": ColorSchemes.gray,
"zinc": ColorSchemes.zinc,
"neutral": ColorSchemes.neutral,
"stone": ColorSchemes.stone,
"red": ColorSchemes.red,
"orange": ColorSchemes.orange,
"yellow": ColorSchemes.yellow,
"green": ColorSchemes.green,
"blue": ColorSchemes.blue,
"violet": ColorSchemes.violet,
"rose": ColorSchemes.rose,
};
class ColorSchemePickerDialog extends HookConsumerWidget {
@ -51,180 +62,93 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final preferences = ref.watch(userPreferencesProvider);
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
final scheme = preferences.accentColorScheme;
final active = useState<String>(colorsMap.firstWhere(
final active = useState<String?>(
colorsMap.firstWhereOrNull(
(element) {
return scheme.name == element.name;
},
).name);
onOk() {
preferencesNotifier.setAccentColorScheme(
colorsMap.firstWhere(
(element) {
return element.name == active.value;
},
),
)?.name,
);
Navigator.pop(context);
}
return AlertDialog(
title: Text(context.l10n.pick_color_scheme),
title: Text(
context.l10n.pick_color_scheme,
style: TextStyle(color: context.theme.colorScheme.foreground),
).large(),
actions: [
OutlinedButton(
Button.outline(
child: Text(context.l10n.cancel),
onPressed: () {
Navigator.pop(context);
},
),
FilledButton(
onPressed: onOk,
Button.primary(
onPressed: () {
Navigator.pop(context);
},
child: Text(context.l10n.save),
),
],
content: SizedBox(
height: 200,
width: 400,
child: ListView.separated(
separatorBuilder: (context, index) {
return const SizedBox(height: 10);
},
itemCount: colorsMap.length,
itemBuilder: (context, index) {
final color = colorsMap.elementAt(index);
return ColorTile(
child: Wrap(
spacing: 8,
runSpacing: 8,
children: colorsMap.map(
(color) {
return ColorChip(
name: color.name,
color: color,
isActive: active.value == color.name,
isActive: color.name == active.value,
onPressed: () {
active.value = color.name;
preferencesNotifier.setAccentColorScheme(
colorsMap.firstWhere(
(element) {
return element.name == color.name;
},
tooltip: color.name,
),
);
},
);
},
).toList(),
),
),
);
}
}
class ColorTile extends StatelessWidget {
class ColorChip extends StatelessWidget {
final String name;
final Color color;
final bool isActive;
final void Function()? onPressed;
final String? tooltip;
final bool isCompact;
const ColorTile({
required this.color,
this.isActive = false,
this.onPressed,
this.tooltip = "",
this.isCompact = false,
final VoidCallback onPressed;
const ColorChip({
super.key,
required this.name,
required this.color,
required this.isActive,
required this.onPressed,
});
factory ColorTile.compact({
required Color color,
bool isActive = false,
void Function()? onPressed,
String? tooltip = "",
Key? key,
}) {
return ColorTile(
color: color,
isActive: isActive,
onPressed: onPressed,
tooltip: tooltip,
isCompact: true,
key: key,
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final lead = Container(
height: 40,
width: 40,
decoration: BoxDecoration(
border: isActive
? Border.fromBorderSide(
BorderSide(
color: Color.lerp(
theme.colorScheme.primary,
theme.colorScheme.onPrimary,
0.5,
)!,
width: 4,
),
)
: null,
borderRadius: BorderRadius.circular(15),
color: color,
),
);
if (isCompact) {
return GestureDetector(
onTap: onPressed,
child: lead,
);
}
final colorScheme = ColorScheme.fromSeed(seedColor: color);
final palette = [
colorScheme.primary,
colorScheme.inversePrimary,
colorScheme.primaryContainer,
colorScheme.secondary,
colorScheme.secondaryContainer,
colorScheme.surface,
colorScheme.surface,
colorScheme.surfaceContainerHighest,
colorScheme.onPrimary,
colorScheme.onSurface,
];
return GestureDetector(
onTap: onPressed,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
lead,
const SizedBox(width: 10),
Text(
tooltip!,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 10),
Wrap(
alignment: WrapAlignment.start,
spacing: 10,
runSpacing: 10,
children: [
...palette.map(
(e) => Container(
height: 20,
return Chip(
leading: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: e,
borderRadius: BorderRadius.circular(5),
color: color,
borderRadius: BorderRadius.circular(10),
),
),
),
],
),
],
),
onPressed: onPressed,
style: isActive ? ButtonVariance.primary : ButtonVariance.outline,
child: Text(name),
);
}
}

View File

@ -94,10 +94,11 @@ class SettingsAppearanceSection extends HookConsumerWidget {
horizontal: 15,
vertical: 5,
),
trailing: ColorTile.compact(
trailing: ColorChip(
color: preferences.accentColorScheme,
name: preferences.accentColorScheme.name,
onPressed: pickColorScheme(),
isActive: true,
isActive: false,
),
onTap: pickColorScheme(),
),