feat: implement dynamic theme palette selection

- Introduced a new feature allowing users to select from multiple accent color palettes for buttons, cards, and chat bubbles.
- Added `AppThemePalette` provider to manage the current theme palette and persist user selections.
- Updated the `AppTheme` class to utilize the selected palette for light and dark themes, enhancing visual customization.
- Enhanced the `AppCustomizationPage` to include a palette selector, improving user experience and personalization options.
- Updated localization files to support new palette selection UI elements in multiple languages.
This commit is contained in:
cogwheel0
2025-10-02 01:58:12 +05:30
parent 5eb23b01de
commit 1fa8412e0a
23 changed files with 1011 additions and 577 deletions

View File

@@ -2,13 +2,9 @@ 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';
class AppTheme {
// Brand accents tuned for WCAG contrast in light/dark modes
static const Color brandPrimary = Color(0xFFA420FF); // Light mode primary
static const Color brandPrimaryLight = Color(0xFFC773FF); // Light accents
static const Color brandPrimaryDark = Color(0xFF9500FF); // Dark mode primary
// Enhanced neutral palette for better contrast (WCAG AA compliant)
static const Color neutral900 = Color(0xFF000000); // Pure black
static const Color neutral800 = Color(
@@ -35,298 +31,219 @@ class AppTheme {
static const Color info = Color(0xFF0284C7); // Better blue contrast
static const Color infoDark = Color(0xFF0369A1); // Dark theme blue
// Brand aliases
static const Color primaryColor = brandPrimary;
static const Color secondaryColor = brandPrimaryLight;
static const Color surfaceColor = neutral50;
static const Color errorColor = error;
static const Color successColor = success;
static ThemeData light(AppColorPalette palette) {
final lightTone = palette.light;
// Base Light Theme
static ThemeData lightTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: const ColorScheme.light(
primary: brandPrimary,
secondary: brandPrimaryLight,
surface: surfaceColor,
error: errorColor,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
},
),
splashFactory: NoSplash.splashFactory,
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral900,
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: neutral50,
modalBackgroundColor: neutral50,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: ColorScheme.light(
primary: lightTone.primary,
secondary: lightTone.secondary,
surface: neutral50,
error: error,
).copyWith(surfaceContainerHighest: const Color(0xFFF0F1F1)),
pageTransitionsTheme: _pageTransitionsTheme,
splashFactory: NoSplash.splashFactory,
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral800,
),
showDragHandle: false,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.lg,
vertical: Spacing.xs,
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: neutral50,
modalBackgroundColor: neutral50,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderRadius: BorderRadius.circular(AppBorderRadius.modal),
),
showDragHandle: false,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.lg,
vertical: Spacing.xs,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
),
),
),
),
cardTheme: CardThemeData(
elevation: Elevation.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
side: BorderSide(color: neutral200),
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: neutral900.withValues(alpha: 0.92),
contentTextStyle: const TextStyle(
color: neutral50,
).copyWith(fontSize: AppTypography.bodyMedium),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
),
elevation: Elevation.high,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral50,
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: const BorderSide(color: primaryColor, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: errorColor, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
// Use platform default system font text theme
textTheme: ThemeData.light().textTheme,
extensions: const [ConduitThemeExtension.light],
);
// Base Dark Theme
static ThemeData darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: Color(0xFF0A0D0C),
colorScheme: const ColorScheme.dark(
primary: brandPrimaryDark,
secondary: brandPrimary,
surface: Color(0xFF0A0D0C),
surfaceContainerHighest: neutral700,
onSurface: neutral50,
onSurfaceVariant: neutral300,
outline: neutral600,
error: error,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
},
),
splashFactory: NoSplash.splashFactory,
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral50,
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: neutral900,
modalBackgroundColor: neutral900,
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,
),
cardTheme: CardThemeData(
elevation: Elevation.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
side: BorderSide(color: neutral200),
),
),
),
cardTheme: CardThemeData(
elevation: Elevation.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
side: BorderSide(color: neutral800),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: neutral900.withValues(alpha: 0.92),
contentTextStyle: const TextStyle(
color: neutral50,
).copyWith(fontSize: AppTypography.bodyMedium),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
),
elevation: Elevation.high,
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: neutral800.withValues(alpha: 0.92),
contentTextStyle: const TextStyle(
color: neutral50,
).copyWith(fontSize: AppTypography.bodyMedium),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral50,
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: const BorderSide(color: error, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
elevation: Elevation.high,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral700,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: brandPrimaryDark, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: error, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
// Use platform default system font text theme
textTheme: ThemeData.dark().textTheme,
extensions: const [ConduitThemeExtension.dark],
);
// Conduit variants using brand colors
static ThemeData conduitLightTheme = lightTheme.copyWith(
colorScheme: lightTheme.colorScheme.copyWith(
primary: brandPrimary,
secondary: brandPrimaryLight,
surface: neutral50,
),
extensions: const [ConduitThemeExtension.light],
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral800,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral50,
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: const BorderSide(color: brandPrimary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: error, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
);
static ThemeData conduitDarkTheme = darkTheme.copyWith(
scaffoldBackgroundColor: const Color(0xFF0A0D0C),
colorScheme: darkTheme.colorScheme.copyWith(
primary: brandPrimaryDark,
secondary: brandPrimary,
surface: const Color(0xFF0A0D0C),
surfaceContainerHighest: neutral700,
),
extensions: const [ConduitThemeExtension.dark],
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral50,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral700,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: brandPrimaryDark, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: error, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
);
// Classic Conduit variants for runtime switching
// Removed classic Conduit variants from public API to keep Aurora only
// Platform-specific theming helpers
static CupertinoThemeData cupertinoTheme(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return CupertinoThemeData(
brightness: isDark ? Brightness.dark : Brightness.light,
primaryColor: isDark ? brandPrimaryDark : brandPrimary,
scaffoldBackgroundColor: isDark ? neutral900 : neutral50,
barBackgroundColor: isDark ? neutral900 : neutral50,
textTheme: ThemeData.light().textTheme,
extensions: <ThemeExtension<dynamic>>[
ConduitThemeExtension.lightPalette(palette),
AppPaletteThemeExtension(palette: palette),
],
);
}
static ThemeData dark(AppColorPalette palette) {
final darkTone = palette.dark;
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: const Color(0xFF0A0D0C),
colorScheme: ColorScheme.dark(
primary: darkTone.primary,
secondary: darkTone.secondary,
surface: const Color(0xFF0A0D0C),
surfaceContainerHighest: neutral700,
onSurface: neutral50,
onSurfaceVariant: neutral300,
outline: neutral600,
error: error,
),
pageTransitionsTheme: _pageTransitionsTheme,
splashFactory: NoSplash.splashFactory,
appBarTheme: const AppBarTheme(
centerTitle: true,
elevation: Elevation.none,
backgroundColor: Colors.transparent,
foregroundColor: neutral50,
),
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: neutral900,
modalBackgroundColor: neutral900,
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,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
),
),
),
cardTheme: CardThemeData(
elevation: Elevation.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.lg),
side: BorderSide(color: neutral800),
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: neutral800.withValues(alpha: 0.92),
contentTextStyle: const TextStyle(
color: neutral50,
).copyWith(fontSize: AppTypography.bodyMedium),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.snackbar),
),
elevation: Elevation.high,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: neutral700,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: neutral600, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: BorderSide(color: darkTone.primary, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppBorderRadius.md),
borderSide: const BorderSide(color: error, width: 1),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.sm,
),
),
textTheme: ThemeData.dark().textTheme,
extensions: <ThemeExtension<dynamic>>[
ConduitThemeExtension.darkPalette(palette),
AppPaletteThemeExtension(palette: palette),
],
);
}
static CupertinoThemeData cupertinoTheme(
BuildContext context,
AppColorPalette palette,
) {
final brightness = Theme.of(context).brightness;
final tone = palette.toneFor(brightness);
return CupertinoThemeData(
brightness: brightness,
primaryColor: tone.primary,
scaffoldBackgroundColor: brightness == Brightness.dark
? neutral900
: neutral50,
barBackgroundColor: brightness == Brightness.dark
? neutral900
: neutral50,
);
}
static const PageTransitionsTheme _pageTransitionsTheme =
PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: ZoomPageTransitionsBuilder(),
TargetPlatform.linux: ZoomPageTransitionsBuilder(),
TargetPlatform.macOS: ZoomPageTransitionsBuilder(),
TargetPlatform.windows: ZoomPageTransitionsBuilder(),
},
);
}
/// Animated theme wrapper for smooth theme transitions

