mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: color scheme support based on shadcn colors
This commit is contained in:
parent
696875e4b5
commit
6c005592e3
@ -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_get_storage_perms.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_has_touch.dart';
|
import 'package:spotube/hooks/configurators/use_has_touch.dart';
|
||||||
import 'package:spotube/models/database/database.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/audio_player/audio_player_streams.dart';
|
||||||
import 'package:spotube/provider/database/database.dart';
|
import 'package:spotube/provider/database/database.dart';
|
||||||
import 'package:spotube/provider/glance/glance.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/services/wm_tools/wm_tools.dart';
|
||||||
import 'package:spotube/utils/migrations/sandbox.dart';
|
import 'package:spotube/utils/migrations/sandbox.dart';
|
||||||
import 'package:spotube/utils/platform.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_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
@ -83,8 +83,6 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
await windowManager.setPreventClose(true);
|
await windowManager.setPreventClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SystemTheme.accentColor.load();
|
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
MetadataGod.initialize();
|
MetadataGod.initialize();
|
||||||
}
|
}
|
||||||
@ -133,8 +131,8 @@ class Spotube extends HookConsumerWidget {
|
|||||||
final themeMode =
|
final themeMode =
|
||||||
ref.watch(userPreferencesProvider.select((s) => s.themeMode));
|
ref.watch(userPreferencesProvider.select((s) => s.themeMode));
|
||||||
final locale = ref.watch(userPreferencesProvider.select((s) => s.locale));
|
final locale = ref.watch(userPreferencesProvider.select((s) => s.locale));
|
||||||
// final accentMaterialColor =
|
final accentMaterialColor =
|
||||||
// ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme));
|
ref.watch(userPreferencesProvider.select((s) => s.accentColorScheme));
|
||||||
// final isAmoledTheme =
|
// final isAmoledTheme =
|
||||||
// ref.watch(userPreferencesProvider.select((s) => s.amoledDarkTheme));
|
// ref.watch(userPreferencesProvider.select((s) => s.amoledDarkTheme));
|
||||||
// final paletteColor =
|
// final paletteColor =
|
||||||
@ -217,14 +215,18 @@ class Spotube extends HookConsumerWidget {
|
|||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
radius: .5,
|
radius: .5,
|
||||||
iconTheme: const IconThemeProperties(),
|
iconTheme: const IconThemeProperties(),
|
||||||
colorScheme: ColorSchemes.lightOrange(),
|
colorScheme:
|
||||||
|
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ??
|
||||||
|
ColorSchemes.lightOrange(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
radius: .5,
|
radius: .5,
|
||||||
iconTheme: const IconThemeProperties(),
|
iconTheme: const IconThemeProperties(),
|
||||||
colorScheme: ColorSchemes.darkOrange(),
|
colorScheme:
|
||||||
|
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ??
|
||||||
|
ColorSchemes.darkOrange(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
|
||||||
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/home/feed/feed_section.dart';
|
import 'package:spotube/pages/home/feed/feed_section.dart';
|
||||||
@ -38,18 +37,14 @@ class HomePageFeedSection extends HookConsumerWidget {
|
|||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
isLoadingNextPage: false,
|
isLoadingNextPage: false,
|
||||||
onFetchMore: () {},
|
onFetchMore: () {},
|
||||||
titleTrailing: Directionality(
|
titleTrailing: Button.text(
|
||||||
textDirection: TextDirection.rtl,
|
child: Text(context.l10n.browse_all),
|
||||||
child: Button.link(
|
onPressed: () => ServiceUtils.pushNamed(
|
||||||
leading: const Icon(SpotubeIcons.angleRight),
|
context,
|
||||||
child: Text(context.l10n.browse_more),
|
HomeFeedSectionPage.name,
|
||||||
onPressed: () => ServiceUtils.pushNamed(
|
pathParameters: {
|
||||||
context,
|
"feedId": section.uri,
|
||||||
HomeFeedSectionPage.name,
|
},
|
||||||
pathParameters: {
|
|
||||||
"feedId": section.uri,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -74,7 +74,6 @@ class HomeGenresSection extends HookConsumerWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(GenrePage.name);
|
context.pushNamed(GenrePage.name);
|
||||||
},
|
},
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.browse_all,
|
context.l10n.browse_all,
|
||||||
).muted(),
|
).muted(),
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:collection/collection.dart';
|
||||||
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:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
|
||||||
|
|
||||||
class SpotubeColor extends Color {
|
class SpotubeColor extends Color {
|
||||||
final String name;
|
final String name;
|
||||||
@ -25,23 +26,33 @@ class SpotubeColor extends Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Set<SpotubeColor> colorsMap = {
|
final Set<SpotubeColor> colorsMap = {
|
||||||
SpotubeColor(SystemTheme.accentColor.accent.value, name: "System"),
|
SpotubeColor(Colors.slate.value, name: "slate"),
|
||||||
SpotubeColor(Colors.red.value, name: "Red"),
|
SpotubeColor(Colors.gray.value, name: "gray"),
|
||||||
SpotubeColor(Colors.pink.value, name: "Pink"),
|
SpotubeColor(Colors.zinc.value, name: "zinc"),
|
||||||
SpotubeColor(Colors.purple.value, name: "Purple"),
|
SpotubeColor(Colors.neutral.value, name: "neutral"),
|
||||||
SpotubeColor(Colors.deepPurple.value, name: "DeepPurple"),
|
SpotubeColor(Colors.stone.value, name: "stone"),
|
||||||
SpotubeColor(Colors.indigo.value, name: "Indigo"),
|
SpotubeColor(Colors.red.value, name: "red"),
|
||||||
SpotubeColor(Colors.blue.value, name: "Blue"),
|
SpotubeColor(Colors.orange.value, name: "orange"),
|
||||||
SpotubeColor(Colors.lightBlue.value, name: "LightBlue"),
|
SpotubeColor(Colors.yellow.value, name: "yellow"),
|
||||||
SpotubeColor(Colors.cyan.value, name: "Cyan"),
|
SpotubeColor(Colors.green.value, name: "green"),
|
||||||
SpotubeColor(Colors.teal.value, name: "Teal"),
|
SpotubeColor(Colors.blue.value, name: "blue"),
|
||||||
SpotubeColor(Colors.green.value, name: "Green"),
|
SpotubeColor(Colors.violet.value, name: "violet"),
|
||||||
SpotubeColor(Colors.lightGreen.value, name: "LightGreen"),
|
SpotubeColor(Colors.rose.value, name: "rose"),
|
||||||
SpotubeColor(Colors.yellow.value, name: "Yellow"),
|
};
|
||||||
SpotubeColor(Colors.amber.value, name: "Amber"),
|
|
||||||
SpotubeColor(Colors.orange.value, name: "Orange"),
|
final colorSchemeMap = {
|
||||||
SpotubeColor(Colors.deepOrange.value, name: "DeepOrange"),
|
"slate": ColorSchemes.slate,
|
||||||
SpotubeColor(Colors.brown.value, name: "Brown"),
|
"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 {
|
class ColorSchemePickerDialog extends HookConsumerWidget {
|
||||||
@ -51,180 +62,93 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||||
final scheme = preferences.accentColorScheme;
|
|
||||||
final active = useState<String>(colorsMap.firstWhere(
|
|
||||||
(element) {
|
|
||||||
return scheme.name == element.name;
|
|
||||||
},
|
|
||||||
).name);
|
|
||||||
|
|
||||||
onOk() {
|
final scheme = preferences.accentColorScheme;
|
||||||
preferencesNotifier.setAccentColorScheme(
|
final active = useState<String?>(
|
||||||
colorsMap.firstWhere(
|
colorsMap.firstWhereOrNull(
|
||||||
(element) {
|
(element) {
|
||||||
return element.name == active.value;
|
return scheme.name == element.name;
|
||||||
},
|
},
|
||||||
),
|
)?.name,
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog(
|
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: [
|
actions: [
|
||||||
OutlinedButton(
|
Button.outline(
|
||||||
child: Text(context.l10n.cancel),
|
child: Text(context.l10n.cancel),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FilledButton(
|
Button.primary(
|
||||||
onPressed: onOk,
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
child: Text(context.l10n.save),
|
child: Text(context.l10n.save),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
width: 400,
|
width: 400,
|
||||||
child: ListView.separated(
|
child: Wrap(
|
||||||
separatorBuilder: (context, index) {
|
spacing: 8,
|
||||||
return const SizedBox(height: 10);
|
runSpacing: 8,
|
||||||
},
|
children: colorsMap.map(
|
||||||
itemCount: colorsMap.length,
|
(color) {
|
||||||
itemBuilder: (context, index) {
|
return ColorChip(
|
||||||
final color = colorsMap.elementAt(index);
|
name: color.name,
|
||||||
return ColorTile(
|
color: color,
|
||||||
color: color,
|
isActive: color.name == active.value,
|
||||||
isActive: active.value == color.name,
|
onPressed: () {
|
||||||
onPressed: () {
|
active.value = color.name;
|
||||||
active.value = color.name;
|
preferencesNotifier.setAccentColorScheme(
|
||||||
},
|
colorsMap.firstWhere(
|
||||||
tooltip: color.name,
|
(element) {
|
||||||
);
|
return element.name == color.name;
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorTile extends StatelessWidget {
|
class ColorChip extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
final void Function()? onPressed;
|
final VoidCallback onPressed;
|
||||||
final String? tooltip;
|
const ColorChip({
|
||||||
final bool isCompact;
|
|
||||||
const ColorTile({
|
|
||||||
required this.color,
|
|
||||||
this.isActive = false,
|
|
||||||
this.onPressed,
|
|
||||||
this.tooltip = "",
|
|
||||||
this.isCompact = false,
|
|
||||||
super.key,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
return Chip(
|
||||||
|
leading: Container(
|
||||||
final lead = Container(
|
width: 20,
|
||||||
height: 40,
|
height: 20,
|
||||||
width: 40,
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: color,
|
||||||
border: isActive
|
borderRadius: BorderRadius.circular(10),
|
||||||
? 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,
|
|
||||||
width: 20,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: e,
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
style: isActive ? ButtonVariance.primary : ButtonVariance.outline,
|
||||||
|
child: Text(name),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,11 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
|||||||
horizontal: 15,
|
horizontal: 15,
|
||||||
vertical: 5,
|
vertical: 5,
|
||||||
),
|
),
|
||||||
trailing: ColorTile.compact(
|
trailing: ColorChip(
|
||||||
color: preferences.accentColorScheme,
|
color: preferences.accentColorScheme,
|
||||||
|
name: preferences.accentColorScheme.name,
|
||||||
onPressed: pickColorScheme(),
|
onPressed: pickColorScheme(),
|
||||||
isActive: true,
|
isActive: false,
|
||||||
),
|
),
|
||||||
onTap: pickColorScheme(),
|
onTap: pickColorScheme(),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user