360 lines
12 KiB
Dart
360 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
@immutable
|
|
class JzPalette extends ThemeExtension<JzPalette> {
|
|
final Color text;
|
|
final Color text2;
|
|
final Color text3;
|
|
final Color background;
|
|
final Color card;
|
|
final Color line;
|
|
final Color primaryBackground;
|
|
final Color aiBackground;
|
|
final Color expenseBackground;
|
|
final Color warningBackground;
|
|
|
|
const JzPalette({
|
|
required this.text,
|
|
required this.text2,
|
|
required this.text3,
|
|
required this.background,
|
|
required this.card,
|
|
required this.line,
|
|
required this.primaryBackground,
|
|
required this.aiBackground,
|
|
required this.expenseBackground,
|
|
required this.warningBackground,
|
|
});
|
|
|
|
@override
|
|
JzPalette copyWith({
|
|
Color? text,
|
|
Color? text2,
|
|
Color? text3,
|
|
Color? background,
|
|
Color? card,
|
|
Color? line,
|
|
Color? primaryBackground,
|
|
Color? aiBackground,
|
|
Color? expenseBackground,
|
|
Color? warningBackground,
|
|
}) => JzPalette(
|
|
text: text ?? this.text,
|
|
text2: text2 ?? this.text2,
|
|
text3: text3 ?? this.text3,
|
|
background: background ?? this.background,
|
|
card: card ?? this.card,
|
|
line: line ?? this.line,
|
|
primaryBackground: primaryBackground ?? this.primaryBackground,
|
|
aiBackground: aiBackground ?? this.aiBackground,
|
|
expenseBackground: expenseBackground ?? this.expenseBackground,
|
|
warningBackground: warningBackground ?? this.warningBackground,
|
|
);
|
|
|
|
@override
|
|
JzPalette lerp(covariant JzPalette? other, double t) {
|
|
if (other == null) return this;
|
|
return JzPalette(
|
|
text: Color.lerp(text, other.text, t)!,
|
|
text2: Color.lerp(text2, other.text2, t)!,
|
|
text3: Color.lerp(text3, other.text3, t)!,
|
|
background: Color.lerp(background, other.background, t)!,
|
|
card: Color.lerp(card, other.card, t)!,
|
|
line: Color.lerp(line, other.line, t)!,
|
|
primaryBackground: Color.lerp(
|
|
primaryBackground,
|
|
other.primaryBackground,
|
|
t,
|
|
)!,
|
|
aiBackground: Color.lerp(aiBackground, other.aiBackground, t)!,
|
|
expenseBackground: Color.lerp(
|
|
expenseBackground,
|
|
other.expenseBackground,
|
|
t,
|
|
)!,
|
|
warningBackground: Color.lerp(
|
|
warningBackground,
|
|
other.warningBackground,
|
|
t,
|
|
)!,
|
|
);
|
|
}
|
|
}
|
|
|
|
extension JzThemeContext on BuildContext {
|
|
JzPalette get jz => Theme.of(this).extension<JzPalette>()!;
|
|
}
|
|
|
|
class AppTheme {
|
|
AppTheme._();
|
|
|
|
static const Color primary = Color(0xFF00B386);
|
|
static const Color primaryDeep = Color(0xFF009973);
|
|
static const Color primaryBg = Color(0xFFE6F7F1);
|
|
static const Color primaryMid = Color(0xFF4DA6FF);
|
|
static const Color primaryLight = Color(0xFF80DCC2);
|
|
static const Color ai = Color(0xFF5B6BF5);
|
|
static const Color aiBg = Color(0xFFEEF0FE);
|
|
static const Color red = Color(0xFFF0642D);
|
|
static const Color redBg = Color(0xFFFFF3EC);
|
|
static const Color orange = Color(0xFFF5A623);
|
|
static const Color orangeBg = Color(0xFFFFF7E8);
|
|
static const Color text = Color(0xFF191F26);
|
|
static const Color text2 = Color(0xFF5E6772);
|
|
static const Color text3 = Color(0xFF9AA3AD);
|
|
static const Color bg = Color(0xFFF6F7F9);
|
|
static const Color card = Colors.white;
|
|
static const Color line = Color(0xFFEEF0F3);
|
|
|
|
static const _lightPalette = JzPalette(
|
|
text: text,
|
|
text2: text2,
|
|
text3: text3,
|
|
background: bg,
|
|
card: card,
|
|
line: line,
|
|
primaryBackground: primaryBg,
|
|
aiBackground: aiBg,
|
|
expenseBackground: redBg,
|
|
warningBackground: orangeBg,
|
|
);
|
|
|
|
static const _darkPalette = JzPalette(
|
|
text: Color(0xFFE8EFED),
|
|
text2: Color(0xFFAAB6B2),
|
|
text3: Color(0xFF74817D),
|
|
background: Color(0xFF111615),
|
|
card: Color(0xFF1B2220),
|
|
line: Color(0xFF2B3532),
|
|
primaryBackground: Color(0xFF143A31),
|
|
aiBackground: Color(0xFF272B4A),
|
|
expenseBackground: Color(0xFF40251F),
|
|
warningBackground: Color(0xFF3B2E1B),
|
|
);
|
|
|
|
static ThemeData get light => _build(Brightness.light, _lightPalette);
|
|
static ThemeData get dark => _build(Brightness.dark, _darkPalette);
|
|
|
|
static ThemeData _build(Brightness brightness, JzPalette p) {
|
|
final dark = brightness == Brightness.dark;
|
|
final scheme = dark
|
|
? ColorScheme.dark(
|
|
primary: primary,
|
|
secondary: ai,
|
|
surface: p.card,
|
|
error: red,
|
|
onPrimary: Colors.white,
|
|
onSecondary: Colors.white,
|
|
onSurface: p.text,
|
|
primaryContainer: p.primaryBackground,
|
|
onPrimaryContainer: const Color(0xFF9BE7D1),
|
|
secondaryContainer: p.aiBackground,
|
|
onSecondaryContainer: const Color(0xFFC5CAFF),
|
|
outline: p.line,
|
|
outlineVariant: p.line,
|
|
)
|
|
: ColorScheme.light(
|
|
primary: primary,
|
|
secondary: ai,
|
|
surface: p.card,
|
|
error: red,
|
|
onPrimary: Colors.white,
|
|
onSecondary: Colors.white,
|
|
onSurface: p.text,
|
|
primaryContainer: p.primaryBackground,
|
|
onPrimaryContainer: primaryDeep,
|
|
secondaryContainer: p.aiBackground,
|
|
onSecondaryContainer: ai,
|
|
outline: p.line,
|
|
outlineVariant: p.line,
|
|
);
|
|
final baseText = ThemeData(
|
|
brightness: brightness,
|
|
).textTheme.apply(bodyColor: p.text, displayColor: p.text);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
brightness: brightness,
|
|
colorScheme: scheme,
|
|
scaffoldBackgroundColor: p.background,
|
|
fontFamily: 'PingFang SC',
|
|
textTheme: baseText,
|
|
extensions: [p],
|
|
dividerColor: p.line,
|
|
disabledColor: p.text3,
|
|
appBarTheme: AppBarTheme(
|
|
backgroundColor: Colors.transparent,
|
|
foregroundColor: p.text,
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
titleTextStyle: TextStyle(
|
|
color: p.text,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
systemOverlayStyle: dark
|
|
? SystemUiOverlayStyle.light
|
|
: SystemUiOverlayStyle.dark,
|
|
),
|
|
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
|
backgroundColor: p.card,
|
|
selectedItemColor: primary,
|
|
unselectedItemColor: p.text3,
|
|
type: BottomNavigationBarType.fixed,
|
|
elevation: 0,
|
|
),
|
|
cardTheme: CardThemeData(
|
|
color: p.card,
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
side: BorderSide(color: p.line, width: 0.5),
|
|
),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: p.card,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
borderSide: BorderSide(color: p.line),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
borderSide: BorderSide(color: p.line),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
borderSide: const BorderSide(color: primary, width: 1.4),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
borderSide: const BorderSide(color: red),
|
|
),
|
|
hintStyle: TextStyle(color: p.text3, fontSize: 13.5),
|
|
labelStyle: TextStyle(color: p.text2, fontSize: 13),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 14,
|
|
vertical: 13,
|
|
),
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: primary,
|
|
foregroundColor: Colors.white,
|
|
disabledBackgroundColor: p.line,
|
|
disabledForegroundColor: p.text3,
|
|
elevation: 0,
|
|
minimumSize: const Size(0, 46),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
filledButtonTheme: FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
backgroundColor: primary,
|
|
foregroundColor: Colors.white,
|
|
disabledBackgroundColor: p.line,
|
|
disabledForegroundColor: p.text3,
|
|
elevation: 0,
|
|
minimumSize: const Size(0, 46),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: p.text,
|
|
side: BorderSide(color: p.line),
|
|
minimumSize: const Size(0, 46),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: dark ? const Color(0xFF77D7BD) : primaryDeep,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
textStyle: const TextStyle(
|
|
fontSize: 13.5,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
iconButtonTheme: IconButtonThemeData(
|
|
style: IconButton.styleFrom(
|
|
foregroundColor: p.text2,
|
|
highlightColor: p.primaryBackground,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
checkboxTheme: CheckboxThemeData(
|
|
fillColor: WidgetStateProperty.resolveWith(
|
|
(states) => states.contains(WidgetState.selected) ? primary : p.card,
|
|
),
|
|
side: BorderSide(color: p.text3),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
|
|
),
|
|
switchTheme: SwitchThemeData(
|
|
trackColor: WidgetStateProperty.resolveWith(
|
|
(states) => states.contains(WidgetState.selected)
|
|
? p.primaryBackground
|
|
: p.line,
|
|
),
|
|
thumbColor: WidgetStateProperty.resolveWith(
|
|
(states) => states.contains(WidgetState.selected) ? primary : p.text3,
|
|
),
|
|
trackOutlineColor: const WidgetStatePropertyAll(Colors.transparent),
|
|
),
|
|
dialogTheme: DialogThemeData(
|
|
backgroundColor: p.card,
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)),
|
|
titleTextStyle: TextStyle(
|
|
color: p.text,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
contentTextStyle: TextStyle(
|
|
color: p.text2,
|
|
fontSize: 13.5,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
bottomSheetTheme: BottomSheetThemeData(
|
|
backgroundColor: p.card,
|
|
modalBackgroundColor: p.card,
|
|
surfaceTintColor: Colors.transparent,
|
|
showDragHandle: false,
|
|
),
|
|
snackBarTheme: SnackBarThemeData(
|
|
backgroundColor: dark ? const Color(0xFFE8EFED) : p.text,
|
|
contentTextStyle: TextStyle(
|
|
color: dark ? const Color(0xFF111615) : Colors.white,
|
|
fontSize: 13,
|
|
),
|
|
behavior: SnackBarBehavior.floating,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
),
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
backgroundColor: p.card,
|
|
indicatorColor: p.primaryBackground,
|
|
),
|
|
);
|
|
}
|
|
}
|