View File

@@ -0,0 +1,159 @@
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;
}
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
// Using system fonts; no GoogleFonts dependency required
import 'app_theme.dart';
import 'color_palettes.dart';
/// Extended theme data for consistent styling across the app
@immutable
@@ -501,263 +502,263 @@ class ConduitThemeExtension extends ThemeExtension<ConduitThemeExtension> {
);
}
/// Dark theme extension
static const ConduitThemeExtension dark = ConduitThemeExtension(
// Chat-specific colors - Enhanced for production-grade look
chatBubbleUser: AppTheme.brandPrimaryDark,
chatBubbleAssistant: Color(0xFF0E1010),
chatBubbleUserText: AppTheme.neutral50,
chatBubbleAssistantText: AppTheme.neutral50,
chatBubbleUserBorder: AppTheme.brandPrimaryDark,
chatBubbleAssistantBorder: Color(0xFF1A1D1C),
// Input and form colors
inputBackground: Color(0xFF141615),
inputBorder: AppTheme.neutral600,
inputBorderFocused: AppTheme.brandPrimaryDark,
inputText: AppTheme.neutral50,
inputPlaceholder: AppTheme.neutral300,
inputError: AppTheme.error,
/// Dark theme extension derived from the active color palette.
static ConduitThemeExtension darkPalette(AppColorPalette palette) {
final darkTone = palette.dark;
return ConduitThemeExtension(
chatBubbleUser: darkTone.primary,
chatBubbleAssistant: const Color(0xFF0E1010),
chatBubbleUserText: AppTheme.neutral50,
chatBubbleAssistantText: AppTheme.neutral50,
chatBubbleUserBorder: darkTone.secondary,
chatBubbleAssistantBorder: const Color(0xFF1A1D1C),
inputBackground: const Color(0xFF141615),
inputBorder: AppTheme.neutral600,
inputBorderFocused: darkTone.primary,
inputText: AppTheme.neutral50,
inputPlaceholder: AppTheme.neutral300,
inputError: AppTheme.error,
cardBackground: const Color(0xFF0C0F0E),
cardBorder: const Color(0xFF151918),
cardShadow: AppTheme.neutral900,
surfaceBackground: const Color(0xFF0A0D0C),
surfaceContainer: const Color(0xFF0C0F0E),
surfaceContainerHighest: const Color(0xFF121514),
buttonPrimary: darkTone.primary,
buttonPrimaryText: AppTheme.neutral50,
buttonSecondary: const Color(0xFF151918),
buttonSecondaryText: AppTheme.neutral50,
buttonDisabled: AppTheme.neutral600,
buttonDisabledText: AppTheme.neutral400,
success: const Color(0xFF34D399),
successBackground: const Color(0xFF14532D),
error: const Color(0xFFFCA5A5),
errorBackground: const Color(0xFF7F1D1D),
warning: const Color(0xFFFBBF24),
warningBackground: const Color(0xFF451A03),
info: const Color(0xFF93C5FD),
infoBackground: const Color(0xFF0C4A6E),
dividerColor: AppTheme.neutral600,
navigationBackground: const Color(0xFF0A0D0C),
navigationSelected: darkTone.primary,
navigationUnselected: AppTheme.neutral300,
navigationSelectedBackground: _surfaceTint(
darkTone.primary,
const Color(0xFF0A0D0C),
0.24,
),
shimmerBase: const Color(0xFF121514),
shimmerHighlight: const Color(0xFF1A1D1C),
loadingIndicator: darkTone.primary,
textPrimary: AppTheme.neutral50,
textSecondary: const Color(0xFFBAC2C0),
textTertiary: AppTheme.neutral400,
textInverse: AppTheme.neutral900,
textDisabled: AppTheme.neutral600,
iconPrimary: AppTheme.neutral50,
iconSecondary: const Color(0xFFA0A8A5),
iconDisabled: AppTheme.neutral600,
iconInverse: AppTheme.neutral900,
headingLarge: TextStyle(
fontSize: AppTypography.displaySmall,
fontWeight: FontWeight.w700,
color: AppTheme.neutral50,
height: 1.2,
),
headingMedium: TextStyle(
fontSize: AppTypography.headlineLarge,
fontWeight: FontWeight.w600,
color: AppTheme.neutral50,
height: 1.3,
),
headingSmall: TextStyle(
fontSize: AppTypography.headlineSmall,
fontWeight: FontWeight.w600,
color: AppTheme.neutral50,
height: 1.4,
),
bodyLarge: TextStyle(
fontSize: AppTypography.bodyLarge,
fontWeight: FontWeight.w400,
color: AppTheme.neutral50,
height: 1.5,
),
bodyMedium: TextStyle(
fontSize: AppTypography.bodyMedium,
fontWeight: FontWeight.w400,
color: AppTheme.neutral50,
height: 1.5,
),
bodySmall: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: const Color(0xFFD1D5DB),
height: 1.4,
),
caption: TextStyle(
fontSize: AppTypography.labelMedium,
fontWeight: FontWeight.w500,
color: AppTheme.neutral300,
height: 1.3,
letterSpacing: 0.5,
),
label: TextStyle(
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
color: const Color(0xFFD1D5DB),
height: 1.3,
),
code: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: const Color(0xFFD1D5DB),
height: 1.4,
fontFamily: AppTypography.monospaceFontFamily,
),
);
}
// Card and surface colors - Enhanced depth and hierarchy
cardBackground: Color(0xFF0C0F0E),
cardBorder: Color(0xFF151918),
cardShadow: AppTheme.neutral900,
surfaceBackground: Color(0xFF0A0D0C),
surfaceContainer: Color(0xFF0C0F0E),
surfaceContainerHighest: Color(0xFF121514),
/// Light theme extension derived from the active color palette.
static ConduitThemeExtension lightPalette(AppColorPalette palette) {
final lightTone = palette.light;
final darkTone = palette.dark;
return ConduitThemeExtension(
chatBubbleUser: lightTone.primary,
chatBubbleAssistant: const Color(0xFFF7F7F7),
chatBubbleUserText: AppTheme.neutral50,
chatBubbleAssistantText: const Color(0xFF1C1C1C),
chatBubbleUserBorder: darkTone.primary,
chatBubbleAssistantBorder: const Color(0xFFE7E7E7),
inputBackground: AppTheme.neutral50,
inputBorder: AppTheme.neutral200,
inputBorderFocused: lightTone.primary,
inputText: AppTheme.neutral900,
inputPlaceholder: AppTheme.neutral500,
inputError: AppTheme.error,
cardBackground: AppTheme.neutral50,
cardBorder: const Color(0xFFE7E7E7),
cardShadow: const Color(0xFFF3F4F6),
surfaceBackground: AppTheme.neutral50,
surfaceContainer: const Color(0xFFF7F7F7),
surfaceContainerHighest: const Color(0xFFF0F1F1),
buttonPrimary: lightTone.primary,
buttonPrimaryText: AppTheme.neutral50,
buttonSecondary: const Color(0xFFF0F1F1),
buttonSecondaryText: const Color(0xFF1C1C1C),
buttonDisabled: AppTheme.neutral300,
buttonDisabledText: AppTheme.neutral500,
success: const Color(0xFF166534),
successBackground: const Color(0xFFECFDF3),
error: const Color(0xFFB91C1C),
errorBackground: const Color(0xFFFEE2E2),
warning: const Color(0xFF92400E),
warningBackground: const Color(0xFFFEF3C7),
info: const Color(0xFF1D4ED8),
infoBackground: const Color(0xFFDBEAFE),
dividerColor: AppTheme.neutral100,
navigationBackground: AppTheme.neutral50,
navigationSelected: lightTone.primary,
navigationUnselected: AppTheme.neutral600,
navigationSelectedBackground: _surfaceTint(
lightTone.primary,
AppTheme.neutral50,
0.16,
),
shimmerBase: const Color(0xFFF3F4F6),
shimmerHighlight: AppTheme.neutral50,
loadingIndicator: lightTone.primary,
textPrimary: const Color(0xFF1C1C1C),
textSecondary: const Color(0xFF3A3F3E),
textTertiary: AppTheme.neutral500,
textInverse: AppTheme.neutral50,
textDisabled: AppTheme.neutral400,
iconPrimary: const Color(0xFF1C1C1C),
iconSecondary: const Color(0xFF666C6A),
iconDisabled: AppTheme.neutral400,
iconInverse: AppTheme.neutral50,
headingLarge: TextStyle(
fontSize: AppTypography.displaySmall,
fontWeight: FontWeight.w700,
color: const Color(0xFF111827),
height: 1.2,
),
headingMedium: TextStyle(
fontSize: AppTypography.headlineLarge,
fontWeight: FontWeight.w600,
color: const Color(0xFF111827),
height: 1.3,
),
headingSmall: TextStyle(
fontSize: AppTypography.headlineSmall,
fontWeight: FontWeight.w600,
color: const Color(0xFF111827),
height: 1.4,
),
bodyLarge: TextStyle(
fontSize: AppTypography.bodyLarge,
fontWeight: FontWeight.w400,
color: const Color(0xFF111827),
height: 1.5,
),
bodyMedium: TextStyle(
fontSize: AppTypography.bodyMedium,
fontWeight: FontWeight.w400,
color: const Color(0xFF111827),
height: 1.5,
),
bodySmall: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: AppTheme.neutral500,
height: 1.4,
),
caption: TextStyle(
fontSize: AppTypography.labelMedium,
fontWeight: FontWeight.w500,
color: AppTheme.neutral400,
height: 1.3,
letterSpacing: 0.5,
),
label: TextStyle(
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
color: const Color(0xFF444948),
height: 1.3,
),
code: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: const Color(0xFF1C1C1C),
height: 1.4,
fontFamily: AppTypography.monospaceFontFamily,
),
);
}
// Interactive element colors - More vibrant and accessible
buttonPrimary: AppTheme.brandPrimaryDark,
buttonPrimaryText: AppTheme.neutral50,
buttonSecondary: Color(0xFF151918),
buttonSecondaryText: AppTheme.neutral50,
buttonDisabled: AppTheme.neutral600,
buttonDisabledText: AppTheme.neutral400,
// Status and feedback colors - Enhanced visibility
success: Color(0xFF34D399),
successBackground: Color(0xFF14532D),
error: Color(0xFFFCA5A5),
errorBackground: Color(0xFF7F1D1D),
warning: Color(0xFFFBBF24),
warningBackground: Color(0xFF451A03),
info: Color(0xFF93C5FD),
infoBackground: Color(0xFF0C4A6E),
// Navigation and UI element colors - Enhanced contrast
dividerColor: AppTheme.neutral600,
navigationBackground: Color(0xFF0A0D0C),
navigationSelected: AppTheme.brandPrimaryDark,
navigationUnselected: AppTheme.neutral300,
navigationSelectedBackground: Color(0xFF312E81),
// Loading and animation colors - Enhanced visibility
shimmerBase: Color(0xFF121514),
shimmerHighlight: Color(0xFF1A1D1C),
loadingIndicator: AppTheme.brandPrimaryDark,
// Text colors - Enhanced hierarchy
textPrimary: AppTheme.neutral50,
textSecondary: Color(0xFFBAC2C0),
textTertiary: AppTheme.neutral400,
textInverse: AppTheme.neutral900,
textDisabled: AppTheme.neutral600,
// Icon colors - Enhanced visibility
iconPrimary: AppTheme.neutral50,
iconSecondary: Color(0xFFA0A8A5),
iconDisabled: AppTheme.neutral600,
iconInverse: AppTheme.neutral900,
// Typography styles
headingLarge: TextStyle(
fontSize: AppTypography.displaySmall,
fontWeight: FontWeight.w700,
color: AppTheme.neutral50,
height: 1.2,
),
headingMedium: TextStyle(
fontSize: AppTypography.headlineLarge,
fontWeight: FontWeight.w600,
color: AppTheme.neutral50,
height: 1.3,
),
headingSmall: TextStyle(
fontSize: AppTypography.headlineSmall,
fontWeight: FontWeight.w600,
color: AppTheme.neutral50,
height: 1.4,
),
bodyLarge: TextStyle(
fontSize: AppTypography.bodyLarge,
fontWeight: FontWeight.w400,
color: AppTheme.neutral50,
height: 1.5,
),
bodyMedium: TextStyle(
fontSize: AppTypography.bodyMedium,
fontWeight: FontWeight.w400,
color: AppTheme.neutral50,
height: 1.5,
),
bodySmall: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: Color(0xFFD1D5DB), // Enhanced contrast
height: 1.4,
),
caption: TextStyle(
fontSize: AppTypography.labelMedium,
fontWeight: FontWeight.w500,
color: AppTheme.neutral300,
height: 1.3,
letterSpacing: 0.5,
),
label: TextStyle(
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
color: Color(0xFFD1D5DB), // Enhanced contrast
height: 1.3,
),
code: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: Color(0xFFD1D5DB), // Enhanced contrast
height: 1.4,
fontFamily: AppTypography.monospaceFontFamily,
),
);
/// Light theme extension
static const ConduitThemeExtension light = ConduitThemeExtension(
// Chat-specific colors - Enhanced for production-grade look
chatBubbleUser: AppTheme.brandPrimary,
chatBubbleAssistant: Color(0xFFF7F7F7),
chatBubbleUserText: AppTheme.neutral50,
chatBubbleAssistantText: Color(0xFF1C1C1C),
chatBubbleUserBorder: AppTheme.brandPrimaryDark,
chatBubbleAssistantBorder: Color(0xFFE7E7E7),
// Input and form colors
inputBackground: AppTheme.neutral50,
inputBorder: AppTheme.neutral200,
inputBorderFocused: AppTheme.brandPrimary,
inputText: AppTheme.neutral900,
inputPlaceholder: AppTheme.neutral500,
inputError: AppTheme.error,
// Card and surface colors - Enhanced depth and hierarchy
cardBackground: AppTheme.neutral50,
cardBorder: Color(0xFFE7E7E7),
cardShadow: Color(0xFFF3F4F6),
surfaceBackground: AppTheme.neutral50,
surfaceContainer: Color(0xFFF7F7F7),
surfaceContainerHighest: Color(0xFFF0F1F1),
// Interactive element colors - More vibrant and accessible
buttonPrimary: AppTheme.brandPrimary,
buttonPrimaryText: AppTheme.neutral50,
buttonSecondary: Color(0xFFF0F1F1),
buttonSecondaryText: Color(0xFF1C1C1C),
buttonDisabled: AppTheme.neutral300,
buttonDisabledText: AppTheme.neutral500,
// Status and feedback colors - Enhanced visibility
success: Color(0xFF166534),
successBackground: Color(0xFFECFDF3),
error: Color(0xFFB91C1C),
errorBackground: Color(0xFFFEE2E2),
warning: Color(0xFF92400E),
warningBackground: Color(0xFFFEF3C7),
info: Color(0xFF1D4ED8),
infoBackground: Color(0xFFDBEAFE),
// Navigation and UI element colors - Enhanced contrast
dividerColor: AppTheme.neutral100,
navigationBackground: AppTheme.neutral50,
navigationSelected: AppTheme.brandPrimary,
navigationUnselected: AppTheme.neutral600,
navigationSelectedBackground: Color(0xFFE0E7FF),
// Loading and animation colors - Enhanced visibility
shimmerBase: Color(0xFFF3F4F6),
shimmerHighlight: AppTheme.neutral50,
loadingIndicator: AppTheme.brandPrimary,
// Text colors - Enhanced hierarchy
textPrimary: Color(0xFF1C1C1C),
textSecondary: Color(0xFF3A3F3E),
textTertiary: AppTheme.neutral500,
textInverse: AppTheme.neutral50,
textDisabled: AppTheme.neutral400,
// Icon colors - Enhanced visibility
iconPrimary: Color(0xFF1C1C1C),
iconSecondary: Color(0xFF666C6A),
iconDisabled: AppTheme.neutral400,
iconInverse: AppTheme.neutral50,
// Typography styles
headingLarge: TextStyle(
fontSize: AppTypography.displaySmall,
fontWeight: FontWeight.w700,
color: Color(0xFF111827), // Better contrast
height: 1.2,
),
headingMedium: TextStyle(
fontSize: AppTypography.headlineLarge,
fontWeight: FontWeight.w600,
color: Color(0xFF111827), // Better contrast
height: 1.3,
),
headingSmall: TextStyle(
fontSize: AppTypography.headlineSmall,
fontWeight: FontWeight.w600,
color: Color(0xFF111827), // Better contrast
height: 1.4,
),
bodyLarge: TextStyle(
fontSize: AppTypography.bodyLarge,
fontWeight: FontWeight.w400,
color: Color(0xFF111827), // Better contrast
height: 1.5,
),
bodyMedium: TextStyle(
fontSize: AppTypography.bodyMedium,
fontWeight: FontWeight.w400,
color: Color(0xFF374151), // Better contrast
height: 1.5,
),
bodySmall: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: Color(0xFF6B7280), // Better contrast
height: 1.4,
),
caption: TextStyle(
fontSize: AppTypography.labelMedium,
fontWeight: FontWeight.w500,
color: AppTheme.neutral500,
height: 1.3,
letterSpacing: 0.5,
),
label: TextStyle(
fontSize: AppTypography.labelLarge,
fontWeight: FontWeight.w500,
color: Color(0xFF374151), // Better contrast
height: 1.3,
),
code: TextStyle(
fontSize: AppTypography.bodySmall,
fontWeight: FontWeight.w400,
color: Color(0xFF374151), // Better contrast
height: 1.4,
fontFamily: AppTypography.monospaceFontFamily,
),
);
static Color _surfaceTint(Color tone, Color surface, double opacity) {
return Color.alphaBlend(tone.withValues(alpha: opacity), surface);
}
}
/// Extension method to easily access Conduit theme from BuildContext
extension ConduitThemeContext on BuildContext {
ConduitThemeExtension get conduitTheme {
return Theme.of(this).extension<ConduitThemeExtension>() ??
ConduitThemeExtension.dark;
final theme = Theme.of(this);
final extension = theme.extension<ConduitThemeExtension>();
if (extension != null) return extension;
final palette =
theme.extension<AppPaletteThemeExtension>()?.palette ??
AppColorPalettes.auroraViolet;
return theme.brightness == Brightness.dark
? ConduitThemeExtension.darkPalette(palette)
: ConduitThemeExtension.lightPalette(palette);
}
}
extension ConduitPaletteContext on BuildContext {
AppColorPalette get conduitPalette {
return Theme.of(this).extension<AppPaletteThemeExtension>()?.palette ??
AppColorPalettes.auroraViolet;
}
}
@@ -931,7 +932,9 @@ class ConduitShadows {
static List<BoxShadow> get glow => [
BoxShadow(
color: AppTheme.brandPrimary.withValues(alpha: 0.25),
color: AppColorPalettes.auroraViolet.light.primary.withValues(
alpha: 0.25,
),
blurRadius: 20,
offset: const Offset(0, 0),
spreadRadius: 0,