This repository has been archived on 2026-06-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FabledApp/lib/core/theme.dart
T

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),
),
),
),
);
}
}