Troca de desing
This commit is contained in:
369
lib/theme/app_theme.dart
Normal file
369
lib/theme/app_theme.dart
Normal file
@@ -0,0 +1,369 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Design tokens for DayMaker — a single source of truth for the new look.
|
||||
class AppColors {
|
||||
// Brand
|
||||
static const Color primary = Color(0xFF2563EB); // refined modern blue
|
||||
static const Color primaryDark = Color(0xFF1D4ED8);
|
||||
static const Color primaryLight = Color(0xFF60A5FA);
|
||||
static const Color accent = Color(0xFFFB923C); // warm orange accent (legacy peach reborn)
|
||||
|
||||
// Neutrals
|
||||
static const Color background = Color(0xFFF7F8FB);
|
||||
static const Color surface = Colors.white;
|
||||
static const Color surfaceAlt = Color(0xFFF1F5F9);
|
||||
static const Color border = Color(0xFFE2E8F0);
|
||||
static const Color divider = Color(0xFFEDF2F7);
|
||||
|
||||
// Text
|
||||
static const Color textPrimary = Color(0xFF0F172A);
|
||||
static const Color textSecondary = Color(0xFF64748B);
|
||||
static const Color textTertiary = Color(0xFF94A3B8);
|
||||
static const Color textOnPrimary = Colors.white;
|
||||
|
||||
// Status
|
||||
static const Color success = Color(0xFF10B981);
|
||||
static const Color error = Color(0xFFEF4444);
|
||||
static const Color warning = Color(0xFFF59E0B);
|
||||
|
||||
// Gradients
|
||||
static const LinearGradient brandGradient = LinearGradient(
|
||||
colors: [Color(0xFF2563EB), Color(0xFF60A5FA)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
|
||||
static const LinearGradient warmGradient = LinearGradient(
|
||||
colors: [Color(0xFFFB923C), Color(0xFFF472B6)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
}
|
||||
|
||||
class AppRadius {
|
||||
static const double sm = 8;
|
||||
static const double md = 12;
|
||||
static const double lg = 16;
|
||||
static const double xl = 20;
|
||||
static const double pill = 999;
|
||||
}
|
||||
|
||||
class AppSpacing {
|
||||
static const double xs = 4;
|
||||
static const double sm = 8;
|
||||
static const double md = 12;
|
||||
static const double lg = 16;
|
||||
static const double xl = 20;
|
||||
static const double xxl = 24;
|
||||
static const double huge = 32;
|
||||
}
|
||||
|
||||
class AppShadows {
|
||||
static List<BoxShadow> soft = [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> medium = [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
|
||||
static List<BoxShadow> brand = [
|
||||
BoxShadow(
|
||||
color: AppColors.primary.withValues(alpha: 0.25),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class AppText {
|
||||
static const TextStyle h1 = TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.2,
|
||||
);
|
||||
|
||||
static const TextStyle h2 = TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.25,
|
||||
);
|
||||
|
||||
static const TextStyle h3 = TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.3,
|
||||
);
|
||||
|
||||
static const TextStyle body = TextStyle(
|
||||
fontSize: 15,
|
||||
color: AppColors.textPrimary,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static const TextStyle bodySecondary = TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.textSecondary,
|
||||
height: 1.4,
|
||||
);
|
||||
|
||||
static const TextStyle caption = TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textTertiary,
|
||||
height: 1.3,
|
||||
);
|
||||
|
||||
static const TextStyle button = TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.2,
|
||||
);
|
||||
|
||||
static const TextStyle label = TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
letterSpacing: 0.3,
|
||||
);
|
||||
}
|
||||
|
||||
/// Shared decorations for cards and surfaces.
|
||||
class AppDecorations {
|
||||
static BoxDecoration card({Color? color, double radius = AppRadius.lg}) =>
|
||||
BoxDecoration(
|
||||
color: color ?? AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
boxShadow: AppShadows.soft,
|
||||
);
|
||||
|
||||
static BoxDecoration outlined({double radius = AppRadius.lg}) =>
|
||||
BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
border: Border.all(color: AppColors.border),
|
||||
);
|
||||
|
||||
static BoxDecoration filled({Color? color, double radius = AppRadius.md}) =>
|
||||
BoxDecoration(
|
||||
color: color ?? AppColors.surfaceAlt,
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
);
|
||||
}
|
||||
|
||||
/// Reusable widgets.
|
||||
class AppButton extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData? icon;
|
||||
final VoidCallback? onPressed;
|
||||
final bool loading;
|
||||
final bool secondary;
|
||||
final bool danger;
|
||||
final double height;
|
||||
|
||||
const AppButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.icon,
|
||||
this.onPressed,
|
||||
this.loading = false,
|
||||
this.secondary = false,
|
||||
this.danger = false,
|
||||
this.height = 54,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final disabled = onPressed == null || loading;
|
||||
final bg = danger
|
||||
? AppColors.error
|
||||
: secondary
|
||||
? AppColors.surface
|
||||
: AppColors.primary;
|
||||
final fg = secondary ? AppColors.primary : Colors.white;
|
||||
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: height,
|
||||
child: AnimatedOpacity(
|
||||
opacity: disabled && !loading ? 0.5 : 1,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Material(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
elevation: secondary ? 0 : 0,
|
||||
child: InkWell(
|
||||
onTap: disabled ? null : onPressed,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: secondary
|
||||
? Border.all(color: AppColors.primary, width: 1.5)
|
||||
: null,
|
||||
boxShadow: secondary || danger ? null : AppShadows.brand,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: loading
|
||||
? SizedBox(
|
||||
height: 22,
|
||||
width: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.4,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(fg),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, color: fg, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: AppText.button.copyWith(color: fg),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pill-style chip for filters/tags. Animated selection.
|
||||
class AppChip extends StatelessWidget {
|
||||
final String label;
|
||||
final IconData? icon;
|
||||
final bool selected;
|
||||
final VoidCallback? onTap;
|
||||
final Color? color;
|
||||
|
||||
const AppChip({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.icon,
|
||||
this.selected = false,
|
||||
this.onTap,
|
||||
this.color,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final accent = color ?? AppColors.primary;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
curve: Curves.easeOut,
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? accent : AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.pill),
|
||||
border: Border.all(
|
||||
color: selected ? accent : AppColors.border,
|
||||
width: 1.2,
|
||||
),
|
||||
boxShadow: selected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: accent.withValues(alpha: 0.25),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(AppRadius.pill),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: selected ? Colors.white : accent,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: selected ? Colors.white : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helpers for snackbars
|
||||
class AppSnack {
|
||||
static void error(BuildContext context, String message) {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Colors.white),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.error,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void success(BuildContext context, String message) {
|
||||
ScaffoldMessenger.of(context)
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const Icon(Icons.check_circle_outline, color: Colors.white),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(message)),
|
||||
],
|
||||
),
|
||||
backgroundColor: AppColors.success,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user