refactor: Migrate to Tweakcn themes and enhance UI consistency
- Replaced references to AppColorPalettes with TweakcnThemes across various files to standardize theme usage. - Updated the AppTheme and AppColorTokens to utilize TweakcnThemeDefinition for improved theme management. - Adjusted UI components in ChatPage, ChatsDrawer, AppCustomizationPage, and ProfilePage to align with the new theme structure, ensuring consistent styling and color application. - Removed the deprecated color_palettes.dart file to streamline the theme architecture.
This commit is contained in:
@@ -4,255 +4,251 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'theme_extensions.dart';
|
||||
import 'color_palettes.dart';
|
||||
import 'tweakcn_themes.dart';
|
||||
import 'color_tokens.dart';
|
||||
|
||||
class AppTheme {
|
||||
// Enhanced neutral palette for better contrast (WCAG AA compliant)
|
||||
static const Color neutral900 = Color(0xFF0B0E14);
|
||||
static const Color neutral800 = Color(0xFF161B24);
|
||||
static const Color neutral700 = Color(0xFF1F2531);
|
||||
static const Color neutral600 = Color(0xFF343C4D);
|
||||
static const Color neutral500 = Color(0xFF4A5161);
|
||||
static const Color neutral400 = Color(0xFF9099AC);
|
||||
static const Color neutral300 = Color(0xFFC5CCD9);
|
||||
static const Color neutral200 = Color(0xFFE6EAF1);
|
||||
static const Color neutral100 = Color(0xFFF5F7FA);
|
||||
static const Color neutral50 = Color(0xFFFFFFFF);
|
||||
|
||||
// Semantic colors derived from the token specification
|
||||
static const Color error = Color(0xFFCE2C31);
|
||||
static const Color errorDark = Color(0xFFFF5F67);
|
||||
static const Color success = Color(0xFF0E9D58);
|
||||
static const Color successDark = Color(0xFF23C179);
|
||||
static const Color warning = Color(0xFFDB7900);
|
||||
static const Color warningDark = Color(0xFFFF9800);
|
||||
static const Color info = Color(0xFF0174D3);
|
||||
static const Color infoDark = Color(0xFF4CA8FF);
|
||||
|
||||
static ThemeData light(AppColorPalette palette) {
|
||||
final lightTone = palette.light;
|
||||
final tokens = AppColorTokens.light(palette: palette);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: lightTone.primary,
|
||||
onPrimary: _pickOnColor(lightTone.primary, tokens),
|
||||
secondary: lightTone.secondary,
|
||||
onSecondary: _pickOnColor(lightTone.secondary, tokens),
|
||||
tertiary: lightTone.accent,
|
||||
onTertiary: _pickOnColor(lightTone.accent, tokens),
|
||||
surfaceTint: lightTone.primary,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
static ThemeData light(TweakcnThemeDefinition theme) {
|
||||
final tokens = AppColorTokens.light(theme: theme);
|
||||
return _buildTheme(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: colorScheme,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: tokens.neutralTone00,
|
||||
modalBackgroundColor: tokens.neutralTone00,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: lightTone.primary,
|
||||
foregroundColor: _pickOnColor(lightTone.primary, tokens),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: Elevation.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
|
||||
side: BorderSide(color: tokens.neutralTone20),
|
||||
),
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: Color.alphaBlend(
|
||||
tokens.overlayStrong,
|
||||
tokens.neutralOnSurface,
|
||||
),
|
||||
contentTextStyle: TextStyle(
|
||||
color: tokens.neutralTone00,
|
||||
fontSize: AppTypography.bodyMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
|
||||
),
|
||||
elevation: Elevation.high,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: tokens.neutralTone00,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: lightTone.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
textTheme: ThemeData.light().textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
ConduitThemeExtension.lightPalette(palette: palette, tokens: tokens),
|
||||
AppPaletteThemeExtension(palette: palette),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData dark(AppColorPalette palette) {
|
||||
final darkTone = palette.dark;
|
||||
final tokens = AppColorTokens.dark(palette: palette);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: darkTone.primary,
|
||||
onPrimary: _pickOnColor(darkTone.primary, tokens),
|
||||
secondary: darkTone.secondary,
|
||||
onSecondary: _pickOnColor(darkTone.secondary, tokens),
|
||||
tertiary: darkTone.accent,
|
||||
onTertiary: _pickOnColor(darkTone.accent, tokens),
|
||||
surfaceTint: darkTone.primary,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
static ThemeData dark(TweakcnThemeDefinition theme) {
|
||||
final tokens = AppColorTokens.dark(theme: theme);
|
||||
return _buildTheme(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: tokens.neutralTone00,
|
||||
modalBackgroundColor: tokens.neutralTone00,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
|
||||
),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: darkTone.primary,
|
||||
foregroundColor: _pickOnColor(darkTone.primary, tokens),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
elevation: Elevation.none,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
|
||||
side: BorderSide(color: tokens.neutralTone40),
|
||||
),
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: Color.alphaBlend(
|
||||
tokens.overlayStrong,
|
||||
tokens.neutralTone20,
|
||||
),
|
||||
contentTextStyle: TextStyle(
|
||||
color: tokens.neutralOnSurface,
|
||||
fontSize: AppTypography.bodyMedium,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
|
||||
),
|
||||
elevation: Elevation.high,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: tokens.neutralTone20,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.neutralTone40, width: 1),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.neutralTone40, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: darkTone.primary, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
textTheme: ThemeData.dark().textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
ConduitThemeExtension.darkPalette(palette: palette, tokens: tokens),
|
||||
AppPaletteThemeExtension(palette: palette),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static CupertinoThemeData cupertinoTheme(
|
||||
BuildContext context,
|
||||
AppColorPalette palette,
|
||||
TweakcnThemeDefinition theme,
|
||||
) {
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final tone = palette.toneFor(brightness);
|
||||
final variant = theme.variantFor(brightness);
|
||||
final tokens = brightness == Brightness.dark
|
||||
? AppColorTokens.dark(palette: palette)
|
||||
: AppColorTokens.light(palette: palette);
|
||||
? AppColorTokens.dark(theme: theme)
|
||||
: AppColorTokens.light(theme: theme);
|
||||
return CupertinoThemeData(
|
||||
brightness: brightness,
|
||||
primaryColor: tone.primary,
|
||||
primaryColor: variant.primary,
|
||||
scaffoldBackgroundColor: tokens.neutralTone10,
|
||||
barBackgroundColor: tokens.neutralTone10,
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData _buildTheme({
|
||||
required TweakcnThemeDefinition theme,
|
||||
required AppColorTokens tokens,
|
||||
required Brightness brightness,
|
||||
}) {
|
||||
final variant = theme.variantFor(brightness);
|
||||
final isDark = brightness == Brightness.dark;
|
||||
final typography = TypographyThemeExtension.fromVariant(variant);
|
||||
final surfaces = SurfaceThemeExtension.fromVariant(variant);
|
||||
final shadows = ShadowThemeExtension.standard();
|
||||
final shapes = ShapeThemeExtension.fromVariant(variant);
|
||||
final sidebar = SidebarThemeExtension.fromVariant(variant);
|
||||
final conduitExtension = ConduitThemeExtension.create(
|
||||
theme: theme,
|
||||
tokens: tokens,
|
||||
brightness: brightness,
|
||||
typography: typography,
|
||||
surfaces: surfaces,
|
||||
shadows: shadows,
|
||||
shapes: shapes,
|
||||
);
|
||||
final colorScheme = tokens.toColorScheme().copyWith(
|
||||
primary: variant.primary,
|
||||
onPrimary: _pickOnColor(variant.primary, tokens),
|
||||
secondary: variant.secondary,
|
||||
onSecondary: _pickOnColor(variant.secondary, tokens),
|
||||
tertiary: variant.accent,
|
||||
onTertiary: _pickOnColor(variant.accent, tokens),
|
||||
surfaceTint: variant.primary,
|
||||
);
|
||||
|
||||
final OutlineInputBorder baseInputBorder = OutlineInputBorder(
|
||||
borderRadius: shapes.medium,
|
||||
borderSide: BorderSide(
|
||||
color: isDark
|
||||
? Color.lerp(surfaces.border, surfaces.input, 0.6)!
|
||||
: Color.lerp(surfaces.border, surfaces.input, 0.4)!,
|
||||
width: 1,
|
||||
),
|
||||
);
|
||||
|
||||
final TextTheme baseTextTheme = brightness == Brightness.dark
|
||||
? ThemeData.dark().textTheme
|
||||
: ThemeData.light().textTheme;
|
||||
final TextTheme textTheme = baseTextTheme.apply(
|
||||
fontFamily: typography.primaryFont.isEmpty
|
||||
? null
|
||||
: typography.primaryFont,
|
||||
fontFamilyFallback: typography.primaryFallback.isEmpty
|
||||
? null
|
||||
: typography.primaryFallback,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
fontFamily: typography.primaryFont.isEmpty
|
||||
? null
|
||||
: typography.primaryFont,
|
||||
fontFamilyFallback: typography.primaryFallback.isEmpty
|
||||
? null
|
||||
: typography.primaryFallback,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: surfaces.background,
|
||||
canvasColor: surfaces.background,
|
||||
pageTransitionsTheme: _pageTransitionsTheme,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: true,
|
||||
elevation: Elevation.none,
|
||||
backgroundColor: surfaces.background,
|
||||
foregroundColor: tokens.neutralOnSurface,
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: surfaces.card,
|
||||
modalBackgroundColor: surfaces.card,
|
||||
surfaceTintColor: surfaces.card,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.extraLarge),
|
||||
showDragHandle: false,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.lg,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: variant.primary,
|
||||
foregroundColor: _pickOnColor(variant.primary, tokens),
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
elevation: Elevation.low,
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: surfaces.card,
|
||||
elevation: Elevation.low,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: shapes.large,
|
||||
side: BorderSide(color: surfaces.border),
|
||||
),
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: conduitExtension.statusPalette.info.base,
|
||||
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
||||
color: conduitExtension.statusPalette.info.onBase,
|
||||
),
|
||||
actionTextColor: conduitExtension.statusPalette.info.onBase,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
elevation: Elevation.low,
|
||||
insetPadding: const EdgeInsets.all(Spacing.md),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: conduitExtension.inputBackground,
|
||||
focusColor: surfaces.ring,
|
||||
hoverColor: Color.alphaBlend(
|
||||
shadows.shadowXs.first.color,
|
||||
conduitExtension.inputBackground,
|
||||
),
|
||||
border: baseInputBorder,
|
||||
enabledBorder: baseInputBorder,
|
||||
focusedBorder: baseInputBorder.copyWith(
|
||||
borderSide: BorderSide(color: surfaces.ring, width: 2),
|
||||
),
|
||||
errorBorder: baseInputBorder.copyWith(
|
||||
borderSide: BorderSide(color: tokens.statusError60, width: 1),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.md,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
),
|
||||
chipTheme: ChipThemeData(
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.sm,
|
||||
vertical: Spacing.xs,
|
||||
),
|
||||
backgroundColor: Color.lerp(surfaces.card, surfaces.muted, 0.4)!,
|
||||
disabledColor: Color.alphaBlend(
|
||||
shadows.shadowXs.first.color,
|
||||
surfaces.card,
|
||||
),
|
||||
selectedColor: conduitExtension.statusPalette.success.background,
|
||||
secondarySelectedColor: conduitExtension.statusPalette.info.background,
|
||||
shadowColor: shadows.shadowSm.first.color,
|
||||
selectedShadowColor: shadows.shadowSm.first.color,
|
||||
brightness: brightness,
|
||||
labelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: tokens.neutralOnSurface,
|
||||
),
|
||||
secondaryLabelStyle: textTheme.bodySmall?.copyWith(
|
||||
color: conduitExtension.statusPalette.info.onBase,
|
||||
),
|
||||
side: BorderSide(color: surfaces.border),
|
||||
),
|
||||
badgeTheme: BadgeThemeData(
|
||||
backgroundColor: conduitExtension.statusPalette.info.base,
|
||||
textColor: conduitExtension.statusPalette.info.onBase,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.xs,
|
||||
vertical: Spacing.xxs,
|
||||
),
|
||||
largeSize: 24,
|
||||
smallSize: 18,
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: surfaces.popover,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: Elevation.medium,
|
||||
shadowColor: shadows.shadowLg.first.color,
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.large),
|
||||
titleTextStyle: textTheme.titleLarge?.copyWith(
|
||||
color: surfaces.popoverForeground,
|
||||
),
|
||||
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
||||
color: tokens.neutralOnSurface,
|
||||
),
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
shape: RoundedRectangleBorder(borderRadius: shapes.medium),
|
||||
tileColor: Color.lerp(surfaces.card, surfaces.muted, 0.25),
|
||||
selectedTileColor: Color.alphaBlend(
|
||||
conduitExtension.statusPalette.info.background,
|
||||
surfaces.card,
|
||||
),
|
||||
iconColor: tokens.neutralTone80,
|
||||
textColor: tokens.neutralOnSurface,
|
||||
),
|
||||
textTheme: textTheme,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
tokens,
|
||||
typography,
|
||||
surfaces,
|
||||
shadows,
|
||||
shapes,
|
||||
sidebar,
|
||||
conduitExtension,
|
||||
AppPaletteThemeExtension(palette: theme),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static Color _pickOnColor(Color background, AppColorTokens tokens) {
|
||||
final contrastOnLight = _contrastRatio(background, tokens.neutralTone00);
|
||||
final contrastOnDark = _contrastRatio(background, tokens.neutralOnSurface);
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@immutable
|
||||
class AppPaletteTone {
|
||||
const AppPaletteTone({
|
||||
required this.primary,
|
||||
required this.secondary,
|
||||
required this.accent,
|
||||
});
|
||||
|
||||
final Color primary;
|
||||
final Color secondary;
|
||||
final Color accent;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppColorPalette {
|
||||
const AppColorPalette({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.description,
|
||||
required this.light,
|
||||
required this.dark,
|
||||
this.preview,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final String description;
|
||||
final AppPaletteTone light;
|
||||
final AppPaletteTone dark;
|
||||
final List<Color>? preview;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppPaletteThemeExtension
|
||||
extends ThemeExtension<AppPaletteThemeExtension> {
|
||||
const AppPaletteThemeExtension({required this.palette});
|
||||
|
||||
final AppColorPalette palette;
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension copyWith({AppColorPalette? palette}) {
|
||||
return AppPaletteThemeExtension(palette: palette ?? this.palette);
|
||||
}
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension lerp(
|
||||
covariant ThemeExtension<AppPaletteThemeExtension>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! AppPaletteThemeExtension) return this;
|
||||
return t < 0.5 ? this : other;
|
||||
}
|
||||
}
|
||||
|
||||
class AppColorPalettes {
|
||||
static const String defaultPaletteId = 'aurora_violet';
|
||||
|
||||
static const AppColorPalette auroraViolet = AppColorPalette(
|
||||
id: defaultPaletteId,
|
||||
label: 'Aurora Violet',
|
||||
description: 'Bold purples inspired by aurora skies.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFFA420FF),
|
||||
secondary: Color(0xFFB058FF),
|
||||
accent: Color(0xFFD9A5FF),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF9500FF),
|
||||
secondary: Color(0xFFC773FF),
|
||||
accent: Color(0xFFE3BDFF),
|
||||
),
|
||||
preview: [Color(0xFF9500FF), Color(0xFFA420FF), Color(0xFFB058FF)],
|
||||
);
|
||||
|
||||
static const AppColorPalette emeraldRush = AppColorPalette(
|
||||
id: 'emerald_rush',
|
||||
label: 'Emerald Rush',
|
||||
description: 'High-contrast greens with calm highlights.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFF0C7F48),
|
||||
secondary: Color(0xFF26A164),
|
||||
accent: Color(0xFF6DE0A4),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF40DD7F),
|
||||
secondary: Color(0xFF26A164),
|
||||
accent: Color(0xFF6DE0A4),
|
||||
),
|
||||
preview: [Color(0xFF0C7F48), Color(0xFF26A164), Color(0xFF40DD7F)],
|
||||
);
|
||||
|
||||
static const AppColorPalette azurePulse = AppColorPalette(
|
||||
id: 'azure_pulse',
|
||||
label: 'Azure Pulse',
|
||||
description: 'Electric blues with crisp highlights.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFF1B64DA),
|
||||
secondary: Color(0xFF2E7AF0),
|
||||
accent: Color(0xFF6DA6FF),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFF37C7FF),
|
||||
secondary: Color(0xFF2E7AF0),
|
||||
accent: Color(0xFF6DA6FF),
|
||||
),
|
||||
preview: [Color(0xFF1B64DA), Color(0xFF2E7AF0), Color(0xFF37C7FF)],
|
||||
);
|
||||
|
||||
static const AppColorPalette sunsetGlow = AppColorPalette(
|
||||
id: 'sunset_glow',
|
||||
label: 'Sunset Glow',
|
||||
description: 'Warm oranges for energetic interfaces.',
|
||||
light: AppPaletteTone(
|
||||
primary: Color(0xFFB83200),
|
||||
secondary: Color(0xFFE65100),
|
||||
accent: Color(0xFFFFA05B),
|
||||
),
|
||||
dark: AppPaletteTone(
|
||||
primary: Color(0xFFFF8A00),
|
||||
secondary: Color(0xFFE65100),
|
||||
accent: Color(0xFFFFA05B),
|
||||
),
|
||||
preview: [Color(0xFFB83200), Color(0xFFE65100), Color(0xFFFF8A00)],
|
||||
);
|
||||
|
||||
static const List<AppColorPalette> all = [
|
||||
auroraViolet,
|
||||
emeraldRush,
|
||||
azurePulse,
|
||||
sunsetGlow,
|
||||
];
|
||||
|
||||
static AppColorPalette byId(String? id) {
|
||||
return all.firstWhere(
|
||||
(palette) => palette.id == id,
|
||||
orElse: () => auroraViolet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension AppColorPaletteX on AppColorPalette {
|
||||
AppPaletteTone toneFor(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? dark : light;
|
||||
}
|
||||
|
||||
Color primaryFor(Brightness brightness) {
|
||||
return toneFor(brightness).primary;
|
||||
}
|
||||
|
||||
Color secondaryFor(Brightness brightness) {
|
||||
return toneFor(brightness).secondary;
|
||||
}
|
||||
|
||||
Color accentFor(Brightness brightness) {
|
||||
return toneFor(brightness).accent;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'color_palettes.dart';
|
||||
import 'tweakcn_themes.dart';
|
||||
|
||||
/// Immutable set of semantic color tokens exposed through [ThemeExtension].
|
||||
///
|
||||
@@ -92,136 +92,118 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
final Color codeText;
|
||||
final Color codeAccent;
|
||||
|
||||
factory AppColorTokens.light({AppColorPalette? palette}) {
|
||||
return AppColorTokens._fromPalette(
|
||||
palette ?? AppColorPalettes.auroraViolet,
|
||||
factory AppColorTokens.light({TweakcnThemeDefinition? theme}) {
|
||||
return AppColorTokens._fromTheme(
|
||||
theme ?? TweakcnThemes.conduit,
|
||||
Brightness.light,
|
||||
);
|
||||
}
|
||||
|
||||
factory AppColorTokens.dark({AppColorPalette? palette}) {
|
||||
return AppColorTokens._fromPalette(
|
||||
palette ?? AppColorPalettes.auroraViolet,
|
||||
factory AppColorTokens.dark({TweakcnThemeDefinition? theme}) {
|
||||
return AppColorTokens._fromTheme(
|
||||
theme ?? TweakcnThemes.conduit,
|
||||
Brightness.dark,
|
||||
);
|
||||
}
|
||||
|
||||
factory AppColorTokens._fromPalette(
|
||||
AppColorPalette palette,
|
||||
factory AppColorTokens._fromTheme(
|
||||
TweakcnThemeDefinition theme,
|
||||
Brightness brightness,
|
||||
) {
|
||||
final AppPaletteTone tone = palette.toneFor(brightness);
|
||||
|
||||
final TweakcnThemeVariant variant = theme.variantFor(brightness);
|
||||
final bool isLight = brightness == Brightness.light;
|
||||
|
||||
final Color neutralTone00 = isLight
|
||||
? const Color(0xFFFFFFFF)
|
||||
: const Color(0xFF0B0E14);
|
||||
final Color neutralTone10 = isLight
|
||||
? const Color(0xFFF5F7FA)
|
||||
: const Color(0xFF161B24);
|
||||
final Color neutralTone20 = isLight
|
||||
? const Color(0xFFE6EAF1)
|
||||
: const Color(0xFF1F2531);
|
||||
final Color neutralTone40 = isLight
|
||||
? const Color(0xFFC5CCD9)
|
||||
: const Color(0xFF343C4D);
|
||||
final Color neutralTone60 = isLight
|
||||
? const Color(0xFF9099AC)
|
||||
: const Color(0xFF4C566A);
|
||||
final Color neutralTone80 = isLight
|
||||
? const Color(0xFF4A5161)
|
||||
: const Color(0xFF8B95AA);
|
||||
final Color neutralOnSurface = isLight
|
||||
? const Color(0xFF151920)
|
||||
: const Color(0xFFE8ECF5);
|
||||
|
||||
final Color overlayWeak = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.08)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.08);
|
||||
final Color overlayMedium = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.16)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.16);
|
||||
final Color overlayStrong = isLight
|
||||
? const Color.fromRGBO(21, 25, 32, 0.32)
|
||||
: const Color.fromRGBO(232, 236, 245, 0.48);
|
||||
|
||||
final ColorScheme seedScheme = ColorScheme.fromSeed(
|
||||
seedColor: tone.primary,
|
||||
brightness: brightness,
|
||||
final Color neutralTone00 = variant.background;
|
||||
final Color neutralTone20 = variant.card;
|
||||
final Color neutralTone10 = mix(neutralTone00, neutralTone20, 0.5);
|
||||
final Color neutralTone40 = variant.muted;
|
||||
final Color neutralTone60 = mix(
|
||||
variant.mutedForeground,
|
||||
variant.foreground,
|
||||
isLight ? 0.25 : 0.4,
|
||||
);
|
||||
final Color neutralTone80 = mix(
|
||||
variant.foreground,
|
||||
isLight ? Colors.black : Colors.white,
|
||||
isLight ? 0.06 : 0.3,
|
||||
);
|
||||
final Color neutralOnSurface = _ensureContrast(
|
||||
surface: neutralTone00,
|
||||
foreground: variant.foreground,
|
||||
minContrast: 4.5,
|
||||
);
|
||||
|
||||
final Color brandTone60 = seedScheme.primary;
|
||||
final Color brandOn60 = _preferredOnColor(
|
||||
background: brandTone60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color brandTone60 = variant.primary;
|
||||
final Color brandOn60 = _ensureContrast(
|
||||
surface: brandTone60,
|
||||
foreground: variant.primaryForeground,
|
||||
);
|
||||
final Color brandTone90 = mix(
|
||||
variant.primary,
|
||||
neutralTone00,
|
||||
isLight ? 0.7 : 0.3,
|
||||
);
|
||||
final Color brandOn90 = _ensureContrast(
|
||||
surface: brandTone90,
|
||||
foreground: brandOn60,
|
||||
);
|
||||
final Color brandTone40 = mix(
|
||||
variant.primary,
|
||||
neutralOnSurface,
|
||||
isLight ? 0.35 : 0.55,
|
||||
);
|
||||
|
||||
final Color brandTone90 = seedScheme.primaryContainer;
|
||||
final Color brandOn90 = _preferredOnColor(
|
||||
background: brandTone90,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color accentIndigo60 = variant.secondary;
|
||||
final Color accentOnIndigo60 = _ensureContrast(
|
||||
surface: accentIndigo60,
|
||||
foreground: variant.secondaryForeground,
|
||||
);
|
||||
final Color accentTeal60 = variant.accent;
|
||||
final Color accentGold60 = mix(
|
||||
variant.accent,
|
||||
isLight ? Colors.white : Colors.black,
|
||||
isLight ? 0.18 : 0.24,
|
||||
);
|
||||
|
||||
final double brandShift = isLight ? 0.18 : -0.14;
|
||||
final Color brandTone40 = _shiftLightness(brandTone60, brandShift);
|
||||
|
||||
final Color accentIndigo60 = tone.secondary;
|
||||
final Color accentOnIndigo60 = _preferredOnColor(
|
||||
background: accentIndigo60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color statusError60 = variant.destructive;
|
||||
final Color statusOnError60 = _ensureContrast(
|
||||
surface: statusError60,
|
||||
foreground: variant.destructiveForeground,
|
||||
);
|
||||
final Color statusSuccess60 = variant.success;
|
||||
final Color statusOnSuccess60 = _ensureContrast(
|
||||
surface: statusSuccess60,
|
||||
foreground: variant.successForeground,
|
||||
);
|
||||
final Color statusWarning60 = variant.warning;
|
||||
final Color statusOnWarning60 = _ensureContrast(
|
||||
surface: statusWarning60,
|
||||
foreground: variant.warningForeground,
|
||||
);
|
||||
final Color statusInfo60 = variant.info;
|
||||
final Color statusOnInfo60 = _ensureContrast(
|
||||
surface: statusInfo60,
|
||||
foreground: variant.infoForeground,
|
||||
);
|
||||
|
||||
final Color accentTeal60 = tone.accent;
|
||||
final Color accentGold60 = isLight
|
||||
? const Color(0xFFFFB54A)
|
||||
: const Color(0xFFFFC266);
|
||||
|
||||
final Color statusSuccess60 = isLight
|
||||
? const Color(0xFF0E9D58)
|
||||
: const Color(0xFF23C179);
|
||||
final Color statusOnSuccess60 = _preferredOnColor(
|
||||
background: statusSuccess60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color overlayWeak = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.08 : 0.12,
|
||||
);
|
||||
final Color overlayMedium = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.16 : 0.2,
|
||||
);
|
||||
final Color overlayStrong = neutralOnSurface.withValues(
|
||||
alpha: isLight ? 0.32 : 0.36,
|
||||
);
|
||||
|
||||
final Color statusWarning60 = isLight
|
||||
? const Color(0xFFDB7900)
|
||||
: const Color(0xFFFF9800);
|
||||
final Color statusOnWarning60 = _preferredOnColor(
|
||||
background: statusWarning60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
final Color codeBackground = mix(variant.muted, neutralTone00, 0.5);
|
||||
final Color codeBorder = mix(variant.border, neutralTone40, 0.6);
|
||||
final Color codeText = _ensureContrast(
|
||||
surface: codeBackground,
|
||||
foreground: neutralOnSurface,
|
||||
minContrast: 4.5,
|
||||
);
|
||||
|
||||
final Color statusError60 = isLight
|
||||
? const Color(0xFFCE2C31)
|
||||
: const Color(0xFFFF5F67);
|
||||
final Color statusOnError60 = _preferredOnColor(
|
||||
background: statusError60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
);
|
||||
|
||||
final Color statusInfo60 = isLight
|
||||
? const Color(0xFF0174D3)
|
||||
: const Color(0xFF4CA8FF);
|
||||
final Color statusOnInfo60 = _preferredOnColor(
|
||||
background: statusInfo60,
|
||||
light: neutralTone00,
|
||||
dark: neutralOnSurface,
|
||||
);
|
||||
|
||||
final Color codeBackground = isLight ? neutralTone10 : neutralTone00;
|
||||
final Color codeBorder = isLight ? neutralTone20 : neutralTone40;
|
||||
final Color codeText = neutralOnSurface;
|
||||
final Color codeAccent = isLight
|
||||
? Color.alphaBlend(brandTone60.withValues(alpha: 0.14), codeBackground)
|
||||
: Color.alphaBlend(brandTone40.withValues(alpha: 0.24), codeBackground);
|
||||
final Color codeAccent = mix(variant.accent, variant.primary, 0.4);
|
||||
|
||||
return AppColorTokens(
|
||||
brightness: brightness,
|
||||
@@ -406,7 +388,10 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
secondary: accentIndigo60,
|
||||
onSecondary: accentOnIndigo60,
|
||||
tertiary: accentTeal60,
|
||||
onTertiary: neutralTone00,
|
||||
onTertiary: _ensureContrast(
|
||||
surface: accentTeal60,
|
||||
foreground: neutralTone00,
|
||||
),
|
||||
surface: neutralTone00,
|
||||
surfaceContainerLow: neutralTone10,
|
||||
surfaceContainerHighest: neutralTone20,
|
||||
@@ -433,20 +418,24 @@ class AppColorTokens extends ThemeExtension<AppColorTokens> {
|
||||
: AppColorTokens.light();
|
||||
}
|
||||
|
||||
static Color _shiftLightness(Color color, double amount) {
|
||||
final HSLColor hsl = HSLColor.fromColor(color);
|
||||
final double lightness = (hsl.lightness + amount).clamp(0.0, 1.0);
|
||||
return hsl.withLightness(lightness).toColor();
|
||||
}
|
||||
|
||||
static Color _preferredOnColor({
|
||||
required Color background,
|
||||
required Color light,
|
||||
required Color dark,
|
||||
static Color _ensureContrast({
|
||||
required Color surface,
|
||||
required Color foreground,
|
||||
double minContrast = 4.5,
|
||||
}) {
|
||||
final double lightContrast = _contrastRatio(background, light);
|
||||
final double darkContrast = _contrastRatio(background, dark);
|
||||
return lightContrast >= darkContrast ? light : dark;
|
||||
if (_contrastRatio(surface, foreground) >= minContrast) {
|
||||
return foreground;
|
||||
}
|
||||
final bool surfaceIsDark = surface.computeLuminance() < 0.5;
|
||||
final Color target = surfaceIsDark ? Colors.white : Colors.black;
|
||||
Color candidate = foreground;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
candidate = Color.lerp(candidate, target, 0.3)!;
|
||||
if (_contrastRatio(surface, candidate) >= minContrast) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
static double _contrastRatio(Color a, Color b) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
386
lib/shared/theme/tweakcn_themes.dart
Normal file
386
lib/shared/theme/tweakcn_themes.dart
Normal file
@@ -0,0 +1,386 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Represents a single tweakcn theme variant (light or dark) and exposes the
|
||||
/// standard set of color tokens defined by the registry.
|
||||
@immutable
|
||||
class TweakcnThemeVariant {
|
||||
const TweakcnThemeVariant({
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
required this.card,
|
||||
required this.cardForeground,
|
||||
required this.popover,
|
||||
required this.popoverForeground,
|
||||
required this.primary,
|
||||
required this.primaryForeground,
|
||||
required this.secondary,
|
||||
required this.secondaryForeground,
|
||||
required this.muted,
|
||||
required this.mutedForeground,
|
||||
required this.accent,
|
||||
required this.accentForeground,
|
||||
required this.destructive,
|
||||
required this.destructiveForeground,
|
||||
required this.border,
|
||||
required this.input,
|
||||
required this.ring,
|
||||
required this.sidebarBackground,
|
||||
required this.sidebarForeground,
|
||||
required this.sidebarPrimary,
|
||||
required this.sidebarPrimaryForeground,
|
||||
required this.sidebarAccent,
|
||||
required this.sidebarAccentForeground,
|
||||
required this.sidebarBorder,
|
||||
required this.sidebarRing,
|
||||
required this.success,
|
||||
required this.successForeground,
|
||||
required this.warning,
|
||||
required this.warningForeground,
|
||||
required this.info,
|
||||
required this.infoForeground,
|
||||
this.radius = 16,
|
||||
this.fontSans = const <String>[],
|
||||
this.fontSerif = const <String>[],
|
||||
this.fontMono = const <String>[],
|
||||
});
|
||||
|
||||
final Color background;
|
||||
final Color foreground;
|
||||
final Color card;
|
||||
final Color cardForeground;
|
||||
final Color popover;
|
||||
final Color popoverForeground;
|
||||
final Color primary;
|
||||
final Color primaryForeground;
|
||||
final Color secondary;
|
||||
final Color secondaryForeground;
|
||||
final Color muted;
|
||||
final Color mutedForeground;
|
||||
final Color accent;
|
||||
final Color accentForeground;
|
||||
final Color destructive;
|
||||
final Color destructiveForeground;
|
||||
final Color border;
|
||||
final Color input;
|
||||
final Color ring;
|
||||
final Color sidebarBackground;
|
||||
final Color sidebarForeground;
|
||||
final Color sidebarPrimary;
|
||||
final Color sidebarPrimaryForeground;
|
||||
final Color sidebarAccent;
|
||||
final Color sidebarAccentForeground;
|
||||
final Color sidebarBorder;
|
||||
final Color sidebarRing;
|
||||
final Color success;
|
||||
final Color successForeground;
|
||||
final Color warning;
|
||||
final Color warningForeground;
|
||||
final Color info;
|
||||
final Color infoForeground;
|
||||
final double radius;
|
||||
final List<String> fontSans;
|
||||
final List<String> fontSerif;
|
||||
final List<String> fontMono;
|
||||
}
|
||||
|
||||
/// Definition of a tweakcn theme that provides both light and dark variants.
|
||||
@immutable
|
||||
class TweakcnThemeDefinition {
|
||||
const TweakcnThemeDefinition({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.description,
|
||||
required this.light,
|
||||
required this.dark,
|
||||
required this.preview,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String label;
|
||||
final String description;
|
||||
final TweakcnThemeVariant light;
|
||||
final TweakcnThemeVariant dark;
|
||||
final List<Color> preview;
|
||||
|
||||
TweakcnThemeVariant variantFor(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? dark : light;
|
||||
}
|
||||
}
|
||||
|
||||
Color mix(Color a, Color b, double amount) {
|
||||
return Color.lerp(a, b, amount.clamp(0.0, 1.0)) ?? a;
|
||||
}
|
||||
|
||||
class TweakcnThemes {
|
||||
static final TweakcnThemeVariant _conduitLight = TweakcnThemeVariant(
|
||||
background: const Color(0xFFFFFFFF),
|
||||
foreground: const Color(0xFF0A0A0A),
|
||||
card: const Color(0xFFFFFFFF),
|
||||
cardForeground: const Color(0xFF0A0A0A),
|
||||
popover: const Color(0xFFFFFFFF),
|
||||
popoverForeground: const Color(0xFF0A0A0A),
|
||||
primary: const Color(0xFF171717),
|
||||
primaryForeground: const Color(0xFFFAFAFA),
|
||||
secondary: const Color(0xFFF5F5F5),
|
||||
secondaryForeground: const Color(0xFF171717),
|
||||
muted: const Color(0xFFF5F5F5),
|
||||
mutedForeground: const Color(0xFF737373),
|
||||
accent: const Color(0xFFF5F5F5),
|
||||
accentForeground: const Color(0xFF171717),
|
||||
destructive: const Color(0xFFE7000B),
|
||||
destructiveForeground: const Color(0xFFFAFAFA),
|
||||
border: const Color(0xFFE5E5E5),
|
||||
input: const Color(0xFFE5E5E5),
|
||||
ring: const Color(0xFFA1A1A1),
|
||||
sidebarBackground: const Color(0xFFFAFAFA),
|
||||
sidebarForeground: const Color(0xFF0A0A0A),
|
||||
sidebarPrimary: const Color(0xFF171717),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAFAFA),
|
||||
sidebarAccent: const Color(0xFFF5F5F5),
|
||||
sidebarAccentForeground: const Color(0xFF171717),
|
||||
sidebarBorder: const Color(0xFFE5E5E5),
|
||||
sidebarRing: const Color(0xFFA1A1A1),
|
||||
success: const Color(0xFF00E6C7),
|
||||
successForeground: const Color(0xFF09090B),
|
||||
warning: const Color(0xFFF97316),
|
||||
warningForeground: const Color(0xFF09090B),
|
||||
info: const Color(0xFF2563EB),
|
||||
infoForeground: const Color(0xFFFAFAFA),
|
||||
radius: 10,
|
||||
fontSans: const <String>[
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Helvetica Neue',
|
||||
'Arial',
|
||||
'Noto Sans',
|
||||
'sans-serif',
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji',
|
||||
],
|
||||
fontSerif: const <String>[
|
||||
'ui-serif',
|
||||
'Georgia',
|
||||
'Cambria',
|
||||
'Times New Roman',
|
||||
'Times',
|
||||
'serif',
|
||||
],
|
||||
fontMono: const <String>[
|
||||
'ui-monospace',
|
||||
'SFMono-Regular',
|
||||
'SF Mono',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _conduitDark = TweakcnThemeVariant(
|
||||
background: const Color(0xFF0A0A0A),
|
||||
foreground: const Color(0xFFFAFAFA),
|
||||
card: const Color(0xFF171717),
|
||||
cardForeground: const Color(0xFFFAFAFA),
|
||||
popover: const Color(0xFF262626),
|
||||
popoverForeground: const Color(0xFFFAFAFA),
|
||||
primary: const Color(0xFFE5E5E5),
|
||||
primaryForeground: const Color(0xFF171717),
|
||||
secondary: const Color(0xFF262626),
|
||||
secondaryForeground: const Color(0xFFFAFAFA),
|
||||
muted: const Color(0xFF262626),
|
||||
mutedForeground: const Color(0xFFA1A1AA),
|
||||
accent: const Color(0xFF404040),
|
||||
accentForeground: const Color(0xFFFAFAFA),
|
||||
destructive: const Color(0xFFFF6467),
|
||||
destructiveForeground: const Color(0xFFFAFAFA),
|
||||
border: const Color(0xFF282828),
|
||||
input: const Color(0xFF343434),
|
||||
ring: const Color(0xFF737373),
|
||||
sidebarBackground: const Color(0xFF171717),
|
||||
sidebarForeground: const Color(0xFFFAFAFA),
|
||||
sidebarPrimary: const Color(0xFF1447E6),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAFAFA),
|
||||
sidebarAccent: const Color(0xFF262626),
|
||||
sidebarAccentForeground: const Color(0xFFFAFAFA),
|
||||
sidebarBorder: const Color(0xFF282828),
|
||||
sidebarRing: const Color(0xFF525252),
|
||||
success: const Color(0xFF00E6C7),
|
||||
successForeground: const Color(0xFF09090B),
|
||||
warning: const Color(0xFFF97316),
|
||||
warningForeground: const Color(0xFF09090B),
|
||||
info: const Color(0xFF2563EB),
|
||||
infoForeground: const Color(0xFFFAFAFA),
|
||||
radius: 10,
|
||||
fontSans: const <String>[
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'Segoe UI',
|
||||
'Roboto',
|
||||
'Helvetica Neue',
|
||||
'Arial',
|
||||
'Noto Sans',
|
||||
'sans-serif',
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji',
|
||||
],
|
||||
fontSerif: const <String>[
|
||||
'ui-serif',
|
||||
'Georgia',
|
||||
'Cambria',
|
||||
'Times New Roman',
|
||||
'Times',
|
||||
'serif',
|
||||
],
|
||||
fontMono: const <String>[
|
||||
'ui-monospace',
|
||||
'SFMono-Regular',
|
||||
'SF Mono',
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'Liberation Mono',
|
||||
'Courier New',
|
||||
'monospace',
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _t3ChatLight = TweakcnThemeVariant(
|
||||
background: const Color(0xFFFAF5FA),
|
||||
foreground: const Color(0xFF501854),
|
||||
card: const Color(0xFFFAF5FA),
|
||||
cardForeground: const Color(0xFF501854),
|
||||
popover: const Color(0xFFFFFFFF),
|
||||
popoverForeground: const Color(0xFF501854),
|
||||
primary: const Color(0xFFA84370),
|
||||
primaryForeground: const Color(0xFFFFFFFF),
|
||||
secondary: const Color(0xFFF1C4E6),
|
||||
secondaryForeground: const Color(0xFF77347C),
|
||||
muted: const Color(0xFFF6E5F3),
|
||||
mutedForeground: const Color(0xFF834588),
|
||||
accent: const Color(0xFFF1C4E6),
|
||||
accentForeground: const Color(0xFF77347C),
|
||||
destructive: const Color(0xFFAB4347),
|
||||
destructiveForeground: const Color(0xFFFFFFFF),
|
||||
border: const Color(0xFFEFBDEB),
|
||||
input: const Color(0xFFE7C1DC),
|
||||
ring: const Color(0xFFDB2777),
|
||||
sidebarBackground: const Color(0xFFF3E4F6),
|
||||
sidebarForeground: const Color(0xFFAC1668),
|
||||
sidebarPrimary: const Color(0xFF454554),
|
||||
sidebarPrimaryForeground: const Color(0xFFFAF1F7),
|
||||
sidebarAccent: const Color(0xFFF8F8F7),
|
||||
sidebarAccentForeground: const Color(0xFF454554),
|
||||
sidebarBorder: const Color(0xFFECEAE9),
|
||||
sidebarRing: const Color(0xFFDB2777),
|
||||
success: const Color(0xFFF4A462),
|
||||
successForeground: const Color(0xFF501854),
|
||||
warning: const Color(0xFFE8C468),
|
||||
warningForeground: const Color(0xFF501854),
|
||||
info: const Color(0xFF6C12B9),
|
||||
infoForeground: const Color(0xFFF8F1F5),
|
||||
radius: 8,
|
||||
);
|
||||
|
||||
static final TweakcnThemeVariant _t3ChatDark = TweakcnThemeVariant(
|
||||
background: const Color(0xFF221D27),
|
||||
foreground: const Color(0xFFD2C4DE),
|
||||
card: const Color(0xFF2C2632),
|
||||
cardForeground: const Color(0xFFDBC5D2),
|
||||
popover: const Color(0xFF100A0E),
|
||||
popoverForeground: const Color(0xFFF8F1F5),
|
||||
primary: const Color(0xFFA3004C),
|
||||
primaryForeground: const Color(0xFFEFC0D8),
|
||||
secondary: const Color(0xFF362D3D),
|
||||
secondaryForeground: const Color(0xFFD4C7E1),
|
||||
muted: const Color(0xFF28222D),
|
||||
mutedForeground: const Color(0xFFC2B6CF),
|
||||
accent: const Color(0xFF463753),
|
||||
accentForeground: const Color(0xFFF8F1F5),
|
||||
destructive: const Color(0xFF301015),
|
||||
destructiveForeground: const Color(0xFFFFFFFF),
|
||||
border: const Color(0xFF3B3237),
|
||||
input: const Color(0xFF3E343C),
|
||||
ring: const Color(0xFFDB2777),
|
||||
sidebarBackground: const Color(0xFF181117),
|
||||
sidebarForeground: const Color(0xFFE0CAD6),
|
||||
sidebarPrimary: const Color(0xFF1D4ED8),
|
||||
sidebarPrimaryForeground: const Color(0xFFFFFFFF),
|
||||
sidebarAccent: const Color(0xFF261922),
|
||||
sidebarAccentForeground: const Color(0xFFF4F4F5),
|
||||
sidebarBorder: const Color(0xFF000000),
|
||||
sidebarRing: const Color(0xFFDB2777),
|
||||
success: const Color(0xFFE88C30),
|
||||
successForeground: const Color(0xFF181117),
|
||||
warning: const Color(0xFFAF57DB),
|
||||
warningForeground: const Color(0xFF181117),
|
||||
info: const Color(0xFF934DCB),
|
||||
infoForeground: const Color(0xFFF8F1F5),
|
||||
radius: 8,
|
||||
);
|
||||
|
||||
static final TweakcnThemeDefinition t3Chat = TweakcnThemeDefinition(
|
||||
id: 't3_chat',
|
||||
label: 'T3 Chat',
|
||||
description: 'Playful gradients inspired by the T3 Stack brand.',
|
||||
light: _t3ChatLight,
|
||||
dark: _t3ChatDark,
|
||||
preview: const <Color>[
|
||||
Color(0xFFA84370),
|
||||
Color(0xFFF1C4E6),
|
||||
Color(0xFFDB2777),
|
||||
],
|
||||
);
|
||||
|
||||
static final TweakcnThemeDefinition conduit = TweakcnThemeDefinition(
|
||||
id: 'conduit',
|
||||
label: 'Conduit',
|
||||
description: 'Clean neutral theme designed for Conduit.',
|
||||
light: _conduitLight,
|
||||
dark: _conduitDark,
|
||||
preview: const <Color>[
|
||||
Color(0xFFA1A1AA),
|
||||
Color(0xFFF4F4F5),
|
||||
Color(0xFF2563EB),
|
||||
],
|
||||
);
|
||||
|
||||
static List<TweakcnThemeDefinition> all = [conduit, t3Chat];
|
||||
|
||||
static TweakcnThemeDefinition byId(String? id) {
|
||||
return all.firstWhere((theme) => theme.id == id, orElse: () => conduit);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppPaletteThemeExtension
|
||||
extends ThemeExtension<AppPaletteThemeExtension> {
|
||||
const AppPaletteThemeExtension({required this.palette});
|
||||
|
||||
final TweakcnThemeDefinition palette;
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension copyWith({TweakcnThemeDefinition? palette}) {
|
||||
return AppPaletteThemeExtension(palette: palette ?? this.palette);
|
||||
}
|
||||
|
||||
@override
|
||||
AppPaletteThemeExtension lerp(
|
||||
covariant ThemeExtension<AppPaletteThemeExtension>? other,
|
||||
double t,
|
||||
) {
|
||||
if (other is! AppPaletteThemeExtension) return this;
|
||||
return t < 0.5 ? this : other;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user