247 lines
9.2 KiB
Dart
247 lines
9.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
// ── Colour constants ──────────────────────────────────────────────────────────
|
|
|
|
const _darkBackground = Color(0xFF0F0F14);
|
|
const _darkSurface = Color(0xFF16161F);
|
|
const _darkSurfaceVar = Color(0xFF1A1A24);
|
|
const _darkPrimary = Color(0xFF7C3AED);
|
|
const _darkOnSurface = Color(0xFFE4E4F0);
|
|
const _darkOnSurfaceVar = Color(0xFF8888A8);
|
|
const _darkOutline = Color(0xFF2A2A3A);
|
|
|
|
const _lightBackground = Color(0xFFF5F5FB);
|
|
const _lightSurface = Color(0xFFFFFFFF);
|
|
const _lightSurfaceVar = Color(0xFFF0F0F8);
|
|
const _lightPrimary = Color(0xFF7C3AED);
|
|
const _lightOnSurface = Color(0xFF1A1A1A);
|
|
const _lightOnSurfaceVar = Color(0xFF666666);
|
|
const _lightOutline = Color(0xFFDDDDE8);
|
|
|
|
// ── Typography ─────────────────────────────────────────────────────────────────
|
|
|
|
TextTheme _buildTextTheme(TextTheme base) {
|
|
final fraunces = GoogleFonts.frauncesTextTheme(base);
|
|
return base.copyWith(
|
|
// Headings / titles use Fraunces
|
|
headlineLarge: fraunces.headlineLarge,
|
|
headlineMedium: fraunces.headlineMedium,
|
|
headlineSmall: fraunces.headlineSmall,
|
|
titleLarge: fraunces.titleLarge,
|
|
titleMedium: fraunces.titleMedium,
|
|
// Body / labels remain system default
|
|
);
|
|
}
|
|
|
|
// ── Themes ─────────────────────────────────────────────────────────────────────
|
|
|
|
ThemeData fabledDarkTheme() {
|
|
final cs = ColorScheme(
|
|
brightness: Brightness.dark,
|
|
primary: _darkPrimary,
|
|
onPrimary: Colors.white,
|
|
primaryContainer: const Color(0xFF5B21B6),
|
|
onPrimaryContainer: _darkOnSurface,
|
|
secondary: _darkPrimary,
|
|
onSecondary: Colors.white,
|
|
secondaryContainer: _darkSurfaceVar,
|
|
onSecondaryContainer: _darkOnSurface,
|
|
tertiary: _darkPrimary,
|
|
onTertiary: Colors.white,
|
|
tertiaryContainer: _darkSurfaceVar,
|
|
onTertiaryContainer: _darkOnSurface,
|
|
error: const Color(0xFFEF4444),
|
|
onError: Colors.white,
|
|
errorContainer: const Color(0xFF7F1D1D),
|
|
onErrorContainer: const Color(0xFFFEE2E2),
|
|
surface: _darkSurface,
|
|
onSurface: _darkOnSurface,
|
|
surfaceContainerHighest: _darkSurfaceVar,
|
|
onSurfaceVariant: _darkOnSurfaceVar,
|
|
outline: _darkOutline,
|
|
outlineVariant: _darkOutline,
|
|
shadow: Colors.black,
|
|
scrim: Colors.black,
|
|
inverseSurface: _darkOnSurface,
|
|
onInverseSurface: _darkSurface,
|
|
inversePrimary: _lightPrimary,
|
|
);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: cs,
|
|
scaffoldBackgroundColor: _darkBackground,
|
|
textTheme: _buildTextTheme(ThemeData.dark().textTheme),
|
|
cardTheme: CardThemeData(
|
|
color: _darkSurface,
|
|
elevation: 2,
|
|
shadowColor: Colors.black.withValues(alpha: 0.4),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
|
),
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
backgroundColor: _darkSurface,
|
|
indicatorColor: _darkPrimary.withValues(alpha: 0.2),
|
|
),
|
|
navigationRailTheme: NavigationRailThemeData(
|
|
backgroundColor: _darkSurface,
|
|
indicatorColor: _darkPrimary.withValues(alpha: 0.2),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: _darkSurfaceVar,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _darkOutline),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _darkOutline),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _darkPrimary, width: 2),
|
|
),
|
|
),
|
|
dividerTheme: DividerThemeData(color: _darkOutline, thickness: 1),
|
|
chipTheme: ChipThemeData(
|
|
backgroundColor: _darkSurfaceVar,
|
|
labelStyle: TextStyle(color: _darkOnSurfaceVar, fontSize: 12),
|
|
side: BorderSide(color: _darkOutline),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
),
|
|
);
|
|
}
|
|
|
|
ThemeData fabledLightTheme() {
|
|
final cs = ColorScheme(
|
|
brightness: Brightness.light,
|
|
primary: _lightPrimary,
|
|
onPrimary: Colors.white,
|
|
primaryContainer: const Color(0xFFEDE5FF),
|
|
onPrimaryContainer: _lightOnSurface,
|
|
secondary: _lightPrimary,
|
|
onSecondary: Colors.white,
|
|
secondaryContainer: _lightSurfaceVar,
|
|
onSecondaryContainer: _lightOnSurface,
|
|
tertiary: _lightPrimary,
|
|
onTertiary: Colors.white,
|
|
tertiaryContainer: _lightSurfaceVar,
|
|
onTertiaryContainer: _lightOnSurface,
|
|
error: const Color(0xFFDC2626),
|
|
onError: Colors.white,
|
|
errorContainer: const Color(0xFFFEE2E2),
|
|
onErrorContainer: const Color(0xFF7F1D1D),
|
|
surface: _lightSurface,
|
|
onSurface: _lightOnSurface,
|
|
surfaceContainerHighest: _lightSurfaceVar,
|
|
onSurfaceVariant: _lightOnSurfaceVar,
|
|
outline: _lightOutline,
|
|
outlineVariant: _lightOutline,
|
|
shadow: Colors.black,
|
|
scrim: Colors.black,
|
|
inverseSurface: _lightOnSurface,
|
|
onInverseSurface: _lightSurface,
|
|
inversePrimary: _darkPrimary,
|
|
);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: cs,
|
|
scaffoldBackgroundColor: _lightBackground,
|
|
textTheme: _buildTextTheme(ThemeData.light().textTheme),
|
|
cardTheme: CardThemeData(
|
|
color: _lightSurface,
|
|
elevation: 1,
|
|
shadowColor: Colors.black.withValues(alpha: 0.08),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
|
),
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
backgroundColor: _lightSurface,
|
|
indicatorColor: _lightPrimary.withValues(alpha: 0.12),
|
|
),
|
|
navigationRailTheme: NavigationRailThemeData(
|
|
backgroundColor: _lightSurface,
|
|
indicatorColor: _lightPrimary.withValues(alpha: 0.12),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: _lightSurfaceVar,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _lightOutline),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _lightOutline),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(24),
|
|
borderSide: BorderSide(color: _lightPrimary, width: 2),
|
|
),
|
|
),
|
|
dividerTheme: DividerThemeData(color: _lightOutline, thickness: 1),
|
|
chipTheme: ChipThemeData(
|
|
backgroundColor: _lightSurfaceVar,
|
|
labelStyle: TextStyle(color: _lightOnSurfaceVar, fontSize: 12),
|
|
side: BorderSide(color: _lightOutline),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ── GradientButton ─────────────────────────────────────────────────────────────
|
|
// Use wherever the web app uses the indigo gradient button (send, primary actions).
|
|
|
|
class GradientButton extends StatelessWidget {
|
|
final VoidCallback? onPressed;
|
|
final Widget child;
|
|
final EdgeInsetsGeometry padding;
|
|
|
|
const GradientButton({
|
|
super.key,
|
|
required this.onPressed,
|
|
required this.child,
|
|
this.padding = const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final disabled = onPressed == null;
|
|
return AnimatedOpacity(
|
|
opacity: disabled ? 0.45 : 1.0,
|
|
duration: const Duration(milliseconds: 150),
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
gradient: disabled
|
|
? null
|
|
: const LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [Color(0xFF7C3AED), Color(0xFF5B21B6)],
|
|
),
|
|
color: disabled ? const Color(0xFF7C3AED) : null,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: disabled
|
|
? null
|
|
: [
|
|
BoxShadow(
|
|
color: const Color(0xFF7C3AED).withValues(alpha: 0.45),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 3),
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: onPressed,
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Padding(padding: padding, child: child),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|