- Reduced the chat bubble padding from 16.0 to 12.0 for a more compact design. - Updated the message bubble border radius from 18.0 to 12.0 to enhance visual harmony across components.
1591 lines
51 KiB
Dart
1591 lines
51 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
// Using system fonts; no GoogleFonts dependency required
|
|
import 'tweakcn_themes.dart';
|
|
import 'color_tokens.dart';
|
|
|
|
/// Extended theme data for consistent styling across the app
|
|
@immutable
|
|
class ConduitThemeExtension extends ThemeExtension<ConduitThemeExtension> {
|
|
const ConduitThemeExtension._({
|
|
required this.tokens,
|
|
required this.variant,
|
|
required this.isDark,
|
|
required this.typography,
|
|
required this.surfaces,
|
|
required this.shadows,
|
|
required this.shapes,
|
|
});
|
|
|
|
factory ConduitThemeExtension.create({
|
|
required TweakcnThemeDefinition theme,
|
|
required AppColorTokens tokens,
|
|
required Brightness brightness,
|
|
required TypographyThemeExtension typography,
|
|
required SurfaceThemeExtension surfaces,
|
|
required ShadowThemeExtension shadows,
|
|
required ShapeThemeExtension shapes,
|
|
}) {
|
|
return ConduitThemeExtension._(
|
|
tokens: tokens,
|
|
variant: theme.variantFor(brightness),
|
|
isDark: brightness == Brightness.dark,
|
|
typography: typography,
|
|
surfaces: surfaces,
|
|
shadows: shadows,
|
|
shapes: shapes,
|
|
);
|
|
}
|
|
|
|
final AppColorTokens tokens;
|
|
final TweakcnThemeVariant variant;
|
|
final bool isDark;
|
|
final TypographyThemeExtension typography;
|
|
final SurfaceThemeExtension surfaces;
|
|
final ShadowThemeExtension shadows;
|
|
final ShapeThemeExtension shapes;
|
|
|
|
Color get chatBubbleUser => variant.primary;
|
|
Color get chatBubbleAssistant =>
|
|
isDark ? tokens.neutralTone20 : tokens.neutralTone00;
|
|
Color get chatBubbleUserText => _onSurfaceColor(variant.primary);
|
|
Color get chatBubbleAssistantText => tokens.neutralOnSurface;
|
|
Color get chatBubbleUserBorder => variant.secondary;
|
|
Color get chatBubbleAssistantBorder =>
|
|
isDark ? tokens.neutralTone40 : tokens.neutralTone20;
|
|
|
|
Color get inputBackground =>
|
|
Color.lerp(surfaces.background, surfaces.input, isDark ? 0.35 : 0.75)!;
|
|
Color get inputBorder =>
|
|
Color.lerp(surfaces.border, surfaces.ring, isDark ? 0.4 : 0.2)!;
|
|
Color get inputBorderFocused => surfaces.ring;
|
|
Color get inputText => tokens.neutralOnSurface;
|
|
Color get inputPlaceholder =>
|
|
isDark ? tokens.neutralTone80 : tokens.neutralTone60;
|
|
Color get inputError => tokens.statusError60;
|
|
|
|
Color get cardBackground => surfaces.card;
|
|
Color get cardBorder => surfaces.border;
|
|
Color get cardShadow => shadows.shadowSm.first.color;
|
|
List<BoxShadow> get cardShadows => shadows.shadowSm;
|
|
List<BoxShadow> get popoverShadows => shadows.shadowLg;
|
|
List<BoxShadow> get overlayShadows => shadows.shadowXs;
|
|
|
|
Color get surfaceBackground => surfaces.background;
|
|
Color get surfaceContainer => surfaces.container;
|
|
Color get surfaceContainerHighest => surfaces.containerHighest;
|
|
|
|
Color get buttonPrimary => variant.primary;
|
|
Color get buttonPrimaryText => _onSurfaceColor(variant.primary);
|
|
Color get buttonSecondary => tokens.neutralTone20;
|
|
Color get buttonSecondaryText => tokens.neutralOnSurface;
|
|
Color get buttonDisabled => tokens.neutralTone40;
|
|
Color get buttonDisabledText =>
|
|
isDark ? tokens.neutralTone80 : tokens.neutralTone60;
|
|
|
|
StatusPalette get statusPalette => StatusPalette(
|
|
success: StatusColors(
|
|
base: tokens.statusSuccess60,
|
|
onBase: tokens.statusOnSuccess60,
|
|
background: _toneOverlay(tokens.statusSuccess60),
|
|
light: tokens.statusSuccess60,
|
|
dark: Color.alphaBlend(
|
|
tokens.statusSuccess60.withValues(alpha: 0.8),
|
|
tokens.neutralTone20,
|
|
),
|
|
),
|
|
warning: StatusColors(
|
|
base: tokens.statusWarning60,
|
|
onBase: tokens.statusOnWarning60,
|
|
background: _toneOverlay(tokens.statusWarning60),
|
|
light: tokens.statusWarning60,
|
|
dark: Color.alphaBlend(
|
|
tokens.statusWarning60.withValues(alpha: 0.8),
|
|
tokens.neutralTone20,
|
|
),
|
|
),
|
|
info: StatusColors(
|
|
base: tokens.statusInfo60,
|
|
onBase: tokens.statusOnInfo60,
|
|
background: _toneOverlay(tokens.statusInfo60),
|
|
light: tokens.statusInfo60,
|
|
dark: Color.alphaBlend(
|
|
tokens.statusInfo60.withValues(alpha: 0.8),
|
|
tokens.neutralTone20,
|
|
),
|
|
),
|
|
destructive: StatusColors(
|
|
base: tokens.statusError60,
|
|
onBase: tokens.statusOnError60,
|
|
background: _toneOverlay(tokens.statusError60),
|
|
light: tokens.statusError60,
|
|
dark: Color.alphaBlend(
|
|
tokens.statusError60.withValues(alpha: 0.8),
|
|
tokens.neutralTone20,
|
|
),
|
|
),
|
|
);
|
|
|
|
Color get success => statusPalette.success.base;
|
|
Color get successBackground => statusPalette.success.background;
|
|
Color get error => statusPalette.destructive.base;
|
|
Color get errorBackground => statusPalette.destructive.background;
|
|
Color get warning => statusPalette.warning.base;
|
|
Color get warningBackground => statusPalette.warning.background;
|
|
Color get info => statusPalette.info.base;
|
|
Color get infoBackground => statusPalette.info.background;
|
|
|
|
Color get dividerColor =>
|
|
isDark ? tokens.neutralTone40 : tokens.neutralTone20;
|
|
Color get navigationBackground =>
|
|
isDark ? tokens.neutralTone10 : tokens.neutralTone00;
|
|
Color get navigationSelected => variant.primary;
|
|
Color get navigationUnselected =>
|
|
isDark ? tokens.neutralTone80 : tokens.neutralTone60;
|
|
Color get navigationSelectedBackground =>
|
|
_overlay(tokens.overlayMedium, surface: navigationBackground);
|
|
|
|
Color get shimmerBase =>
|
|
_overlay(tokens.overlayWeak, surface: tokens.neutralTone10);
|
|
Color get shimmerHighlight => isDark
|
|
? _overlay(tokens.overlayMedium, surface: tokens.neutralTone20)
|
|
: tokens.neutralTone00;
|
|
Color get loadingIndicator => variant.primary;
|
|
|
|
Color get codeBackground => tokens.codeBackground;
|
|
Color get codeBorder => tokens.codeBorder;
|
|
Color get codeText => tokens.codeText;
|
|
Color get codeAccent => tokens.codeAccent;
|
|
|
|
Color get textPrimary => tokens.neutralOnSurface;
|
|
Color get textSecondary => tokens.neutralTone80;
|
|
Color get textTertiary => tokens.neutralTone60;
|
|
Color get textInverse => tokens.neutralTone00;
|
|
Color get textDisabled =>
|
|
isDark ? tokens.neutralTone40 : tokens.neutralTone60;
|
|
|
|
Color get iconPrimary => tokens.neutralOnSurface;
|
|
Color get iconSecondary => tokens.neutralTone80;
|
|
Color get iconDisabled =>
|
|
isDark ? tokens.neutralTone40 : tokens.neutralTone60;
|
|
Color get iconInverse => tokens.neutralTone00;
|
|
|
|
double get radiusSm => shapes.radiusSm;
|
|
double get radiusMd => shapes.radiusMd;
|
|
double get radiusLg => shapes.radiusLg;
|
|
double get radiusXl => shapes.radiusXl;
|
|
|
|
Color get sidebarBackground => variant.sidebarBackground;
|
|
Color get sidebarForeground => variant.sidebarForeground;
|
|
Color get sidebarPrimary => variant.sidebarPrimary;
|
|
Color get sidebarPrimaryText => variant.sidebarPrimaryForeground;
|
|
Color get sidebarAccent => variant.sidebarAccent;
|
|
Color get sidebarAccentText => variant.sidebarAccentForeground;
|
|
Color get sidebarBorder => variant.sidebarBorder;
|
|
Color get sidebarRing => variant.sidebarRing;
|
|
|
|
TextStyle? get headingLarge => TextStyle(
|
|
fontSize: AppTypography.displaySmall,
|
|
fontWeight: FontWeight.w700,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.2,
|
|
);
|
|
|
|
TextStyle? get headingMedium => TextStyle(
|
|
fontSize: AppTypography.headlineLarge,
|
|
fontWeight: FontWeight.w600,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.3,
|
|
);
|
|
|
|
TextStyle? get headingSmall => TextStyle(
|
|
fontSize: AppTypography.headlineSmall,
|
|
fontWeight: FontWeight.w600,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.4,
|
|
);
|
|
|
|
TextStyle? get bodyLarge => TextStyle(
|
|
fontSize: AppTypography.bodyLarge,
|
|
fontWeight: FontWeight.w400,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.5,
|
|
);
|
|
|
|
TextStyle? get bodyMedium => TextStyle(
|
|
fontSize: AppTypography.bodyMedium,
|
|
fontWeight: FontWeight.w400,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.5,
|
|
);
|
|
|
|
TextStyle? get bodySmall => TextStyle(
|
|
fontSize: AppTypography.bodySmall,
|
|
fontWeight: FontWeight.w400,
|
|
color: isDark ? tokens.neutralTone80 : tokens.neutralTone60,
|
|
height: 1.4,
|
|
);
|
|
|
|
TextStyle? get caption => TextStyle(
|
|
fontSize: AppTypography.labelMedium,
|
|
fontWeight: FontWeight.w500,
|
|
color: isDark ? tokens.neutralTone80 : tokens.neutralTone60,
|
|
height: 1.3,
|
|
letterSpacing: 0.5,
|
|
);
|
|
|
|
TextStyle? get label => TextStyle(
|
|
fontSize: AppTypography.labelLarge,
|
|
fontWeight: FontWeight.w500,
|
|
color: tokens.neutralTone80,
|
|
height: 1.3,
|
|
);
|
|
|
|
TextStyle? get code => TextStyle(
|
|
fontSize: AppTypography.bodySmall,
|
|
fontWeight: FontWeight.w400,
|
|
color: tokens.neutralOnSurface,
|
|
height: 1.4,
|
|
fontFamily: typography.monospaceFont,
|
|
fontFamilyFallback: typography.monospaceFallback,
|
|
);
|
|
|
|
@override
|
|
ConduitThemeExtension copyWith({
|
|
AppColorTokens? tokens,
|
|
TweakcnThemeVariant? variant,
|
|
bool? isDark,
|
|
TypographyThemeExtension? typography,
|
|
SurfaceThemeExtension? surfaces,
|
|
ShadowThemeExtension? shadows,
|
|
ShapeThemeExtension? shapes,
|
|
}) {
|
|
return ConduitThemeExtension._(
|
|
tokens: tokens ?? this.tokens,
|
|
variant: variant ?? this.variant,
|
|
isDark: isDark ?? this.isDark,
|
|
typography: typography ?? this.typography,
|
|
surfaces: surfaces ?? this.surfaces,
|
|
shadows: shadows ?? this.shadows,
|
|
shapes: shapes ?? this.shapes,
|
|
);
|
|
}
|
|
|
|
@override
|
|
ConduitThemeExtension lerp(
|
|
covariant ThemeExtension<ConduitThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! ConduitThemeExtension) return this;
|
|
return t < 0.5 ? this : other;
|
|
}
|
|
|
|
Color _overlay(Color overlay, {Color? surface}) {
|
|
return Color.alphaBlend(overlay, surface ?? surfaceBackground);
|
|
}
|
|
|
|
Color _toneOverlay(Color tone) {
|
|
final double alpha = isDark ? 0.24 : 0.12;
|
|
final Color base = isDark ? tokens.neutralTone10 : tokens.neutralTone00;
|
|
return Color.alphaBlend(tone.withValues(alpha: alpha), base);
|
|
}
|
|
|
|
Color _onSurfaceColor(Color background) {
|
|
final contrastOnLight = _contrastRatio(background, tokens.neutralTone00);
|
|
final contrastOnDark = _contrastRatio(background, tokens.neutralOnSurface);
|
|
return contrastOnLight >= contrastOnDark
|
|
? tokens.neutralTone00
|
|
: tokens.neutralOnSurface;
|
|
}
|
|
|
|
static double _contrastRatio(Color a, Color b) {
|
|
final luminanceA = a.computeLuminance();
|
|
final luminanceB = b.computeLuminance();
|
|
final lighter = math.max(luminanceA, luminanceB);
|
|
final darker = math.min(luminanceA, luminanceB);
|
|
return (lighter + 0.05) / (darker + 0.05);
|
|
}
|
|
}
|
|
|
|
/// Extension method to easily access Conduit theme from BuildContext
|
|
extension ConduitThemeContext on BuildContext {
|
|
ConduitThemeExtension get conduitTheme {
|
|
final theme = Theme.of(this);
|
|
final extension = theme.extension<ConduitThemeExtension>();
|
|
if (extension != null) return extension;
|
|
final palette =
|
|
theme.extension<AppPaletteThemeExtension>()?.palette ??
|
|
TweakcnThemes.t3Chat;
|
|
final TweakcnThemeVariant variant = palette.variantFor(theme.brightness);
|
|
final tokens = theme.brightness == Brightness.dark
|
|
? AppColorTokens.dark(theme: palette)
|
|
: AppColorTokens.light(theme: palette);
|
|
final TypographyThemeExtension typography =
|
|
theme.extension<TypographyThemeExtension>() ??
|
|
TypographyThemeExtension.fromVariant(variant);
|
|
final SurfaceThemeExtension surfaces =
|
|
theme.extension<SurfaceThemeExtension>() ??
|
|
SurfaceThemeExtension.fromVariant(variant);
|
|
final ShadowThemeExtension shadows =
|
|
theme.extension<ShadowThemeExtension>() ??
|
|
ShadowThemeExtension.standard();
|
|
final ShapeThemeExtension shapes =
|
|
theme.extension<ShapeThemeExtension>() ??
|
|
ShapeThemeExtension.fromVariant(variant);
|
|
return ConduitThemeExtension.create(
|
|
theme: palette,
|
|
tokens: tokens,
|
|
brightness: theme.brightness,
|
|
typography: typography,
|
|
surfaces: surfaces,
|
|
shadows: shadows,
|
|
shapes: shapes,
|
|
);
|
|
}
|
|
}
|
|
|
|
extension ConduitColorTokensContext on BuildContext {
|
|
AppColorTokens get colorTokens {
|
|
final theme = Theme.of(this);
|
|
final tokens = theme.extension<AppColorTokens>();
|
|
if (tokens != null) return tokens;
|
|
final palette =
|
|
theme.extension<AppPaletteThemeExtension>()?.palette ??
|
|
TweakcnThemes.t3Chat;
|
|
return theme.brightness == Brightness.dark
|
|
? AppColorTokens.dark(theme: palette)
|
|
: AppColorTokens.light(theme: palette);
|
|
}
|
|
}
|
|
|
|
extension ConduitPaletteContext on BuildContext {
|
|
TweakcnThemeDefinition get conduitPalette {
|
|
return Theme.of(this).extension<AppPaletteThemeExtension>()?.palette ??
|
|
TweakcnThemes.t3Chat;
|
|
}
|
|
}
|
|
|
|
extension SidebarThemeContext on BuildContext {
|
|
SidebarThemeExtension get sidebarTheme {
|
|
final theme = Theme.of(this);
|
|
final extension = theme.extension<SidebarThemeExtension>();
|
|
if (extension != null) return extension;
|
|
final palette =
|
|
theme.extension<AppPaletteThemeExtension>()?.palette ??
|
|
TweakcnThemes.t3Chat;
|
|
final TweakcnThemeVariant variant = palette.variantFor(theme.brightness);
|
|
return SidebarThemeExtension.fromVariant(variant);
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class StatusColors {
|
|
const StatusColors({
|
|
required this.base,
|
|
required this.onBase,
|
|
required this.background,
|
|
required this.light,
|
|
required this.dark,
|
|
});
|
|
|
|
final Color base;
|
|
final Color onBase;
|
|
final Color background;
|
|
final Color light;
|
|
final Color dark;
|
|
}
|
|
|
|
@immutable
|
|
class StatusPalette {
|
|
const StatusPalette({
|
|
required this.success,
|
|
required this.warning,
|
|
required this.info,
|
|
required this.destructive,
|
|
});
|
|
|
|
final StatusColors success;
|
|
final StatusColors warning;
|
|
final StatusColors info;
|
|
final StatusColors destructive;
|
|
}
|
|
|
|
@immutable
|
|
class TypographyThemeExtension
|
|
extends ThemeExtension<TypographyThemeExtension> {
|
|
const TypographyThemeExtension({
|
|
required this.primaryFont,
|
|
required this.primaryFallback,
|
|
required this.serifFont,
|
|
required this.serifFallback,
|
|
required this.monospaceFont,
|
|
required this.monospaceFallback,
|
|
});
|
|
|
|
factory TypographyThemeExtension.fromVariant(TweakcnThemeVariant variant) {
|
|
final List<String> sansStack = _sanitizeFontStack(variant.fontSans);
|
|
final List<String> serifStack = _sanitizeFontStack(variant.fontSerif);
|
|
final List<String> monoStack = _sanitizeFontStack(variant.fontMono);
|
|
|
|
return TypographyThemeExtension(
|
|
primaryFont: _preferredFont(sansStack),
|
|
primaryFallback: _fallbackForStack(sansStack),
|
|
serifFont: _preferredFont(serifStack),
|
|
serifFallback: _fallbackForStack(serifStack),
|
|
monospaceFont: _preferredFont(monoStack),
|
|
monospaceFallback: _fallbackForStack(monoStack),
|
|
);
|
|
}
|
|
|
|
final String primaryFont;
|
|
final List<String> primaryFallback;
|
|
final String serifFont;
|
|
final List<String> serifFallback;
|
|
final String monospaceFont;
|
|
final List<String> monospaceFallback;
|
|
|
|
@override
|
|
TypographyThemeExtension copyWith({
|
|
String? primaryFont,
|
|
List<String>? primaryFallback,
|
|
String? serifFont,
|
|
List<String>? serifFallback,
|
|
String? monospaceFont,
|
|
List<String>? monospaceFallback,
|
|
}) {
|
|
return TypographyThemeExtension(
|
|
primaryFont: primaryFont ?? this.primaryFont,
|
|
primaryFallback: primaryFallback ?? this.primaryFallback,
|
|
serifFont: serifFont ?? this.serifFont,
|
|
serifFallback: serifFallback ?? this.serifFallback,
|
|
monospaceFont: monospaceFont ?? this.monospaceFont,
|
|
monospaceFallback: monospaceFallback ?? this.monospaceFallback,
|
|
);
|
|
}
|
|
|
|
@override
|
|
TypographyThemeExtension lerp(
|
|
covariant ThemeExtension<TypographyThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! TypographyThemeExtension) return this;
|
|
return t < 0.5 ? this : other;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class SurfaceThemeExtension extends ThemeExtension<SurfaceThemeExtension> {
|
|
const SurfaceThemeExtension({
|
|
required this.background,
|
|
required this.container,
|
|
required this.containerHighest,
|
|
required this.card,
|
|
required this.cardForeground,
|
|
required this.popover,
|
|
required this.popoverForeground,
|
|
required this.muted,
|
|
required this.mutedForeground,
|
|
required this.border,
|
|
required this.ring,
|
|
required this.input,
|
|
});
|
|
|
|
factory SurfaceThemeExtension.fromVariant(TweakcnThemeVariant variant) {
|
|
return SurfaceThemeExtension(
|
|
background: variant.background,
|
|
container: Color.lerp(variant.background, variant.card, 0.5)!,
|
|
containerHighest: variant.card,
|
|
card: variant.card,
|
|
cardForeground: variant.cardForeground,
|
|
popover: variant.popover,
|
|
popoverForeground: variant.popoverForeground,
|
|
muted: variant.muted,
|
|
mutedForeground: variant.mutedForeground,
|
|
border: variant.border,
|
|
ring: variant.ring,
|
|
input: variant.input,
|
|
);
|
|
}
|
|
|
|
final Color background;
|
|
final Color container;
|
|
final Color containerHighest;
|
|
final Color card;
|
|
final Color cardForeground;
|
|
final Color popover;
|
|
final Color popoverForeground;
|
|
final Color muted;
|
|
final Color mutedForeground;
|
|
final Color border;
|
|
final Color ring;
|
|
final Color input;
|
|
|
|
@override
|
|
SurfaceThemeExtension copyWith({
|
|
Color? background,
|
|
Color? container,
|
|
Color? containerHighest,
|
|
Color? card,
|
|
Color? cardForeground,
|
|
Color? popover,
|
|
Color? popoverForeground,
|
|
Color? muted,
|
|
Color? mutedForeground,
|
|
Color? border,
|
|
Color? ring,
|
|
Color? input,
|
|
}) {
|
|
return SurfaceThemeExtension(
|
|
background: background ?? this.background,
|
|
container: container ?? this.container,
|
|
containerHighest: containerHighest ?? this.containerHighest,
|
|
card: card ?? this.card,
|
|
cardForeground: cardForeground ?? this.cardForeground,
|
|
popover: popover ?? this.popover,
|
|
popoverForeground: popoverForeground ?? this.popoverForeground,
|
|
muted: muted ?? this.muted,
|
|
mutedForeground: mutedForeground ?? this.mutedForeground,
|
|
border: border ?? this.border,
|
|
ring: ring ?? this.ring,
|
|
input: input ?? this.input,
|
|
);
|
|
}
|
|
|
|
@override
|
|
SurfaceThemeExtension lerp(
|
|
covariant ThemeExtension<SurfaceThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! SurfaceThemeExtension) return this;
|
|
return t < 0.5 ? this : other;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class ShadowThemeExtension extends ThemeExtension<ShadowThemeExtension> {
|
|
const ShadowThemeExtension({
|
|
required this.shadow2Xs,
|
|
required this.shadowXs,
|
|
required this.shadowSm,
|
|
required this.shadow,
|
|
required this.shadowMd,
|
|
required this.shadowLg,
|
|
required this.shadowXl,
|
|
required this.shadow2Xl,
|
|
});
|
|
|
|
factory ShadowThemeExtension.standard() {
|
|
return ShadowThemeExtension(
|
|
shadow2Xs: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.05),
|
|
]),
|
|
shadowXs: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.05),
|
|
]),
|
|
shadowSm: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.10),
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 2, spread: -1, opacity: 0.10),
|
|
]),
|
|
shadow: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.10),
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 2, spread: -1, opacity: 0.10),
|
|
]),
|
|
shadowMd: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.10),
|
|
_ShadowSpec(dx: 0, dy: 2, blur: 4, spread: -1, opacity: 0.10),
|
|
]),
|
|
shadowLg: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.10),
|
|
_ShadowSpec(dx: 0, dy: 4, blur: 6, spread: -1, opacity: 0.10),
|
|
]),
|
|
shadowXl: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.10),
|
|
_ShadowSpec(dx: 0, dy: 8, blur: 10, spread: -1, opacity: 0.10),
|
|
]),
|
|
shadow2Xl: _buildShadow(const <_ShadowSpec>[
|
|
_ShadowSpec(dx: 0, dy: 1, blur: 3, spread: 0, opacity: 0.25),
|
|
]),
|
|
);
|
|
}
|
|
|
|
final List<BoxShadow> shadow2Xs;
|
|
final List<BoxShadow> shadowXs;
|
|
final List<BoxShadow> shadowSm;
|
|
final List<BoxShadow> shadow;
|
|
final List<BoxShadow> shadowMd;
|
|
final List<BoxShadow> shadowLg;
|
|
final List<BoxShadow> shadowXl;
|
|
final List<BoxShadow> shadow2Xl;
|
|
|
|
@override
|
|
ShadowThemeExtension copyWith({
|
|
List<BoxShadow>? shadow2Xs,
|
|
List<BoxShadow>? shadowXs,
|
|
List<BoxShadow>? shadowSm,
|
|
List<BoxShadow>? shadow,
|
|
List<BoxShadow>? shadowMd,
|
|
List<BoxShadow>? shadowLg,
|
|
List<BoxShadow>? shadowXl,
|
|
List<BoxShadow>? shadow2Xl,
|
|
}) {
|
|
return ShadowThemeExtension(
|
|
shadow2Xs: shadow2Xs ?? this.shadow2Xs,
|
|
shadowXs: shadowXs ?? this.shadowXs,
|
|
shadowSm: shadowSm ?? this.shadowSm,
|
|
shadow: shadow ?? this.shadow,
|
|
shadowMd: shadowMd ?? this.shadowMd,
|
|
shadowLg: shadowLg ?? this.shadowLg,
|
|
shadowXl: shadowXl ?? this.shadowXl,
|
|
shadow2Xl: shadow2Xl ?? this.shadow2Xl,
|
|
);
|
|
}
|
|
|
|
@override
|
|
ShadowThemeExtension lerp(
|
|
covariant ThemeExtension<ShadowThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! ShadowThemeExtension) return this;
|
|
return t < 0.5 ? this : other;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class ShapeThemeExtension extends ThemeExtension<ShapeThemeExtension> {
|
|
const ShapeThemeExtension({
|
|
required this.radiusBase,
|
|
required this.radiusSm,
|
|
required this.radiusMd,
|
|
required this.radiusLg,
|
|
required this.radiusXl,
|
|
});
|
|
|
|
factory ShapeThemeExtension.fromVariant(TweakcnThemeVariant variant) {
|
|
final double base = variant.radius;
|
|
return ShapeThemeExtension(
|
|
radiusBase: base,
|
|
radiusSm: math.max(0, base - 4),
|
|
radiusMd: math.max(0, base - 2),
|
|
radiusLg: base,
|
|
radiusXl: base + 4,
|
|
);
|
|
}
|
|
|
|
final double radiusBase;
|
|
final double radiusSm;
|
|
final double radiusMd;
|
|
final double radiusLg;
|
|
final double radiusXl;
|
|
|
|
BorderRadius get small => BorderRadius.circular(radiusSm);
|
|
BorderRadius get medium => BorderRadius.circular(radiusMd);
|
|
BorderRadius get large => BorderRadius.circular(radiusLg);
|
|
BorderRadius get extraLarge => BorderRadius.circular(radiusXl);
|
|
|
|
@override
|
|
ShapeThemeExtension copyWith({
|
|
double? radiusBase,
|
|
double? radiusSm,
|
|
double? radiusMd,
|
|
double? radiusLg,
|
|
double? radiusXl,
|
|
}) {
|
|
return ShapeThemeExtension(
|
|
radiusBase: radiusBase ?? this.radiusBase,
|
|
radiusSm: radiusSm ?? this.radiusSm,
|
|
radiusMd: radiusMd ?? this.radiusMd,
|
|
radiusLg: radiusLg ?? this.radiusLg,
|
|
radiusXl: radiusXl ?? this.radiusXl,
|
|
);
|
|
}
|
|
|
|
@override
|
|
ShapeThemeExtension lerp(
|
|
covariant ThemeExtension<ShapeThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! ShapeThemeExtension) return this;
|
|
return ShapeThemeExtension(
|
|
radiusBase: lerpDouble(radiusBase, other.radiusBase, t),
|
|
radiusSm: lerpDouble(radiusSm, other.radiusSm, t),
|
|
radiusMd: lerpDouble(radiusMd, other.radiusMd, t),
|
|
radiusLg: lerpDouble(radiusLg, other.radiusLg, t),
|
|
radiusXl: lerpDouble(radiusXl, other.radiusXl, t),
|
|
);
|
|
}
|
|
|
|
static double lerpDouble(double a, double b, double t) {
|
|
return a + (b - a) * t;
|
|
}
|
|
}
|
|
|
|
@immutable
|
|
class SidebarThemeExtension extends ThemeExtension<SidebarThemeExtension> {
|
|
const SidebarThemeExtension({
|
|
required this.background,
|
|
required this.foreground,
|
|
required this.primary,
|
|
required this.primaryForeground,
|
|
required this.accent,
|
|
required this.accentForeground,
|
|
required this.border,
|
|
required this.ring,
|
|
});
|
|
|
|
factory SidebarThemeExtension.fromVariant(TweakcnThemeVariant variant) {
|
|
return SidebarThemeExtension(
|
|
background: variant.sidebarBackground,
|
|
foreground: variant.sidebarForeground,
|
|
primary: variant.sidebarPrimary,
|
|
primaryForeground: variant.sidebarPrimaryForeground,
|
|
accent: variant.sidebarAccent,
|
|
accentForeground: variant.sidebarAccentForeground,
|
|
border: variant.sidebarBorder,
|
|
ring: variant.sidebarRing,
|
|
);
|
|
}
|
|
|
|
final Color background;
|
|
final Color foreground;
|
|
final Color primary;
|
|
final Color primaryForeground;
|
|
final Color accent;
|
|
final Color accentForeground;
|
|
final Color border;
|
|
final Color ring;
|
|
|
|
@override
|
|
SidebarThemeExtension copyWith({
|
|
Color? background,
|
|
Color? foreground,
|
|
Color? primary,
|
|
Color? primaryForeground,
|
|
Color? accent,
|
|
Color? accentForeground,
|
|
Color? border,
|
|
Color? ring,
|
|
}) {
|
|
return SidebarThemeExtension(
|
|
background: background ?? this.background,
|
|
foreground: foreground ?? this.foreground,
|
|
primary: primary ?? this.primary,
|
|
primaryForeground: primaryForeground ?? this.primaryForeground,
|
|
accent: accent ?? this.accent,
|
|
accentForeground: accentForeground ?? this.accentForeground,
|
|
border: border ?? this.border,
|
|
ring: ring ?? this.ring,
|
|
);
|
|
}
|
|
|
|
@override
|
|
SidebarThemeExtension lerp(
|
|
covariant ThemeExtension<SidebarThemeExtension>? other,
|
|
double t,
|
|
) {
|
|
if (other is! SidebarThemeExtension) return this;
|
|
return SidebarThemeExtension(
|
|
background: Color.lerp(background, other.background, t)!,
|
|
foreground: Color.lerp(foreground, other.foreground, t)!,
|
|
primary: Color.lerp(primary, other.primary, t)!,
|
|
primaryForeground: Color.lerp(
|
|
primaryForeground,
|
|
other.primaryForeground,
|
|
t,
|
|
)!,
|
|
accent: Color.lerp(accent, other.accent, t)!,
|
|
accentForeground: Color.lerp(
|
|
accentForeground,
|
|
other.accentForeground,
|
|
t,
|
|
)!,
|
|
border: Color.lerp(border, other.border, t)!,
|
|
ring: Color.lerp(ring, other.ring, t)!,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ShadowSpec {
|
|
const _ShadowSpec({
|
|
required this.dx,
|
|
required this.dy,
|
|
required this.blur,
|
|
required this.spread,
|
|
required this.opacity,
|
|
});
|
|
|
|
final double dx;
|
|
final double dy;
|
|
final double blur;
|
|
final double spread;
|
|
final double opacity;
|
|
}
|
|
|
|
List<BoxShadow> _buildShadow(List<_ShadowSpec> specs) {
|
|
return specs
|
|
.map(
|
|
(spec) => BoxShadow(
|
|
color: Colors.black.withValues(alpha: spec.opacity),
|
|
offset: Offset(spec.dx, spec.dy),
|
|
blurRadius: spec.blur,
|
|
spreadRadius: spec.spread,
|
|
),
|
|
)
|
|
.toList(growable: false);
|
|
}
|
|
|
|
final Set<String> _genericFontFamilies = <String>{
|
|
'ui-sans-serif',
|
|
'system-ui',
|
|
'-apple-system',
|
|
'BlinkMacSystemFont',
|
|
'sans-serif',
|
|
'ui-serif',
|
|
'serif',
|
|
'ui-monospace',
|
|
'monospace',
|
|
};
|
|
|
|
List<String> _sanitizeFontStack(List<String> stack) {
|
|
final List<String> cleaned = <String>[];
|
|
for (final String raw in stack) {
|
|
final String trimmed = raw
|
|
.trim()
|
|
.replaceAll(RegExp("^[\"']"), '')
|
|
.replaceAll(RegExp("[\"']\$"), '');
|
|
if (trimmed.isEmpty) continue;
|
|
if (!cleaned.contains(trimmed)) cleaned.add(trimmed);
|
|
}
|
|
return cleaned;
|
|
}
|
|
|
|
String _preferredFont(List<String> stack) {
|
|
for (final String font in stack) {
|
|
if (!_genericFontFamilies.contains(font)) {
|
|
return font;
|
|
}
|
|
}
|
|
return stack.isNotEmpty ? stack.first : '';
|
|
}
|
|
|
|
List<String> _fallbackForStack(List<String> stack) {
|
|
if (stack.isEmpty) return const <String>[];
|
|
final String primary = _preferredFont(stack);
|
|
return stack.where((font) => font != primary).toList(growable: false);
|
|
}
|
|
|
|
/// Consistent spacing values - Enhanced for production with better hierarchy
|
|
class Spacing {
|
|
// Base spacing scale (8pt grid system)
|
|
static const double xxs = 2.0;
|
|
static const double xs = 4.0;
|
|
static const double sm = 8.0;
|
|
static const double md = 16.0;
|
|
static const double lg = 24.0;
|
|
static const double xl = 32.0;
|
|
static const double xxl = 48.0;
|
|
static const double xxxl = 64.0;
|
|
|
|
// Enhanced spacing for specific components with better hierarchy
|
|
static const double buttonPadding = 16.0;
|
|
static const double cardPadding = 20.0;
|
|
static const double inputPadding = 16.0;
|
|
static const double modalPadding = 24.0;
|
|
static const double messagePadding = 16.0;
|
|
static const double navigationPadding = 12.0;
|
|
static const double listItemPadding = 16.0;
|
|
static const double sectionPadding = 24.0;
|
|
static const double pagePadding = 20.0;
|
|
static const double screenPadding = 16.0;
|
|
|
|
// Spacing for different densities with improved hierarchy
|
|
static const double compact = 8.0;
|
|
static const double comfortable = 16.0;
|
|
static const double spacious = 24.0;
|
|
static const double extraSpacious = 32.0;
|
|
|
|
// Specific component spacing with better consistency
|
|
static const double chatBubblePadding = 12.0;
|
|
static const double actionButtonPadding = 12.0;
|
|
static const double floatingButtonPadding = 16.0;
|
|
static const double bottomSheetPadding = 24.0;
|
|
static const double dialogPadding = 20.0;
|
|
static const double snackbarPadding = 16.0;
|
|
|
|
// Layout spacing with improved hierarchy
|
|
static const double gridGap = 16.0;
|
|
static const double listGap = 12.0;
|
|
static const double sectionGap = 32.0;
|
|
static const double contentGap = 24.0;
|
|
|
|
// Enhanced spacing for better visual hierarchy
|
|
static const double micro = 4.0;
|
|
static const double small = 8.0;
|
|
static const double medium = 16.0;
|
|
static const double large = 24.0;
|
|
static const double extraLarge = 32.0;
|
|
static const double huge = 48.0;
|
|
static const double massive = 64.0;
|
|
|
|
// Component-specific spacing
|
|
static const double iconSpacing = 8.0;
|
|
static const double textSpacing = 4.0;
|
|
static const double borderSpacing = 1.0;
|
|
static const double shadowSpacing = 2.0;
|
|
}
|
|
|
|
/// Consistent border radius values - Enhanced for production with better hierarchy
|
|
class AppBorderRadius {
|
|
// Base radius scale
|
|
static const double xs = 4.0;
|
|
static const double sm = 8.0;
|
|
static const double md = 12.0;
|
|
static const double lg = 16.0;
|
|
static const double xl = 24.0;
|
|
static const double round = 999.0;
|
|
|
|
// Enhanced radius values for specific components with better hierarchy
|
|
static const double button = 12.0;
|
|
static const double card = 16.0;
|
|
static const double input = 12.0;
|
|
static const double modal = 20.0;
|
|
static const double messageBubble = 12.0;
|
|
static const double navigation = 12.0;
|
|
static const double avatar = 50.0;
|
|
static const double badge = 20.0;
|
|
static const double chip = 16.0;
|
|
static const double tooltip = 8.0;
|
|
|
|
// Border radius for different sizes with improved hierarchy
|
|
static const double small = 6.0;
|
|
static const double medium = 12.0;
|
|
static const double large = 18.0;
|
|
static const double extraLarge = 24.0;
|
|
static const double pill = 999.0;
|
|
|
|
// Specific component radius with better consistency
|
|
static const double chatBubble = 20.0;
|
|
static const double actionButton = 14.0;
|
|
static const double floatingButton = 28.0;
|
|
static const double bottomSheet = 24.0;
|
|
static const double dialog = 16.0;
|
|
static const double snackbar = 8.0;
|
|
|
|
// Enhanced radius values for better visual hierarchy
|
|
static const double micro = 2.0;
|
|
static const double tiny = 4.0;
|
|
static const double standard = 8.0;
|
|
static const double comfortable = 12.0;
|
|
static const double spacious = 16.0;
|
|
static const double extraSpacious = 24.0;
|
|
static const double circular = 999.0;
|
|
}
|
|
|
|
/// Consistent border width values - Enhanced for production
|
|
class BorderWidth {
|
|
static const double thin = 0.5;
|
|
static const double regular = 1.0;
|
|
static const double medium = 1.5;
|
|
static const double thick = 2.0;
|
|
|
|
// Enhanced border widths for better visual hierarchy
|
|
static const double micro = 0.5;
|
|
static const double small = 1.0;
|
|
static const double standard = 1.5;
|
|
static const double large = 2.0;
|
|
static const double extraLarge = 3.0;
|
|
}
|
|
|
|
/// Consistent elevation values - Enhanced for production with better hierarchy
|
|
class Elevation {
|
|
static const double none = 0.0;
|
|
static const double low = 2.0;
|
|
static const double medium = 4.0;
|
|
static const double high = 8.0;
|
|
static const double highest = 16.0;
|
|
|
|
// Enhanced elevation values for better visual hierarchy
|
|
static const double micro = 1.0;
|
|
static const double small = 2.0;
|
|
static const double standard = 4.0;
|
|
static const double large = 8.0;
|
|
static const double extraLarge = 16.0;
|
|
static const double massive = 24.0;
|
|
}
|
|
|
|
/// Helper class for consistent shadows - Enhanced for production with better hierarchy
|
|
class ConduitShadows {
|
|
static List<BoxShadow> low(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.08,
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
);
|
|
|
|
static List<BoxShadow> medium(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.12,
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 4),
|
|
);
|
|
|
|
static List<BoxShadow> high(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.16,
|
|
blurRadius: 24,
|
|
offset: const Offset(0, 8),
|
|
);
|
|
|
|
static List<BoxShadow> glow(BuildContext context) =>
|
|
glowWithTokens(context.colorTokens);
|
|
|
|
static List<BoxShadow> glowWithTokens(AppColorTokens tokens) {
|
|
final double alpha = tokens.brightness == Brightness.light ? 0.25 : 0.35;
|
|
return [
|
|
BoxShadow(
|
|
color: tokens.brandTone60.withValues(alpha: alpha),
|
|
blurRadius: 20,
|
|
offset: const Offset(0, 0),
|
|
spreadRadius: 0,
|
|
),
|
|
];
|
|
}
|
|
|
|
static List<BoxShadow> card(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.06,
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 3),
|
|
);
|
|
|
|
static List<BoxShadow> button(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.1,
|
|
blurRadius: 6,
|
|
offset: const Offset(0, 2),
|
|
);
|
|
|
|
static List<BoxShadow> modal(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.2,
|
|
blurRadius: 32,
|
|
offset: const Offset(0, 12),
|
|
);
|
|
|
|
static List<BoxShadow> navigation(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.08,
|
|
blurRadius: 16,
|
|
offset: const Offset(0, -2),
|
|
);
|
|
|
|
static List<BoxShadow> messageBubble(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.04,
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 1),
|
|
);
|
|
|
|
static List<BoxShadow> input(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.05,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 1),
|
|
);
|
|
|
|
static List<BoxShadow> pressed(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.15,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 1),
|
|
);
|
|
|
|
static List<BoxShadow> hover(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.12,
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 4),
|
|
);
|
|
|
|
static List<BoxShadow> micro(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.04,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 1),
|
|
);
|
|
|
|
static List<BoxShadow> small(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.06,
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
);
|
|
|
|
static List<BoxShadow> standard(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.08,
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 3),
|
|
);
|
|
|
|
static List<BoxShadow> large(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.12,
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 4),
|
|
);
|
|
|
|
static List<BoxShadow> extraLarge(BuildContext context) => _shadow(
|
|
context.colorTokens,
|
|
opacity: 0.16,
|
|
blurRadius: 24,
|
|
offset: const Offset(0, 8),
|
|
);
|
|
|
|
static List<BoxShadow> _shadow(
|
|
AppColorTokens tokens, {
|
|
required double opacity,
|
|
required double blurRadius,
|
|
required Offset offset,
|
|
}) {
|
|
return [
|
|
BoxShadow(
|
|
color: _overlayColor(tokens, opacity),
|
|
blurRadius: blurRadius,
|
|
offset: offset,
|
|
spreadRadius: 0,
|
|
),
|
|
];
|
|
}
|
|
|
|
static Color _overlayColor(AppColorTokens tokens, double alpha) {
|
|
final Color base = tokens.overlayStrong.withValues(alpha: 1.0);
|
|
return base.withValues(alpha: alpha.clamp(0.0, 1.0));
|
|
}
|
|
}
|
|
|
|
/// Typography scale following Conduit design tokens - Enhanced for production
|
|
class AppTypography {
|
|
// Primary UI font now uses the platform default system font
|
|
static const String fontFamily = '';
|
|
static const String monospaceFontFamily = 'SF Mono';
|
|
|
|
// Letter spacing values - Enhanced for better readability
|
|
static const double letterSpacingTight = -0.5;
|
|
static const double letterSpacingNormal = 0.0;
|
|
static const double letterSpacingWide = 0.5;
|
|
static const double letterSpacingExtraWide = 1.0;
|
|
|
|
// Font sizes - Enhanced scale for better hierarchy
|
|
static const double displayLarge = 48;
|
|
static const double displayMedium = 36;
|
|
static const double displaySmall = 32;
|
|
static const double headlineLarge = 28;
|
|
static const double headlineMedium = 24;
|
|
static const double headlineSmall = 20;
|
|
static const double bodyLarge = 18;
|
|
static const double bodyMedium = 16;
|
|
static const double bodySmall = 14;
|
|
static const double labelLarge = 16;
|
|
static const double labelMedium = 14;
|
|
static const double labelSmall = 12;
|
|
|
|
// Text styles following Conduit design - Enhanced for production
|
|
static final TextStyle displayLargeStyle = const TextStyle(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.8,
|
|
height: 1.1,
|
|
).copyWith(fontSize: displayLarge);
|
|
|
|
static final TextStyle displayMediumStyle = const TextStyle(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.6,
|
|
height: 1.2,
|
|
).copyWith(fontSize: displayMedium);
|
|
|
|
static final TextStyle bodyLargeStyle = const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.6,
|
|
).copyWith(fontSize: bodyLarge);
|
|
|
|
static final TextStyle bodyMediumStyle = const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.6,
|
|
).copyWith(fontSize: bodyMedium);
|
|
|
|
static final TextStyle codeStyle = const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.5,
|
|
fontFamily: monospaceFontFamily,
|
|
).copyWith(fontSize: bodySmall);
|
|
|
|
// Additional styled text getters for convenience - Enhanced
|
|
static TextStyle get headlineLargeStyle => const TextStyle(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.4,
|
|
height: 1.3,
|
|
).copyWith(fontSize: headlineLarge);
|
|
|
|
static TextStyle get headlineMediumStyle => const TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: -0.2,
|
|
height: 1.3,
|
|
).copyWith(fontSize: headlineMedium);
|
|
|
|
static TextStyle get headlineSmallStyle => const TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 0,
|
|
height: 1.4,
|
|
).copyWith(fontSize: headlineSmall);
|
|
|
|
static TextStyle get bodySmallStyle => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.5,
|
|
).copyWith(fontSize: bodySmall);
|
|
|
|
// Enhanced text styles for chat messages
|
|
static TextStyle get chatMessageStyle => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0.0,
|
|
height: 1.4,
|
|
).copyWith(fontSize: bodyMedium);
|
|
|
|
static TextStyle get chatCodeStyle => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.5,
|
|
fontFamily: monospaceFontFamily,
|
|
).copyWith(fontSize: bodySmall);
|
|
|
|
// Enhanced label styles
|
|
static TextStyle get labelStyle => const TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
letterSpacing: 0.2,
|
|
height: 1.4,
|
|
).copyWith(fontSize: labelMedium);
|
|
|
|
// Enhanced caption styles
|
|
static TextStyle get captionStyle => const TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
letterSpacing: 0.5,
|
|
height: 1.3,
|
|
).copyWith(fontSize: labelSmall);
|
|
|
|
// Enhanced typography for better hierarchy
|
|
static TextStyle get micro => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0.1,
|
|
height: 1.4,
|
|
).copyWith(fontSize: 10);
|
|
|
|
static TextStyle get tiny => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0.1,
|
|
height: 1.4,
|
|
).copyWith(fontSize: 12);
|
|
|
|
static TextStyle get small => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.5,
|
|
).copyWith(fontSize: 14);
|
|
|
|
static TextStyle get standard => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.6,
|
|
).copyWith(fontSize: 16);
|
|
|
|
static TextStyle get large => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.6,
|
|
).copyWith(fontSize: 18);
|
|
|
|
static TextStyle get extraLarge => const TextStyle(
|
|
fontWeight: FontWeight.w400,
|
|
letterSpacing: 0,
|
|
height: 1.5,
|
|
).copyWith(fontSize: 20);
|
|
|
|
static TextStyle get huge => const TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: -0.2,
|
|
height: 1.3,
|
|
).copyWith(fontSize: 24);
|
|
|
|
static TextStyle get massive => const TextStyle(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.4,
|
|
height: 1.2,
|
|
).copyWith(fontSize: 32);
|
|
}
|
|
|
|
/// Consistent icon sizes - Enhanced for production with better hierarchy
|
|
class IconSize {
|
|
static const double xs = 12.0;
|
|
static const double sm = 16.0;
|
|
static const double md = 20.0;
|
|
static const double lg = 24.0;
|
|
static const double xl = 32.0;
|
|
static const double xxl = 48.0;
|
|
|
|
// Enhanced icon sizes for specific components with better hierarchy
|
|
static const double button = 20.0;
|
|
static const double card = 24.0;
|
|
static const double input = 20.0;
|
|
static const double modal = 24.0;
|
|
static const double message = 18.0;
|
|
static const double navigation = 24.0;
|
|
static const double avatar = 40.0;
|
|
static const double badge = 16.0;
|
|
static const double chip = 18.0;
|
|
static const double tooltip = 16.0;
|
|
|
|
// Icon sizes for different contexts with improved hierarchy
|
|
static const double micro = 12.0;
|
|
static const double small = 16.0;
|
|
static const double medium = 20.0;
|
|
static const double large = 24.0;
|
|
static const double extraLarge = 32.0;
|
|
static const double huge = 48.0;
|
|
|
|
// Specific component icon sizes with better consistency
|
|
static const double chatBubble = 18.0;
|
|
static const double actionButton = 20.0;
|
|
static const double floatingButton = 24.0;
|
|
static const double bottomSheet = 24.0;
|
|
static const double dialog = 24.0;
|
|
static const double snackbar = 20.0;
|
|
static const double tabBar = 24.0;
|
|
static const double appBar = 24.0;
|
|
static const double listItem = 20.0;
|
|
static const double formField = 20.0;
|
|
}
|
|
|
|
/// Alpha values for opacity/transparency - Enhanced for production with better hierarchy
|
|
class Alpha {
|
|
static const double subtle = 0.1;
|
|
static const double light = 0.3;
|
|
static const double medium = 0.5;
|
|
static const double strong = 0.7;
|
|
static const double intense = 0.9;
|
|
|
|
// Enhanced alpha values for specific use cases with better hierarchy
|
|
static const double disabled = 0.38;
|
|
static const double overlay = 0.5;
|
|
static const double backdrop = 0.6;
|
|
static const double highlight = 0.12;
|
|
static const double pressed = 0.2;
|
|
static const double hover = 0.08;
|
|
static const double focus = 0.12;
|
|
static const double selected = 0.16;
|
|
static const double active = 0.24;
|
|
static const double inactive = 0.6;
|
|
|
|
// Alpha values for different states with improved hierarchy
|
|
static const double primary = 1.0;
|
|
static const double secondary = 0.7;
|
|
static const double tertiary = 0.5;
|
|
static const double quaternary = 0.3;
|
|
static const double disabledText = 0.38;
|
|
static const double disabledIcon = 0.38;
|
|
static const double disabledBackground = 0.12;
|
|
|
|
// Specific component alpha values with better consistency
|
|
static const double buttonPressed = 0.2;
|
|
static const double buttonHover = 0.08;
|
|
static const double cardHover = 0.04;
|
|
static const double inputFocus = 0.12;
|
|
static const double modalBackdrop = 0.6;
|
|
static const double snackbarBackground = 0.95;
|
|
static const double tooltipBackground = 0.9;
|
|
static const double badgeBackground = 0.1;
|
|
static const double chipBackground = 0.08;
|
|
static const double avatarBorder = 0.2;
|
|
|
|
// Enhanced alpha values for better visual hierarchy
|
|
static const double micro = 0.05;
|
|
static const double tiny = 0.1;
|
|
static const double small = 0.2;
|
|
static const double standard = 0.3;
|
|
static const double large = 0.5;
|
|
static const double extraLarge = 0.7;
|
|
static const double huge = 0.9;
|
|
}
|
|
|
|
/// Touch target sizes for accessibility compliance - Enhanced for production with better hierarchy
|
|
class TouchTarget {
|
|
static const double minimum = 44.0;
|
|
static const double comfortable = 48.0;
|
|
static const double large = 56.0;
|
|
|
|
// Enhanced touch targets for specific components with better hierarchy
|
|
static const double button = 48.0;
|
|
static const double card = 48.0;
|
|
static const double input = 48.0;
|
|
static const double modal = 48.0;
|
|
static const double message = 44.0;
|
|
static const double navigation = 48.0;
|
|
static const double avatar = 48.0;
|
|
static const double badge = 32.0;
|
|
static const double chip = 32.0;
|
|
static const double tooltip = 32.0;
|
|
|
|
// Touch targets for different contexts with improved hierarchy
|
|
static const double micro = 32.0;
|
|
static const double small = 40.0;
|
|
static const double medium = 48.0;
|
|
static const double standard = 56.0;
|
|
static const double extraLarge = 64.0;
|
|
static const double huge = 80.0;
|
|
|
|
// Specific component touch targets with better consistency
|
|
static const double chatBubble = 44.0;
|
|
static const double actionButton = 48.0;
|
|
static const double floatingButton = 56.0;
|
|
static const double bottomSheet = 48.0;
|
|
static const double dialog = 48.0;
|
|
static const double snackbar = 48.0;
|
|
static const double tabBar = 48.0;
|
|
static const double appBar = 48.0;
|
|
static const double listItem = 48.0;
|
|
static const double formField = 48.0;
|
|
static const double iconButton = 48.0;
|
|
static const double textButton = 44.0;
|
|
static const double toggle = 48.0;
|
|
static const double slider = 48.0;
|
|
static const double checkbox = 48.0;
|
|
static const double radio = 48.0;
|
|
}
|
|
|
|
/// Animation durations for consistent motion design - Enhanced for production with better hierarchy
|
|
class AnimationDuration {
|
|
static const Duration instant = Duration(milliseconds: 100);
|
|
static const Duration fast = Duration(milliseconds: 200);
|
|
static const Duration medium = Duration(milliseconds: 300);
|
|
static const Duration slow = Duration(milliseconds: 500);
|
|
static const Duration slower = Duration(milliseconds: 800);
|
|
static const Duration slowest = Duration(milliseconds: 1000);
|
|
static const Duration extraSlow = Duration(milliseconds: 1200);
|
|
static const Duration ultra = Duration(milliseconds: 1500);
|
|
static const Duration extended = Duration(seconds: 2);
|
|
static const Duration long = Duration(seconds: 4);
|
|
|
|
// Enhanced durations for specific interactions with better hierarchy
|
|
static const Duration microInteraction = Duration(milliseconds: 150);
|
|
static const Duration buttonPress = Duration(milliseconds: 100);
|
|
static const Duration cardHover = Duration(milliseconds: 200);
|
|
static const Duration pageTransition = Duration(milliseconds: 400);
|
|
static const Duration modalPresentation = Duration(milliseconds: 500);
|
|
static const Duration typingIndicator = Duration(milliseconds: 800);
|
|
static const Duration messageAppear = Duration(milliseconds: 350);
|
|
static const Duration messageSlide = Duration(milliseconds: 400);
|
|
|
|
// Enhanced durations for better visual hierarchy
|
|
static const Duration micro = Duration(milliseconds: 50);
|
|
static const Duration tiny = Duration(milliseconds: 100);
|
|
static const Duration small = Duration(milliseconds: 200);
|
|
static const Duration standard = Duration(milliseconds: 300);
|
|
static const Duration large = Duration(milliseconds: 500);
|
|
static const Duration extraLarge = Duration(milliseconds: 800);
|
|
static const Duration huge = Duration(milliseconds: 1200);
|
|
}
|
|
|
|
/// Animation curves for consistent motion design - Enhanced for production with better hierarchy
|
|
class AnimationCurves {
|
|
static const Curve easeIn = Curves.easeIn;
|
|
static const Curve easeOut = Curves.easeOut;
|
|
static const Curve easeInOut = Curves.easeInOut;
|
|
static const Curve bounce = Curves.bounceOut;
|
|
static const Curve elastic = Curves.elasticOut;
|
|
static const Curve fastOutSlowIn = Curves.fastOutSlowIn;
|
|
static const Curve linear = Curves.linear;
|
|
|
|
// Enhanced curves for specific interactions with better hierarchy
|
|
static const Curve buttonPress = Curves.easeOutCubic;
|
|
static const Curve cardHover = Curves.easeInOutCubic;
|
|
static const Curve messageSlide = Curves.easeOutCubic;
|
|
static const Curve typingIndicator = Curves.easeInOut;
|
|
static const Curve modalPresentation = Curves.easeOutBack;
|
|
static const Curve pageTransition = Curves.easeInOutCubic;
|
|
static const Curve microInteraction = Curves.easeOutQuart;
|
|
static const Curve spring = Curves.elasticOut;
|
|
|
|
// Enhanced curves for better visual hierarchy
|
|
static const Curve micro = Curves.easeOutQuart;
|
|
static const Curve tiny = Curves.easeOutCubic;
|
|
static const Curve small = Curves.easeInOutCubic;
|
|
static const Curve standard = Curves.easeInOut;
|
|
static const Curve large = Curves.easeOutBack;
|
|
static const Curve extraLarge = Curves.elasticOut;
|
|
static const Curve huge = Curves.bounceOut;
|
|
}
|
|
|
|
/// Common animation values - Enhanced for production with better hierarchy
|
|
class AnimationValues {
|
|
static const double fadeInOpacity = 0.0;
|
|
static const double fadeOutOpacity = 1.0;
|
|
static const Offset slideInFromTop = Offset(0, -0.05);
|
|
static const Offset slideInFromBottom = Offset(0, 0.05);
|
|
static const Offset slideInFromLeft = Offset(-0.05, 0);
|
|
static const Offset slideInFromRight = Offset(0.05, 0);
|
|
static const Offset slideCenter = Offset.zero;
|
|
static const double scaleMin = 0.0;
|
|
static const double scaleMax = 1.0;
|
|
static const double shimmerBegin = -1.0;
|
|
static const double shimmerEnd = 2.0;
|
|
|
|
// Enhanced values for specific interactions with better hierarchy
|
|
static const double buttonScalePressed = 0.95;
|
|
static const double buttonScaleHover = 1.02;
|
|
static const double cardScaleHover = 1.01;
|
|
static const double messageSlideDistance = 0.1;
|
|
static const double typingIndicatorScale = 0.8;
|
|
static const double modalScale = 0.9;
|
|
static const double pageSlideDistance = 0.15;
|
|
static const double microInteractionScale = 0.98;
|
|
|
|
// Enhanced values for better visual hierarchy
|
|
static const double micro = 0.95;
|
|
static const double tiny = 0.98;
|
|
static const double small = 1.01;
|
|
static const double standard = 1.02;
|
|
static const double large = 1.05;
|
|
static const double extraLarge = 1.1;
|
|
static const double huge = 1.2;
|
|
}
|
|
|
|
/// Delay values for staggered animations - Enhanced for production with better hierarchy
|
|
class AnimationDelay {
|
|
static const Duration none = Duration.zero;
|
|
static const Duration short = Duration(milliseconds: 100);
|
|
static const Duration medium = Duration(milliseconds: 200);
|
|
static const Duration long = Duration(milliseconds: 400);
|
|
static const Duration extraLong = Duration(milliseconds: 600);
|
|
static const Duration ultra = Duration(milliseconds: 800);
|
|
|
|
// Enhanced delays for specific interactions with better hierarchy
|
|
static const Duration microDelay = Duration(milliseconds: 50);
|
|
static const Duration buttonDelay = Duration(milliseconds: 75);
|
|
static const Duration cardDelay = Duration(milliseconds: 150);
|
|
static const Duration messageDelay = Duration(milliseconds: 100);
|
|
static const Duration typingDelay = Duration(milliseconds: 200);
|
|
static const Duration modalDelay = Duration(milliseconds: 300);
|
|
static const Duration pageDelay = Duration(milliseconds: 250);
|
|
static const Duration staggeredDelay = Duration(milliseconds: 50);
|
|
|
|
// Enhanced delays for better visual hierarchy
|
|
static const Duration micro = Duration(milliseconds: 25);
|
|
static const Duration tiny = Duration(milliseconds: 50);
|
|
static const Duration small = Duration(milliseconds: 100);
|
|
static const Duration standard = Duration(milliseconds: 200);
|
|
static const Duration large = Duration(milliseconds: 400);
|
|
static const Duration extraLarge = Duration(milliseconds: 600);
|
|
static const Duration huge = Duration(milliseconds: 800);
|
|
}
|