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:
@@ -3,6 +3,7 @@ import '../theme/theme_extensions.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../theme/app_theme.dart';
|
||||
import '../theme/color_palettes.dart';
|
||||
|
||||
/// Centralized service for consistent brand identity throughout the app
|
||||
/// Uses the hub icon as the primary brand element
|
||||
@@ -20,9 +21,32 @@ class BrandService {
|
||||
Platform.isIOS ? CupertinoIcons.globe : Icons.public;
|
||||
|
||||
/// Brand colors - these should be accessed through context.conduitTheme in UI components
|
||||
static Color get primaryBrandColor => AppTheme.brandPrimary;
|
||||
static Color get secondaryBrandColor => AppTheme.brandPrimaryLight;
|
||||
static Color get accentBrandColor => AppTheme.brandPrimaryDark;
|
||||
static Color primaryBrandColor({
|
||||
BuildContext? context,
|
||||
Brightness? brightness,
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.primaryFor(resolvedBrightness);
|
||||
}
|
||||
|
||||
static Color secondaryBrandColor({
|
||||
BuildContext? context,
|
||||
Brightness? brightness,
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.secondaryFor(resolvedBrightness);
|
||||
}
|
||||
|
||||
static Color accentBrandColor({
|
||||
BuildContext? context,
|
||||
Brightness? brightness,
|
||||
}) {
|
||||
final palette = _resolvePalette(context);
|
||||
final resolvedBrightness = brightness ?? _resolveBrightness(context);
|
||||
return palette.accentFor(resolvedBrightness);
|
||||
}
|
||||
|
||||
/// Creates a branded icon with consistent styling
|
||||
static Widget createBrandIcon({
|
||||
@@ -31,21 +55,25 @@ class BrandService {
|
||||
IconData? icon,
|
||||
bool useGradient = false,
|
||||
bool addShadow = false,
|
||||
BuildContext? context,
|
||||
}) {
|
||||
final iconData = icon ?? primaryIcon;
|
||||
final iconColor = color ?? primaryBrandColor;
|
||||
final resolvedColor = color ?? primaryBrandColor(context: context);
|
||||
|
||||
Widget iconWidget = Icon(
|
||||
iconData,
|
||||
size: size,
|
||||
color: useGradient ? null : iconColor,
|
||||
color: useGradient ? null : resolvedColor,
|
||||
);
|
||||
|
||||
if (useGradient) {
|
||||
iconWidget = ShaderMask(
|
||||
blendMode: BlendMode.srcIn,
|
||||
shaderCallback: (bounds) => LinearGradient(
|
||||
colors: [primaryBrandColor, secondaryBrandColor],
|
||||
colors: [
|
||||
primaryBrandColor(context: context),
|
||||
secondaryBrandColor(context: context),
|
||||
],
|
||||
).createShader(bounds),
|
||||
child: Icon(iconData, size: size),
|
||||
);
|
||||
@@ -56,7 +84,7 @@ class BrandService {
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryBrandColor.withValues(alpha: 0.3),
|
||||
color: primaryBrandColor(context: context).withValues(alpha: 0.3),
|
||||
blurRadius: size * 0.3,
|
||||
offset: Offset(0, size * 0.1),
|
||||
),
|
||||
@@ -78,7 +106,7 @@ class BrandService {
|
||||
String? fallbackText,
|
||||
BuildContext? context,
|
||||
}) {
|
||||
final bgColor = backgroundColor ?? primaryBrandColor;
|
||||
final bgColor = backgroundColor ?? primaryBrandColor(context: context);
|
||||
final iColor =
|
||||
iconColor ?? (context?.conduitTheme.textInverse ?? AppTheme.neutral50);
|
||||
|
||||
@@ -90,14 +118,17 @@ class BrandService {
|
||||
? LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [primaryBrandColor, secondaryBrandColor],
|
||||
colors: [
|
||||
primaryBrandColor(context: context),
|
||||
secondaryBrandColor(context: context),
|
||||
],
|
||||
)
|
||||
: null,
|
||||
color: useGradient ? null : bgColor,
|
||||
borderRadius: BorderRadius.circular(size / 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: primaryBrandColor.withValues(alpha: 0.3),
|
||||
color: primaryBrandColor(context: context).withValues(alpha: 0.3),
|
||||
blurRadius: size * 0.2,
|
||||
offset: Offset(0, size * 0.1),
|
||||
),
|
||||
@@ -123,13 +154,16 @@ class BrandService {
|
||||
double size = 24,
|
||||
double strokeWidth = 2,
|
||||
Color? color,
|
||||
BuildContext? context,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: strokeWidth,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color ?? primaryBrandColor),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
color ?? primaryBrandColor(context: context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -149,6 +183,7 @@ class BrandService {
|
||||
size: size,
|
||||
color: iconColor,
|
||||
icon: primaryIconOutlined,
|
||||
context: context,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,6 +202,7 @@ class BrandService {
|
||||
size: size * 0.5,
|
||||
color: iconColor,
|
||||
icon: primaryIconOutlined,
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -181,27 +217,27 @@ class BrandService {
|
||||
bool isSecondary = false,
|
||||
BuildContext? context,
|
||||
}) {
|
||||
final theme = context?.conduitTheme;
|
||||
return SizedBox(
|
||||
width: width,
|
||||
height: 48,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
icon: isLoading
|
||||
? createBrandLoadingIndicator(size: IconSize.sm)
|
||||
? createBrandLoadingIndicator(size: IconSize.sm, context: context)
|
||||
: createBrandIcon(
|
||||
size: IconSize.md,
|
||||
icon: icon ?? primaryIcon,
|
||||
color: context?.conduitTheme.textInverse ?? AppTheme.neutral50,
|
||||
color: theme?.textInverse ?? AppTheme.neutral50,
|
||||
context: context,
|
||||
),
|
||||
label: Text(text),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSecondary
|
||||
? (context?.conduitTheme.buttonSecondary ?? AppTheme.neutral700)
|
||||
: (context?.conduitTheme.buttonPrimary ?? primaryBrandColor),
|
||||
foregroundColor:
|
||||
context?.conduitTheme.buttonPrimaryText ?? AppTheme.neutral50,
|
||||
disabledBackgroundColor:
|
||||
context?.conduitTheme.buttonDisabled ?? AppTheme.neutral500,
|
||||
? (theme?.buttonSecondary ?? AppTheme.neutral700)
|
||||
: (theme?.buttonPrimary ?? primaryBrandColor(context: context)),
|
||||
foregroundColor: theme?.buttonPrimaryText ?? AppTheme.neutral50,
|
||||
disabledBackgroundColor: theme?.buttonDisabled ?? AppTheme.neutral500,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
@@ -255,6 +291,14 @@ class BrandService {
|
||||
bool animate = true,
|
||||
BuildContext? context,
|
||||
}) {
|
||||
final theme = context?.conduitTheme;
|
||||
final baseColor =
|
||||
theme?.buttonPrimary ??
|
||||
primaryBrandColor(context: context, brightness: Brightness.dark);
|
||||
final accentColor =
|
||||
theme?.buttonPrimary.withValues(alpha: 0.8) ??
|
||||
secondaryBrandColor(context: context, brightness: Brightness.dark);
|
||||
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
@@ -262,11 +306,7 @@ class BrandService {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
context?.conduitTheme.buttonPrimary ?? primaryBrandColor,
|
||||
context?.conduitTheme.buttonPrimary.withValues(alpha: 0.8) ??
|
||||
secondaryBrandColor,
|
||||
],
|
||||
colors: [baseColor, accentColor],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(size / 2),
|
||||
boxShadow: ConduitShadows.glow,
|
||||
@@ -274,8 +314,20 @@ class BrandService {
|
||||
child: Icon(
|
||||
primaryIcon,
|
||||
size: size * 0.5,
|
||||
color: context?.conduitTheme.textInverse ?? AppTheme.neutral50,
|
||||
color: theme?.textInverse ?? AppTheme.neutral50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static AppColorPalette _resolvePalette(BuildContext? context) {
|
||||
if (context == null) {
|
||||
return AppColorPalettes.auroraViolet;
|
||||
}
|
||||
final extension = Theme.of(context).extension<AppPaletteThemeExtension>();
|
||||
return extension?.palette ?? AppColorPalettes.auroraViolet;
|
||||
}
|
||||
|
||||
static Brightness _resolveBrightness(BuildContext? context) {
|
||||
return context != null ? Theme.of(context).brightness : Brightness.light;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
159
lib/shared/theme/color_palettes.dart
Normal file
159
lib/shared/theme/color_palettes.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -21,7 +21,7 @@ class ConduitLoading {
|
||||
}) {
|
||||
return _LoadingIndicator(
|
||||
size: size,
|
||||
color: color ?? BrandService.primaryBrandColor,
|
||||
color: color,
|
||||
message: message,
|
||||
type: _LoadingType.primary,
|
||||
);
|
||||
@@ -40,7 +40,7 @@ class ConduitLoading {
|
||||
color ??
|
||||
(context?.conduitTheme.loadingIndicator ??
|
||||
context?.conduitTheme.buttonPrimary ??
|
||||
AppTheme.brandPrimary),
|
||||
BrandService.primaryBrandColor(context: context)),
|
||||
message: message,
|
||||
type: _LoadingType.inline,
|
||||
);
|
||||
@@ -91,30 +91,35 @@ enum _LoadingType { primary, inline, button }
|
||||
|
||||
class _LoadingIndicator extends StatelessWidget {
|
||||
final double size;
|
||||
final Color color;
|
||||
final Color? color;
|
||||
final String? message;
|
||||
final _LoadingType type;
|
||||
|
||||
const _LoadingIndicator({
|
||||
required this.size,
|
||||
required this.color,
|
||||
this.color,
|
||||
this.message,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final resolvedColor = color ?? context.conduitTheme.loadingIndicator;
|
||||
|
||||
Widget indicator;
|
||||
|
||||
if (Platform.isIOS) {
|
||||
indicator = CupertinoActivityIndicator(color: color, radius: size / 2);
|
||||
indicator = CupertinoActivityIndicator(
|
||||
color: resolvedColor,
|
||||
radius: size / 2,
|
||||
);
|
||||
} else {
|
||||
indicator = SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: size / 8,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(resolvedColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user