180 lines
6.5 KiB
Dart
180 lines
6.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
// ── Couleurs custom ───────────────────────────────────────────────────────────
|
|
// Couleurs custom pour le thème vert sage (Light Theme)
|
|
const kSurfaceBg = Color(0xFFEDF1E7); // Fond vert très clair (de l'image)
|
|
const kSurfaceCard = Color(0xFFF6F9F6); // Fond de carte off-white
|
|
const kSurfaceElevated = Color(0xFFCAD8C3); // Sage green pour les surfaces surélevées
|
|
const kGreenAccent = Color(0xFF3B5238); // Vert sombre pour le texte et les accents
|
|
const kGreenAccentDim = Color(0xFF556B52);
|
|
const kRedAccent = Color(0xFFBA1A1A); // Rouge d'erreur Material 3
|
|
const kTextPrimary = Color(0xFF191D17); // Texte principal sombre
|
|
const kTextSecondary = Color(0xFF43493E); // Texte secondaire gris-vert
|
|
const kDivider = Color(0xFFDDE4D7); // Séparateurs clairs
|
|
|
|
final ThemeData ynabTheme = ThemeData(
|
|
useMaterial3: true,
|
|
brightness: Brightness.light,
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFFCAD8C3),
|
|
brightness: Brightness.light,
|
|
surface: kSurfaceBg,
|
|
primary: kGreenAccent,
|
|
secondary: kGreenAccentDim,
|
|
error: kRedAccent,
|
|
onSurface: kTextPrimary,
|
|
onSurfaceVariant: kTextSecondary,
|
|
outline: kDivider,
|
|
),
|
|
scaffoldBackgroundColor: kSurfaceBg,
|
|
cardTheme: CardThemeData(
|
|
color: kSurfaceCard,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
),
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
backgroundColor: kSurfaceCard,
|
|
indicatorColor: const Color(0xFFCAD8C3),
|
|
iconTheme: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return const IconThemeData(color: Color(0xFF191D17));
|
|
}
|
|
return const IconThemeData(color: Color(0xFF43493E));
|
|
}),
|
|
labelTextStyle: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return GoogleFonts.inter(
|
|
color: const Color(0xFF191D17),
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
);
|
|
}
|
|
return GoogleFonts.inter(
|
|
color: const Color(0xFF43493E),
|
|
fontSize: 12,
|
|
);
|
|
}),
|
|
),
|
|
appBarTheme: AppBarTheme(
|
|
backgroundColor: kSurfaceBg,
|
|
elevation: 0,
|
|
scrolledUnderElevation: 1,
|
|
centerTitle: false,
|
|
titleTextStyle: GoogleFonts.inter(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: kTextPrimary,
|
|
),
|
|
iconTheme: const IconThemeData(color: kTextSecondary),
|
|
),
|
|
textTheme: GoogleFonts.interTextTheme(
|
|
ThemeData.light().textTheme.copyWith(
|
|
headlineLarge: const TextStyle(color: kTextPrimary),
|
|
headlineMedium: const TextStyle(color: kTextPrimary),
|
|
headlineSmall: const TextStyle(color: kTextPrimary),
|
|
titleLarge: const TextStyle(color: kTextPrimary),
|
|
titleMedium: const TextStyle(color: kTextPrimary),
|
|
titleSmall: const TextStyle(color: kTextPrimary),
|
|
bodyLarge: const TextStyle(color: kTextPrimary),
|
|
bodyMedium: const TextStyle(color: kTextPrimary, fontSize: 14),
|
|
bodySmall: const TextStyle(color: kTextSecondary, fontSize: 12),
|
|
labelLarge: const TextStyle(color: kTextPrimary),
|
|
labelMedium: const TextStyle(color: kTextSecondary),
|
|
labelSmall: const TextStyle(color: kTextSecondary),
|
|
),
|
|
),
|
|
dividerTheme: const DividerThemeData(color: kDivider, thickness: 1),
|
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
|
backgroundColor: const Color(0xFFCAD8C3),
|
|
foregroundColor: kTextPrimary,
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: kSurfaceCard,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: const BorderSide(color: kDivider),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: const BorderSide(color: kGreenAccent, width: 1.5),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
),
|
|
bottomSheetTheme: const BottomSheetThemeData(
|
|
backgroundColor: kSurfaceCard,
|
|
showDragHandle: true,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
),
|
|
sliderTheme: SliderThemeData(
|
|
activeTrackColor: kGreenAccent,
|
|
thumbColor: kGreenAccent,
|
|
inactiveTrackColor: kDivider,
|
|
overlayColor: kGreenAccent.withAlpha(30),
|
|
),
|
|
dialogTheme: DialogThemeData(
|
|
backgroundColor: kSurfaceCard,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
),
|
|
searchBarTheme: SearchBarThemeData(
|
|
elevation: WidgetStatePropertyAll(0),
|
|
backgroundColor: WidgetStatePropertyAll(kSurfaceCard),
|
|
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(28),
|
|
side: BorderSide(color: kDivider),
|
|
)),
|
|
padding: WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 8)),
|
|
textStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontSize: 14, color: kTextPrimary)),
|
|
hintStyle: WidgetStatePropertyAll(GoogleFonts.inter(fontSize: 14, color: kTextSecondary)),
|
|
),
|
|
);
|
|
|
|
final ThemeData ynabDarkTheme = ThemeData(
|
|
useMaterial3: true,
|
|
brightness: Brightness.dark,
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFFCAD8C3),
|
|
brightness: Brightness.dark,
|
|
),
|
|
cardTheme: CardThemeData(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
),
|
|
);
|
|
|
|
// Utils
|
|
String formatCurrency(double amount) {
|
|
final sign = amount < 0 ? '-' : '';
|
|
final abs = amount.abs();
|
|
final parts = abs.toStringAsFixed(2).split('.');
|
|
final intPart = parts[0];
|
|
final decPart = parts[1];
|
|
final buf = StringBuffer();
|
|
int count = 0;
|
|
for (int i = intPart.length - 1; i >= 0; i--) {
|
|
if (count > 0 && count % 3 == 0) buf.write(' ');
|
|
buf.write(intPart[i]);
|
|
count++;
|
|
}
|
|
final formatted = '${buf.toString().split('').reversed.join()},$decPart';
|
|
return '$sign$formatted €';
|
|
}
|
|
|
|
String formatCurrencyShort(double amount) {
|
|
final abs = amount.abs();
|
|
if (abs >= 1000000) return '${(abs / 1000000).toStringAsFixed(1)} M';
|
|
if (abs >= 1000) return '${(abs / 1000).toStringAsFixed(1)} k';
|
|
return '${abs.toStringAsFixed(2)} €';
|
|
}
|