Troca de desing

This commit is contained in:
Carlos Correia
2026-05-18 15:04:07 +01:00
parent 9b4c2f7e04
commit 9999011cfd
9 changed files with 3000 additions and 2138 deletions

369
lib/theme/app_theme.dart Normal file
View 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),
),
),
);
}
}