Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Background avec motifs géométriques africains subtils
class AfricanPatternBackground extends StatelessWidget {
final Widget child;
final Color? patternColor;
final double opacity;
const AfricanPatternBackground({
super.key,
required this.child,
this.patternColor,
this.opacity = 0.03,
});
@override
Widget build(BuildContext context) {
return Stack(
children: [
// Background avec motifs
Positioned.fill(
child: IgnorePointer(
child: CustomPaint(
painter: AfricanPatternPainter(
color: (patternColor ?? UnionFlowColors.unionGreen).withOpacity(opacity),
),
),
),
),
// Contenu
child,
],
);
}
}
/// Painter pour dessiner les motifs africains
class AfricanPatternPainter extends CustomPainter {
final Color color;
AfricanPatternPainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2;
final fillPaint = Paint()
..color = color
..style = PaintingStyle.fill;
// Espacement entre les motifs
const double spacing = 80.0;
const double patternSize = 40.0;
// Dessiner la grille de motifs
for (double y = 0; y < size.height + spacing; y += spacing) {
for (double x = 0; x < size.width + spacing; x += spacing) {
final offset = Offset(x, y);
// Alterner entre différents motifs
final patternType = ((x ~/ spacing) + (y ~/ spacing)) % 3;
switch (patternType) {
case 0:
_drawDiamondPattern(canvas, offset, patternSize, paint);
break;
case 1:
_drawTrianglePattern(canvas, offset, patternSize, fillPaint);
break;
case 2:
_drawCirclePattern(canvas, offset, patternSize, paint);
break;
}
}
}
}
void _drawDiamondPattern(Canvas canvas, Offset offset, double size, Paint paint) {
final path = Path()
..moveTo(offset.dx, offset.dy - size / 2)
..lineTo(offset.dx + size / 2, offset.dy)
..lineTo(offset.dx, offset.dy + size / 2)
..lineTo(offset.dx - size / 2, offset.dy)
..close();
canvas.drawPath(path, paint);
}
void _drawTrianglePattern(Canvas canvas, Offset offset, double size, Paint paint) {
final path = Path()
..moveTo(offset.dx, offset.dy - size / 3)
..lineTo(offset.dx + size / 3, offset.dy + size / 3)
..lineTo(offset.dx - size / 3, offset.dy + size / 3)
..close();
canvas.drawPath(path, paint);
}
void _drawCirclePattern(Canvas canvas, Offset offset, double size, Paint paint) {
canvas.drawCircle(offset, size / 4, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
/// Widget avec animation de fade-in automatique
class AnimatedFadeIn extends StatefulWidget {
final Widget child;
final Duration duration;
final Duration delay;
final Curve curve;
const AnimatedFadeIn({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 600),
this.delay = Duration.zero,
this.curve = Curves.easeOut,
});
@override
State<AnimatedFadeIn> createState() => _AnimatedFadeInState();
}
class _AnimatedFadeInState extends State<AnimatedFadeIn>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: widget.curve,
);
Future.delayed(widget.delay, () {
if (mounted) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}

View File

@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
/// Widget avec animation de slide-in automatique
class AnimatedSlideIn extends StatefulWidget {
final Widget child;
final Duration duration;
final Duration delay;
final Offset begin;
final Curve curve;
const AnimatedSlideIn({
super.key,
required this.child,
this.duration = const Duration(milliseconds: 600),
this.delay = Duration.zero,
this.begin = const Offset(0, 0.3),
this.curve = Curves.easeOut,
});
@override
State<AnimatedSlideIn> createState() => _AnimatedSlideInState();
}
class _AnimatedSlideInState extends State<AnimatedSlideIn>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _slideAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
);
_slideAnimation = Tween<Offset>(
begin: widget.begin,
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: widget.curve,
));
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: widget.curve,
);
Future.delayed(widget.delay, () {
if (mounted) {
_controller.forward();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: widget.child,
),
);
}
}

View File

@@ -0,0 +1,109 @@
/// UnionFlow Primary Button - Bouton principal
///
/// Bouton primaire avec la couleur Bleu Roi (#4169E1)
/// Utilisé pour les actions principales (connexion, enregistrer, valider, etc.)
library uf_primary_button;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Bouton primaire UnionFlow
///
/// Usage:
/// ```dart
/// UFPrimaryButton(
/// label: 'Connexion',
/// onPressed: () => login(),
/// icon: Icons.login,
/// isLoading: false,
/// )
/// ```
class UFPrimaryButton extends StatelessWidget {
/// Texte du bouton
final String label;
/// Callback appelé lors du clic
final VoidCallback? onPressed;
/// Indique si le bouton est en chargement
final bool isLoading;
/// Icône optionnelle à gauche du texte
final IconData? icon;
/// Bouton pleine largeur
final bool isFullWidth;
/// Hauteur personnalisée (optionnel)
final double? height;
/// Couleur de fond personnalisée (optionnel)
final Color? backgroundColor;
/// Couleur du texte/icône personnalisée (optionnel)
final Color? textColor;
const UFPrimaryButton({
super.key,
required this.label,
this.onPressed,
this.isLoading = false,
this.icon,
this.isFullWidth = false,
this.height,
this.backgroundColor,
this.textColor,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: isFullWidth ? double.infinity : null,
height: height ?? SpacingTokens.buttonHeightLarge,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor ?? AppColors.primaryGreen,
foregroundColor: textColor ?? Colors.white,
disabledBackgroundColor: (backgroundColor ?? AppColors.primaryGreen).withOpacity(0.5),
disabledForegroundColor: (textColor ?? Colors.white).withOpacity(0.7),
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
),
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, size: 20),
const SizedBox(width: SpacingTokens.md),
],
Text(
label,
style: AppTypography.actionText,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,80 @@
/// UnionFlow Secondary Button - Bouton secondaire
///
/// Bouton secondaire avec la couleur Indigo (#6366F1)
/// Utilisé pour les actions secondaires (annuler, retour, etc.)
library uf_secondary_button;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Bouton secondaire UnionFlow
class UFSecondaryButton extends StatelessWidget {
final String label;
final VoidCallback? onPressed;
final bool isLoading;
final IconData? icon;
final bool isFullWidth;
final double? height;
const UFSecondaryButton({
super.key,
required this.label,
this.onPressed,
this.isLoading = false,
this.icon,
this.isFullWidth = false,
this.height,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: isFullWidth ? double.infinity : null,
height: height ?? SpacingTokens.buttonHeightLarge,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.brandGreen,
foregroundColor: Colors.white,
disabledBackgroundColor: AppColors.brandGreen.withOpacity(0.5),
disabledForegroundColor: Colors.white.withOpacity(0.7),
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
),
child: isLoading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(icon, size: 20),
const SizedBox(width: SpacingTokens.md),
],
Text(
label,
style: AppTypography.actionText,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Card standardisé UnionFlow
///
/// Composant Card unifié avec 3 styles prédéfinis :
/// - elevated : Card avec ombre (par défaut)
/// - outlined : Card avec bordure
/// - filled : Card avec fond coloré
///
/// Usage:
/// ```dart
/// UFCard(
/// child: Text('Contenu'),
/// )
///
/// UFCard.outlined(
/// child: Text('Contenu'),
/// )
///
/// UFCard.filled(
/// color: ColorTokens.primary,
/// child: Text('Contenu'),
/// )
/// ```
class UFCard extends StatelessWidget {
final Widget child;
final EdgeInsets? padding;
final EdgeInsets? margin;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final UFCardStyle style;
final Color? color;
final Color? borderColor;
final double? borderWidth;
final double? elevation;
final double? borderRadius;
/// Card avec ombre (style par défaut)
const UFCard({
super.key,
required this.child,
this.padding,
this.margin,
this.onTap,
this.onLongPress,
this.color,
this.elevation,
this.borderRadius,
}) : style = UFCardStyle.elevated,
borderColor = null,
borderWidth = null;
/// Card avec bordure
const UFCard.outlined({
super.key,
required this.child,
this.padding,
this.margin,
this.onTap,
this.onLongPress,
this.color,
this.borderColor,
this.borderWidth,
this.borderRadius,
}) : style = UFCardStyle.outlined,
elevation = null;
/// Card avec fond coloré
const UFCard.filled({
super.key,
required this.child,
this.padding,
this.margin,
this.onTap,
this.onLongPress,
required this.color,
this.borderRadius,
}) : style = UFCardStyle.filled,
borderColor = null,
borderWidth = null,
elevation = null;
@override
Widget build(BuildContext context) {
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.cardPadding);
final effectiveMargin = margin ?? EdgeInsets.zero;
final effectiveBorderRadius = borderRadius ?? SpacingTokens.radiusLg;
Widget content = Container(
padding: effectivePadding,
decoration: _getDecoration(effectiveBorderRadius),
child: child,
);
if (onTap != null || onLongPress != null) {
content = InkWell(
onTap: onTap,
onLongPress: onLongPress,
borderRadius: BorderRadius.circular(effectiveBorderRadius),
child: content,
);
}
return Container(
margin: effectiveMargin,
child: content,
);
}
BoxDecoration _getDecoration(double radius) {
switch (style) {
case UFCardStyle.elevated:
return BoxDecoration(
color: color ?? AppColors.lightSurface,
borderRadius: BorderRadius.circular(radius),
boxShadow: elevation != null
? [
BoxShadow(
color: AppColors.darkBorder.withOpacity(0.1),
blurRadius: elevation!,
offset: const Offset(0, 2),
),
]
: ShadowTokens.sm,
);
case UFCardStyle.outlined:
return BoxDecoration(
color: color ?? AppColors.lightSurface,
borderRadius: BorderRadius.circular(radius),
border: Border.all(
color: borderColor ?? AppColors.lightBorder,
width: borderWidth ?? 1.0,
),
);
case UFCardStyle.filled:
return BoxDecoration(
color: color ?? AppColors.lightSurface,
borderRadius: BorderRadius.circular(radius),
);
}
}
}
/// Styles de Card disponibles
enum UFCardStyle {
/// Card avec ombre
elevated,
/// Card avec bordure
outlined,
/// Card avec fond coloré
filled,
}

View File

@@ -0,0 +1,89 @@
/// UnionFlow Info Card - Card d'information générique
///
/// Card blanche avec titre, icône et contenu personnalisable
library uf_info_card;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Card d'information générique
///
/// Usage:
/// ```dart
/// UFInfoCard(
/// title: 'État du système',
/// icon: Icons.health_and_safety,
/// iconColor: ColorTokens.primary,
/// trailing: Container(...), // Badge ou autre widget
/// child: Column(...), // Contenu de la card
/// )
/// ```
class UFInfoCard extends StatelessWidget {
/// Titre de la card
final String title;
/// Icône du titre
final IconData icon;
/// Couleur de l'icône (par défaut: primary)
final Color? iconColor;
/// Widget à droite du titre (badge, bouton, etc.)
final Widget? trailing;
/// Contenu de la card
final Widget child;
/// Padding de la card (par défaut: xl)
final EdgeInsets? padding;
const UFInfoCard({
super.key,
required this.title,
required this.icon,
this.iconColor,
this.trailing,
required this.child,
this.padding,
});
@override
Widget build(BuildContext context) {
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
final effectivePadding = padding ?? const EdgeInsets.all(SpacingTokens.xl);
return Container(
padding: effectivePadding,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
boxShadow: ShadowTokens.sm,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header avec titre et trailing
Row(
children: [
Icon(icon, color: effectiveIconColor, size: 20),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
),
),
),
if (trailing != null) trailing!,
],
),
const SizedBox(height: SpacingTokens.xl),
// Contenu
child,
],
),
);
}
}

View File

@@ -0,0 +1,75 @@
/// UnionFlow Metric Card - Card de métrique système
///
/// Card compacte pour afficher une métrique système (CPU, RAM, etc.)
library uf_metric_card;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Card de métrique système
///
/// Usage:
/// ```dart
/// UFMetricCard(
/// label: 'CPU',
/// value: '23.5%',
/// icon: Icons.memory,
/// color: ColorTokens.success,
/// )
/// ```
class UFMetricCard extends StatelessWidget {
/// Label de la métrique (ex: "CPU")
final String label;
/// Valeur de la métrique (ex: "23.5%")
final String value;
/// Icône représentant la métrique
final IconData icon;
/// Couleur de la métrique (optionnel)
final Color? color;
const UFMetricCard({
super.key,
required this.label,
required this.value,
required this.icon,
this.color,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
child: Column(
children: [
Icon(icon, color: Colors.white, size: 16),
const SizedBox(height: SpacingTokens.sm),
Text(
value,
style: AppTypography.badgeText.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
Text(
label,
style: AppTypography.badgeText.copyWith(
fontSize: 9,
color: Colors.white.withOpacity(0.8),
),
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@@ -0,0 +1,141 @@
/// UnionFlow Stat Card - Card de statistiques
///
/// Card affichant une statistique avec icône, titre, valeur et sous-titre optionnel
/// Utilisé dans le dashboard pour afficher les métriques clés
library uf_stat_card;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Card de statistiques UnionFlow
///
/// Usage:
/// ```dart
/// UFStatCard(
/// title: 'Membres',
/// value: '142',
/// icon: Icons.people,
/// iconColor: ColorTokens.primary,
/// subtitle: '+5 ce mois',
/// onTap: () => navigateToMembers(),
/// )
/// ```
class UFStatCard extends StatelessWidget {
/// Titre de la statistique (ex: "Membres")
final String title;
/// Valeur de la statistique (ex: "142")
final String value;
/// Icône représentant la statistique
final IconData icon;
/// Couleur de l'icône (par défaut: primary)
final Color? iconColor;
/// Sous-titre optionnel (ex: "+5 ce mois")
final String? subtitle;
/// Callback appelé lors du clic sur la card
final VoidCallback? onTap;
/// Couleur de fond de l'icône (par défaut: iconColor avec opacité)
final Color? iconBackgroundColor;
const UFStatCard({
super.key,
required this.title,
required this.value,
required this.icon,
this.iconColor,
this.subtitle,
this.onTap,
this.iconBackgroundColor,
});
@override
Widget build(BuildContext context) {
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
final effectiveIconBgColor = iconBackgroundColor ??
effectiveIconColor.withOpacity(0.1);
return Card(
elevation: SpacingTokens.elevationSm,
shadowColor: AppColors.darkBorder.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.cardPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
// Header avec icône et flèche
Row(
children: [
// Icône avec background coloré
Container(
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
color: effectiveIconBgColor,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Icon(
icon,
color: effectiveIconColor,
size: 24,
),
),
const Spacer(),
// Flèche si cliquable
if (onTap != null)
const Icon(
Icons.arrow_forward_ios,
size: 16,
color: AppColors.textSecondaryLight,
),
],
),
const SizedBox(height: SpacingTokens.lg),
// Titre
Text(
title,
style: AppTypography.badgeText.copyWith(
color: AppColors.textSecondaryLight,
),
),
const SizedBox(height: SpacingTokens.sm),
// Valeur
Text(
value,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
),
),
// Sous-titre optionnel
if (subtitle != null) ...[
const SizedBox(height: SpacingTokens.sm),
Text(
subtitle!,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
),
),
],
],
),
),
),
);
}
}

View File

@@ -0,0 +1,43 @@
/// UnionFlow Components - Export centralisé
///
/// Ce fichier exporte tous les composants réutilisables du Design System
library components;
// ═══════════════════════════════════════════════════════════════════════════
// BOUTONS
// ═══════════════════════════════════════════════════════════════════════════
export 'buttons/uf_primary_button.dart';
export 'buttons/uf_secondary_button.dart';
// ═══════════════════════════════════════════════════════════════════════════
// CARDS & CONTAINERS
// ═══════════════════════════════════════════════════════════════════════════
export 'cards/uf_card.dart';
export 'cards/uf_stat_card.dart';
export 'cards/uf_info_card.dart';
export 'cards/uf_metric_card.dart';
export 'uf_container.dart';
// ═══════════════════════════════════════════════════════════════════════════
// INPUTS
// ═══════════════════════════════════════════════════════════════════════════
export 'inputs/uf_switch_tile.dart';
export 'inputs/uf_dropdown_tile.dart';
// ═══════════════════════════════════════════════════════════════════════════
// HEADERS & APPBAR
// ═══════════════════════════════════════════════════════════════════════════
export 'uf_header.dart';
export 'uf_page_header.dart';
export 'uf_app_bar.dart';
// Composants supplémentaires à exporter quand créés :
// export 'buttons/uf_outline_button.dart';
// export 'buttons/uf_text_button.dart';
// export 'cards/uf_event_card.dart';
// export 'inputs/uf_text_field.dart';

View File

@@ -0,0 +1,97 @@
/// UnionFlow Dropdown Tile - Ligne de paramètre avec dropdown
///
/// Tile avec titre et dropdown pour les paramètres
library uf_dropdown_tile;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Tile de paramètre avec dropdown
///
/// Usage:
/// ```dart
/// UFDropdownTile<String>(
/// title: 'Niveau de log',
/// value: 'INFO',
/// items: ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'],
/// onChanged: (value) => setState(() => _logLevel = value),
/// )
/// ```
class UFDropdownTile<T> extends StatelessWidget {
/// Titre du paramètre
final String title;
/// Valeur actuelle
final T value;
/// Liste des options
final List<T> items;
/// Callback appelé lors du changement
final ValueChanged<T?>? onChanged;
/// Couleur de fond (par défaut: surfaceVariant)
final Color? backgroundColor;
/// Fonction pour afficher le texte d'un item (par défaut: toString())
final String Function(T)? itemBuilder;
const UFDropdownTile({
super.key,
required this.title,
required this.value,
required this.items,
this.onChanged,
this.backgroundColor,
this.itemBuilder,
});
@override
Widget build(BuildContext context) {
final effectiveBgColor = backgroundColor ?? AppColors.lightSurface;
final effectiveItemBuilder = itemBuilder ?? (item) => item.toString();
return Container(
margin: const EdgeInsets.only(bottom: SpacingTokens.lg),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: effectiveBgColor,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
child: Row(
children: [
Expanded(
child: Text(
title,
style: AppTypography.bodyTextSmall.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(color: AppColors.lightBorder),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: value,
onChanged: onChanged,
items: items.map((item) {
return DropdownMenuItem<T>(
value: item,
child: Text(effectiveItemBuilder(item)),
);
}).toList(),
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,88 @@
/// UnionFlow Switch Tile - Ligne de paramètre avec switch
///
/// Tile avec titre, description et switch pour les paramètres
library uf_switch_tile;
import 'package:flutter/material.dart';
import '../../unionflow_design_system.dart';
/// Tile de paramètre avec switch
///
/// Usage:
/// ```dart
/// UFSwitchTile(
/// title: 'Notifications',
/// subtitle: 'Activer les notifications push',
/// value: true,
/// onChanged: (value) => setState(() => _notifications = value),
/// )
/// ```
class UFSwitchTile extends StatelessWidget {
/// Titre du paramètre
final String title;
/// Description du paramètre
final String subtitle;
/// Valeur actuelle du switch
final bool value;
/// Callback appelé lors du changement
final ValueChanged<bool>? onChanged;
/// Couleur de fond (par défaut: surfaceVariant)
final Color? backgroundColor;
const UFSwitchTile({
super.key,
required this.title,
required this.subtitle,
required this.value,
this.onChanged,
this.backgroundColor,
});
@override
Widget build(BuildContext context) {
final effectiveBgColor = backgroundColor ?? AppColors.lightSurface;
return Container(
margin: const EdgeInsets.only(bottom: SpacingTokens.lg),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: effectiveBgColor,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTypography.bodyTextSmall.copyWith(
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
),
),
Text(
subtitle,
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight,
),
),
],
),
),
Switch(
value: value,
onChanged: onChanged,
activeColor: AppColors.primaryGreen,
),
],
),
);
}
}

View File

@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../unionflow_design_system.dart';
/// AppBar standardisé UnionFlow
///
/// Composant AppBar unifié pour toutes les pages de détail/formulaire.
/// Garantit la cohérence visuelle et l'expérience utilisateur.
///
/// Si [mergeLeadingWithTitle] est true et que la route peut être quittée,
/// le bouton retour et le titre sont fusionnés en une seule ligne (retour
/// toujours visible avec la même couleur que le titre).
class UFAppBar extends StatelessWidget implements PreferredSizeWidget {
final String title;
final List<Widget>? actions;
final Widget? leading;
final bool automaticallyImplyLeading;
/// Fusionne le bouton retour et le titre en une seule zone (retour visible, même couleur que le titre).
final bool mergeLeadingWithTitle;
final PreferredSizeWidget? bottom;
final Color? backgroundColor;
final Color? foregroundColor;
final double elevation;
const UFAppBar({
super.key,
required this.title,
this.actions,
this.leading,
this.automaticallyImplyLeading = true,
this.mergeLeadingWithTitle = false,
this.bottom,
this.backgroundColor,
this.foregroundColor,
this.elevation = 0,
});
@override
Widget build(BuildContext context) {
final canPop = ModalRoute.of(context)?.canPop ?? false;
final fg = foregroundColor ?? Colors.white;
final useMergedTitle = mergeLeadingWithTitle && canPop;
final isTransparent = backgroundColor == Colors.transparent ||
(backgroundColor != null && backgroundColor!.opacity < 0.1);
return AppBar(
title: useMergedTitle
? Row(
children: [
Material(
color: isTransparent ? Colors.black26 : Colors.transparent,
borderRadius: BorderRadius.circular(20),
child: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
color: fg,
tooltip: 'Retour',
style: IconButton.styleFrom(
foregroundColor: fg,
),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: fg,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
],
)
: Text(title),
backgroundColor: backgroundColor ?? AppColors.primaryGreen,
foregroundColor: fg,
elevation: elevation,
leading: useMergedTitle ? null : leading,
automaticallyImplyLeading: useMergedTitle ? false : automaticallyImplyLeading,
actions: actions,
bottom: bottom,
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
statusBarBrightness: Brightness.dark,
),
centerTitle: false,
titleTextStyle: AppTypography.headerSmall.copyWith(
color: fg,
fontWeight: FontWeight.w600,
),
);
}
@override
Size get preferredSize => Size.fromHeight(
kToolbarHeight + (bottom?.preferredSize.height ?? 0.0),
);
}

View File

@@ -0,0 +1,2 @@
export 'buttons/uf_primary_button.dart';
export 'buttons/uf_secondary_button.dart';

View File

@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import '../unionflow_design_system.dart';
/// Container standardisé UnionFlow
///
/// Composant Container unifié avec styles prédéfinis.
/// Garantit la cohérence des espacements, rayons et ombres.
///
/// Usage:
/// ```dart
/// UFContainer(
/// child: Text('Contenu'),
/// )
///
/// UFContainer.rounded(
/// color: ColorTokens.primary,
/// child: Text('Contenu'),
/// )
///
/// UFContainer.elevated(
/// child: Text('Contenu'),
/// )
/// ```
class UFContainer extends StatelessWidget {
final Widget child;
final Color? color;
final EdgeInsets? padding;
final EdgeInsets? margin;
final double? width;
final double? height;
final AlignmentGeometry? alignment;
final BoxConstraints? constraints;
final Gradient? gradient;
final double borderRadius;
final Border? border;
final List<BoxShadow>? boxShadow;
/// Container standard
const UFContainer({
super.key,
required this.child,
this.color,
this.padding,
this.margin,
this.width,
this.height,
this.alignment,
this.constraints,
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusMd;
/// Container avec coins arrondis
const UFContainer.rounded({
super.key,
required this.child,
this.color,
this.padding,
this.margin,
this.width,
this.height,
this.alignment,
this.constraints,
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusLg;
/// Container très arrondi
const UFContainer.extraRounded({
super.key,
required this.child,
this.color,
this.padding,
this.margin,
this.width,
this.height,
this.alignment,
this.constraints,
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusXl;
/// Container avec ombre
UFContainer.elevated({
super.key,
required this.child,
this.color,
this.padding,
this.margin,
this.width,
this.height,
this.alignment,
this.constraints,
this.gradient,
this.border,
}) : borderRadius = SpacingTokens.radiusLg,
boxShadow = ShadowTokens.sm;
/// Container circulaire
const UFContainer.circular({
super.key,
required this.child,
this.color,
this.padding,
this.margin,
this.width,
this.height,
this.alignment,
this.constraints,
this.gradient,
this.border,
this.boxShadow,
}) : borderRadius = SpacingTokens.radiusCircular;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
padding: padding,
margin: margin,
alignment: alignment,
constraints: constraints,
decoration: BoxDecoration(
color: gradient == null ? (color ?? AppColors.lightSurface) : null,
gradient: gradient,
borderRadius: BorderRadius.circular(borderRadius),
border: border,
boxShadow: boxShadow,
),
child: child,
);
}
}

View File

@@ -0,0 +1,127 @@
import 'package:flutter/material.dart';
import '../unionflow_design_system.dart';
/// Header harmonisé UnionFlow
///
/// Composant header standardisé pour toutes les pages de l'application.
/// Garantit la cohérence visuelle et l'expérience utilisateur.
class UFHeader extends StatelessWidget {
final String title;
final String? subtitle;
final IconData icon;
final List<Widget>? actions;
final VoidCallback? onNotificationTap;
final VoidCallback? onSettingsTap;
final bool showActions;
const UFHeader({
super.key,
required this.title,
this.subtitle,
required this.icon,
this.actions,
this.onNotificationTap,
this.onSettingsTap,
this.showActions = true,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(SpacingTokens.xl),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.primaryGreen, AppColors.brandGreenLight],
),
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
boxShadow: ShadowTokens.primary,
),
child: Row(
children: [
// Icône et contenu principal
Container(
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Icon(
icon,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: SpacingTokens.lg),
// Titre et sous-titre
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTypography.headerSmall.copyWith(
color: Colors.white,
),
),
if (subtitle != null) ...[
const SizedBox(height: SpacingTokens.xs),
Text(
subtitle!,
style: AppTypography.subtitleSmall.copyWith(
color: Colors.white.withOpacity(0.8),
),
),
],
],
),
),
// Actions
if (showActions) _buildActions(),
],
),
);
}
Widget _buildActions() {
if (actions != null) {
return Row(children: actions!);
}
return Row(
children: [
if (onNotificationTap != null)
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
),
child: IconButton(
onPressed: onNotificationTap,
icon: const Icon(
Icons.notifications_outlined,
color: Colors.white,
),
),
),
if (onNotificationTap != null && onSettingsTap != null)
const SizedBox(width: SpacingTokens.sm),
if (onSettingsTap != null)
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
),
child: IconButton(
onPressed: onSettingsTap,
icon: const Icon(
Icons.settings_outlined,
color: Colors.white,
),
),
),
],
);
}
}

View File

@@ -0,0 +1,237 @@
import 'package:flutter/material.dart';
import '../unionflow_design_system.dart';
/// Header de page compact et moderne
///
/// Composant header minimaliste pour les pages principales du BottomNavigationBar.
/// Design épuré sans gradient lourd, optimisé pour l'espace.
///
/// Usage:
/// ```dart
/// UFPageHeader(
/// title: 'Membres',
/// icon: Icons.people,
/// actions: [
/// IconButton(icon: Icon(Icons.add), onPressed: () => Navigator.pop(context)),
/// ],
/// )
/// ```
class UFPageHeader extends StatelessWidget {
final String title;
final IconData icon;
final List<Widget>? actions;
final Color? iconColor;
final bool showDivider;
const UFPageHeader({
super.key,
required this.title,
required this.icon,
this.actions,
this.iconColor,
this.showDivider = true,
});
@override
Widget build(BuildContext context) {
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.md,
),
child: Row(
children: [
// Icône
Container(
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: effectiveIconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Icon(
icon,
color: effectiveIconColor,
size: 20,
),
),
const SizedBox(width: SpacingTokens.md),
// Titre
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
fontWeight: FontWeight.w600,
),
),
),
// Actions
if (actions != null) ...actions!,
],
),
),
// Divider optionnel
if (showDivider)
const Divider(
height: 1,
thickness: 1,
color: AppColors.lightBorder,
),
],
);
}
}
/// Header de page avec statistiques
///
/// Header compact avec des métriques KPI intégrées.
class UFPageHeaderWithStats extends StatelessWidget {
final String title;
final IconData icon;
final List<UFHeaderStat> stats;
final List<Widget>? actions;
final Color? iconColor;
const UFPageHeaderWithStats({
super.key,
required this.title,
required this.icon,
required this.stats,
this.actions,
this.iconColor,
});
@override
Widget build(BuildContext context) {
final effectiveIconColor = iconColor ?? AppColors.primaryGreen;
return Column(
children: [
// Titre et actions
Padding(
padding: const EdgeInsets.fromLTRB(
SpacingTokens.lg,
SpacingTokens.md,
SpacingTokens.lg,
SpacingTokens.sm,
),
child: Row(
children: [
// Icône
Container(
padding: const EdgeInsets.all(SpacingTokens.sm),
decoration: BoxDecoration(
color: effectiveIconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
child: Icon(
icon,
color: effectiveIconColor,
size: 20,
),
),
const SizedBox(width: SpacingTokens.md),
// Titre
Expanded(
child: Text(
title,
style: AppTypography.headerSmall.copyWith(
color: AppColors.textPrimaryLight,
fontWeight: FontWeight.w600,
),
),
),
// Actions
if (actions != null) ...actions!,
],
),
),
// Statistiques
Padding(
padding: const EdgeInsets.fromLTRB(
SpacingTokens.lg,
0,
SpacingTokens.lg,
SpacingTokens.md,
),
child: Row(
children: stats.map((stat) {
final isLast = stat == stats.last;
return Expanded(
child: Padding(
padding: EdgeInsets.only(
right: isLast ? 0 : SpacingTokens.sm,
),
child: _buildStatItem(stat),
),
);
}).toList(),
),
),
// Divider
const Divider(
height: 1,
thickness: 1,
color: AppColors.lightBorder,
),
],
);
}
Widget _buildStatItem(UFHeaderStat stat) {
final effectiveColor = stat.color ?? AppColors.primaryGreen;
return UFContainer.rounded(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.md,
vertical: SpacingTokens.sm,
),
color: effectiveColor.withOpacity(0.05),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stat.value,
style: AppTypography.headerSmall.copyWith(
color: effectiveColor,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: SpacingTokens.xs),
Text(
stat.label,
style: AppTypography.badgeText.copyWith(
color: AppColors.textSecondaryLight,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
}
/// Statistique pour UFPageHeaderWithStats
class UFHeaderStat {
final String label;
final String value;
final Color? color;
const UFHeaderStat({
required this.label,
required this.value,
this.color,
});
}

View File

@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Bouton d'action rapide UnionFlow
class UnionActionButton extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
final Color? backgroundColor;
final Color? iconColor;
const UnionActionButton({
super.key,
required this.icon,
required this.label,
required this.onTap,
this.backgroundColor,
this.iconColor,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
decoration: BoxDecoration(
color: backgroundColor ?? UnionFlowColors.unionGreenPale,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: (backgroundColor ?? UnionFlowColors.unionGreenPale)
.withOpacity(0.2),
width: 1,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 28,
color: iconColor ?? UnionFlowColors.unionGreen,
),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
/// Grid d'actions rapides
class UnionActionGrid extends StatelessWidget {
final List<UnionActionButton> actions;
const UnionActionGrid({
super.key,
required this.actions,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
for (int i = 0; i < actions.length; i++) ...[
Expanded(child: actions[i]),
if (i < actions.length - 1) const SizedBox(width: 12),
],
],
);
}
}

View File

@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Card de balance UnionFlow - Affichage élégant du solde principal
class UnionBalanceCard extends StatelessWidget {
final String label;
final String amount;
final String? trend;
final bool? isTrendPositive;
final VoidCallback? onTap;
const UnionBalanceCard({
super.key,
required this.label,
required this.amount,
this.trend,
this.isTrendPositive,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
// Bordure dorée subtile en haut
border: const Border(
top: BorderSide(
color: UnionFlowColors.gold,
width: 3,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Label
Text(
label.toUpperCase(),
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
letterSpacing: 0.8,
),
),
const SizedBox(height: 8),
// Montant principal
Text(
amount,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
color: UnionFlowColors.unionGreen,
height: 1.2,
),
),
// Trend (optionnel)
if (trend != null) ...[
const SizedBox(height: 10),
Row(
children: [
Icon(
isTrendPositive == true
? Icons.trending_up
: Icons.trending_down,
size: 16,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
const SizedBox(width: 4),
Text(
trend!,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: isTrendPositive == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
),
],
),
],
],
),
),
);
}
}

View File

@@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Type d'export disponible
enum ExportType {
pdf('PDF', Icons.picture_as_pdf),
excel('Excel', Icons.table_chart),
csv('CSV', Icons.description);
final String label;
final IconData icon;
const ExportType(this.label, this.icon);
}
/// Bouton d'export avec options
class UnionExportButton extends StatelessWidget {
final Function(ExportType) onExport;
final bool isLoading;
const UnionExportButton({
super.key,
required this.onExport,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
return PopupMenuButton<ExportType>(
icon: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(12),
boxShadow: UnionFlowColors.greenGlowShadow,
),
child: isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: const Icon(
Icons.download,
color: Colors.white,
size: 20,
),
),
itemBuilder: (context) => ExportType.values.map((type) {
return PopupMenuItem<ExportType>(
value: type,
child: Row(
children: [
Icon(
type.icon,
size: 20,
color: UnionFlowColors.unionGreen,
),
const SizedBox(width: 12),
Text(
'Exporter en ${type.label}',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
],
),
);
}).toList(),
onSelected: onExport,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 8,
color: UnionFlowColors.surface,
);
}
}
/// Dialog pour confirmer l'export
class ExportConfirmDialog extends StatelessWidget {
final ExportType exportType;
final VoidCallback onConfirm;
final String? message;
const ExportConfirmDialog({
super.key,
required this.exportType,
required this.onConfirm,
this.message,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: UnionFlowColors.surface,
title: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: UnionFlowColors.unionGreen.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
exportType.icon,
color: UnionFlowColors.unionGreen,
size: 24,
),
),
const SizedBox(width: 12),
Text(
'Exporter en ${exportType.label}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
],
),
content: Text(
message ?? 'Voulez-vous exporter le rapport au format ${exportType.label}?',
style: const TextStyle(
fontSize: 14,
color: UnionFlowColors.textSecondary,
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text(
'Annuler',
style: TextStyle(
color: UnionFlowColors.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
onConfirm();
},
style: ElevatedButton.styleFrom(
backgroundColor: UnionFlowColors.unionGreen,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
'Confirmer',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
],
);
}
}

View File

@@ -0,0 +1,65 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Card avec effet glassmorphism
class UnionGlassCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final double? borderRadius;
final VoidCallback? onTap;
const UnionGlassCard({
super.key,
required this.child,
this.padding,
this.margin,
this.borderRadius,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: margin,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius ?? 16),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: UnionFlowColors.unionGreen.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius ?? 16),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: padding ?? const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white.withOpacity(0.2),
Colors.white.withOpacity(0.1),
],
),
),
child: child,
),
),
),
),
);
}
}

View File

@@ -0,0 +1,216 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../tokens/unionflow_colors.dart';
/// Graphique en ligne UnionFlow - Pour afficher l'évolution temporelle
class UnionLineChart extends StatelessWidget {
final List<FlSpot> spots;
final String title;
final String? subtitle;
final Color? lineColor;
final Color? gradientStartColor;
final Color? gradientEndColor;
const UnionLineChart({
super.key,
required this.spots,
required this.title,
this.subtitle,
this.lineColor,
this.gradientStartColor,
this.gradientEndColor,
});
/// Calcule maxY de manière sécurisée pour éviter NaN, Infinity ou 0
double _calculateSafeMaxY() {
if (spots.isEmpty) return 100.0;
final maxValue = spots.map((e) => e.y).reduce((a, b) => a > b ? a : b);
// Si maxValue est invalide (NaN, Infinity) ou trop petit
if (maxValue.isNaN || maxValue.isInfinite || maxValue <= 0) {
return 100.0;
}
return maxValue * 1.2;
}
/// Calcule maxX de manière sécurisée
double _calculateSafeMaxX() {
if (spots.isEmpty) return 11.0; // 12 mois - 1
return spots.length.toDouble() - 1;
}
/// Calcule l'intervalle de grille approprié basé sur maxY
double _calculateGridInterval() {
final maxY = _calculateSafeMaxY();
// Calculer un intervalle qui donne environ 4-6 lignes de grille
final baseInterval = maxY / 5;
if (baseInterval == 0) return 20.0; // Fallback si maxY est trop petit
// Arrondir à un nombre "propre" (puissance de 10)
final magnitude = pow(10.0, (log(baseInterval) / log(10.0)).floor()).toDouble();
final normalized = baseInterval / magnitude;
// Arrondir vers le haut au multiple de 1, 2 ou 5 le plus proche
double roundedInterval;
if (normalized <= 1) {
roundedInterval = 1;
} else if (normalized <= 2) {
roundedInterval = 2;
} else if (normalized <= 5) {
roundedInterval = 5;
} else {
roundedInterval = 10;
}
return roundedInterval * magnitude;
}
@override
Widget build(BuildContext context) {
final effectiveLineColor = lineColor ?? UnionFlowColors.unionGreen;
final effectiveGradientStart = gradientStartColor ?? UnionFlowColors.unionGreen.withOpacity(0.3);
final effectiveGradientEnd = gradientEndColor ?? UnionFlowColors.unionGreen.withOpacity(0.0);
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
),
],
const SizedBox(height: 20),
// Chart
SizedBox(
height: 180,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: _calculateGridInterval(),
getDrawingHorizontalLine: (value) {
return FlLine(
color: UnionFlowColors.border.withOpacity(0.2),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
getTitlesWidget: (value, meta) {
const months = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'];
if (value.toInt() >= 0 && value.toInt() < months.length) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
months[value.toInt()],
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textTertiary,
),
),
);
}
return const Text('');
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
return Text(
'${(value / 1000).toStringAsFixed(0)}K',
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textTertiary,
),
);
},
),
),
),
borderData: FlBorderData(show: false),
minX: 0,
maxX: _calculateSafeMaxX(),
minY: 0,
maxY: _calculateSafeMaxY(),
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: true,
color: effectiveLineColor,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: UnionFlowColors.surface,
strokeWidth: 2,
strokeColor: effectiveLineColor,
);
},
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
effectiveGradientStart,
effectiveGradientEnd,
],
),
),
),
],
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,213 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Badge de notification avec compteur
class UnionNotificationBadge extends StatelessWidget {
final int count;
final Widget child;
final Color? badgeColor;
final bool showZero;
const UnionNotificationBadge({
super.key,
required this.count,
required this.child,
this.badgeColor,
this.showZero = false,
});
@override
Widget build(BuildContext context) {
final shouldShow = count > 0 || showZero;
return Stack(
clipBehavior: Clip.none,
children: [
child,
if (shouldShow)
Positioned(
right: -6,
top: -6,
child: AnimatedScale(
duration: const Duration(milliseconds: 300),
scale: 1.0,
curve: Curves.elasticOut,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: badgeColor ?? UnionFlowColors.error,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: UnionFlowColors.surface,
width: 2,
),
boxShadow: [
BoxShadow(
color: (badgeColor ?? UnionFlowColors.error)
.withOpacity(0.4),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
constraints: const BoxConstraints(
minWidth: 18,
minHeight: 18,
),
child: Text(
count > 99 ? '99+' : count.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w700,
),
textAlign: TextAlign.center,
),
),
),
),
],
);
}
}
/// Widget de notification en temps réel (toast)
class UnionNotificationToast extends StatelessWidget {
final String title;
final String message;
final IconData icon;
final Color color;
final VoidCallback? onTap;
const UnionNotificationToast({
super.key,
required this.title,
required this.message,
this.icon = Icons.notifications_active,
this.color = UnionFlowColors.info,
this.onTap,
});
static void show(
BuildContext context, {
required String title,
required String message,
IconData icon = Icons.notifications_active,
Color color = UnionFlowColors.info,
VoidCallback? onTap,
}) {
final overlay = Overlay.of(context);
late OverlayEntry entry;
entry = OverlayEntry(
builder: (context) => Positioned(
top: 60,
left: 16,
right: 16,
child: Material(
color: Colors.transparent,
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
builder: (context, value, child) {
return Transform.translate(
offset: Offset(0, -20 * (1 - value)),
child: Opacity(
opacity: value,
child: child,
),
);
},
child: UnionNotificationToast(
title: title,
message: message,
icon: icon,
color: color,
onTap: () {
entry.remove();
onTap?.call();
},
),
),
),
),
);
overlay.insert(entry);
// Auto-dismiss après 4 secondes
Future.delayed(const Duration(seconds: 4), () {
entry.remove();
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.mediumShadow,
border: Border(
left: BorderSide(
color: color,
width: 4,
),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
message,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.chevron_right,
size: 18,
color: UnionFlowColors.textTertiary,
),
],
),
),
);
}
}

View File

@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Filtre de période pour le dashboard
enum PeriodFilter {
today('Aujourd\'hui'),
week('Cette semaine'),
month('Ce mois'),
quarter('Ce trimestre'),
year('Cette année'),
custom('Personnalisé');
final String label;
const PeriodFilter(this.label);
}
/// Widget de sélection de période
class UnionPeriodFilter extends StatelessWidget {
final PeriodFilter selectedPeriod;
final Function(PeriodFilter) onPeriodChanged;
final bool showCustom;
const UnionPeriodFilter({
super.key,
required this.selectedPeriod,
required this.onPeriodChanged,
this.showCustom = false,
});
@override
Widget build(BuildContext context) {
final periods = showCustom
? PeriodFilter.values
: PeriodFilter.values.where((p) => p != PeriodFilter.custom).toList();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(
Icons.calendar_today,
size: 16,
color: UnionFlowColors.unionGreen,
),
SizedBox(width: 8),
Text(
'Période',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: periods.map((period) {
final isSelected = selectedPeriod == period;
return GestureDetector(
onTap: () => onPeriodChanged(period),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
decoration: BoxDecoration(
gradient: isSelected
? UnionFlowColors.primaryGradient
: null,
color: isSelected
? null
: UnionFlowColors.surfaceVariant,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected
? UnionFlowColors.unionGreen
: UnionFlowColors.border,
width: isSelected ? 1.5 : 1,
),
),
child: Text(
period.label,
style: TextStyle(
fontSize: 12,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
color: isSelected
? Colors.white
: UnionFlowColors.textSecondary,
),
),
),
);
}).toList(),
),
],
),
);
}
}

View File

@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import '../tokens/unionflow_colors.dart';
/// Graphique circulaire UnionFlow - Pour afficher des répartitions
class UnionPieChart extends StatelessWidget {
final List<PieChartSectionData> sections;
final String title;
final String? subtitle;
final double? centerSpaceRadius;
const UnionPieChart({
super.key,
required this.sections,
required this.title,
this.subtitle,
this.centerSpaceRadius,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
),
],
const SizedBox(height: 20),
// Chart
SizedBox(
height: 180,
child: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: centerSpaceRadius ?? 50,
sections: sections,
),
),
),
],
),
);
}
}
/// Helper pour créer des sections de pie chart
class UnionPieChartSection {
static PieChartSectionData create({
required double value,
required Color color,
required String title,
double radius = 50,
bool showTitle = true,
}) {
return PieChartSectionData(
color: color,
value: value,
title: showTitle ? title : '',
radius: radius,
titleStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.white,
),
badgeWidget: null,
);
}
}

View File

@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Card de progression UnionFlow avec barre de progrès élégante
class UnionProgressCard extends StatelessWidget {
final String title;
final double progress; // 0.0 à 1.0
final String subtitle;
final Color? progressColor;
final VoidCallback? onTap;
const UnionProgressCard({
super.key,
required this.title,
required this.progress,
required this.subtitle,
this.progressColor,
this.onTap,
});
@override
Widget build(BuildContext context) {
final effectiveColor = progressColor ?? UnionFlowColors.gold;
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 12),
// Progress bar
Stack(
children: [
// Background track
Container(
height: 14,
decoration: BoxDecoration(
color: UnionFlowColors.border,
borderRadius: BorderRadius.circular(20),
),
),
// Progress fill avec gradient
FractionallySizedBox(
widthFactor: progress.clamp(0.0, 1.0),
child: Container(
height: 14,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
effectiveColor,
effectiveColor.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: effectiveColor.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
),
),
],
),
const SizedBox(height: 8),
// Subtitle
Text(
subtitle,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Widget de statistique compacte avec icône et tendance
class UnionStatWidget extends StatelessWidget {
final String label;
final String value;
final IconData icon;
final Color color;
final String? trend;
final bool? isTrendUp;
const UnionStatWidget({
super.key,
required this.label,
required this.value,
required this.icon,
required this.color,
this.trend,
this.isTrendUp,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
border: Border(
left: BorderSide(
color: color,
width: 4,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Icon
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 20, color: color),
),
const SizedBox(height: 12),
// Value
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: color,
),
),
const SizedBox(height: 4),
// Label
Text(
label,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
),
),
// Trend
if (trend != null) ...[
const SizedBox(height: 8),
Row(
children: [
Icon(
isTrendUp == true
? Icons.trending_up
: Icons.trending_down,
size: 14,
color: isTrendUp == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
const SizedBox(width: 4),
Text(
trend!,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: isTrendUp == true
? UnionFlowColors.success
: UnionFlowColors.error,
),
),
],
),
],
],
),
);
}
}

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Tuile de transaction UnionFlow
class UnionTransactionTile extends StatelessWidget {
final String name;
final String amount;
final String status;
final String? date;
final VoidCallback? onTap;
const UnionTransactionTile({
super.key,
required this.name,
required this.amount,
required this.status,
this.date,
this.onTap,
});
Color _getStatusColor() {
switch (status.toLowerCase()) {
case 'confirmé':
case 'confirmed':
return UnionFlowColors.success;
case 'en attente':
case 'pending':
return UnionFlowColors.warning;
case 'échoué':
case 'failed':
return UnionFlowColors.error;
default:
return UnionFlowColors.textSecondary;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: UnionFlowColors.border,
width: 1,
),
),
),
child: Row(
children: [
// Avatar avec initiale
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
name.isNotEmpty ? name[0].toUpperCase() : '?',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
),
const SizedBox(width: 12),
// Nom et date
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
if (date != null) ...[
const SizedBox(height: 2),
Text(
date!,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
],
),
),
// Montant et status
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amount,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: UnionFlowColors.unionGreen,
),
),
const SizedBox(height: 2),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: _getStatusColor().withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
status,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: _getStatusColor(),
),
),
),
],
),
],
),
),
);
}
}
/// Liste de transactions dans une card
class UnionTransactionCard extends StatelessWidget {
final String title;
final List<UnionTransactionTile> transactions;
final VoidCallback? onSeeAll;
const UnionTransactionCard({
super.key,
required this.title,
required this.transactions,
this.onSeeAll,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
if (onSeeAll != null)
GestureDetector(
onTap: onSeeAll,
child: const Text(
'Voir tout',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.unionGreen,
),
),
),
],
),
const SizedBox(height: 12),
// Transactions
...transactions,
],
),
);
}
}

View File

@@ -0,0 +1,217 @@
import 'package:flutter/material.dart';
import '../tokens/unionflow_colors.dart';
/// Carte premium affichant la vue unifiée "Compte Adhérent" du membre.
class UnionUnifiedAccountCard extends StatelessWidget {
final String numeroMembre;
final String organisationNom;
final String soldeTotal;
final String capaciteEmprunt;
final String epargneBloquee;
final double engagementRate; // 0.0 to 1.0
final VoidCallback? onDetailsTap;
const UnionUnifiedAccountCard({
super.key,
required this.numeroMembre,
required this.organisationNom,
required this.soldeTotal,
required this.capaciteEmprunt,
required this.epargneBloquee,
required this.engagementRate,
this.onDetailsTap,
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
UnionFlowColors.unionGreen,
UnionFlowColors.unionGreen.withOpacity(0.85),
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: UnionFlowColors.unionGreen.withOpacity(0.35),
offset: const Offset(0, 10),
blurRadius: 20,
),
],
),
child: Stack(
children: [
// Pattern décoratif subtil (fond)
Positioned(
right: -20,
bottom: -20,
child: Icon(
Icons.account_balance_wallet,
size: 150,
color: Colors.white.withOpacity(0.1),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête : Organisation + Numéro
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
organisationNom.toUpperCase(),
style: const TextStyle(
color: Colors.white70,
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 1.2,
),
),
const SizedBox(height: 4),
Text(
'COMPTE ADHÉRENT',
style: TextStyle(
color: Colors.white.withOpacity(0.95),
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white30),
),
child: Text(
numeroMembre,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w700,
fontFamily: 'Courier', // Look like a card number
),
),
),
],
),
const SizedBox(height: 32),
// Solde Total Disponible
const Text(
'Solde Total Disponible',
style: TextStyle(
color: Colors.white70,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
soldeTotal,
style: const TextStyle(
color: Colors.white,
fontSize: 36,
fontWeight: FontWeight.w800,
letterSpacing: -0.5,
),
),
),
const SizedBox(height: 32),
// Grille de détails
Row(
children: [
_buildSubStat('Capacité Emprunt', capaciteEmprunt, Icons.rocket_launch, UnionFlowColors.gold),
const SizedBox(width: 16),
_buildSubStat('Épargne Bloquée', epargneBloquee, Icons.lock_clock, Colors.white60),
],
),
const SizedBox(height: 20),
// Barre d'engagement
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Taux d\'engagement (Cotisations)',
style: TextStyle(color: Colors.white70, fontSize: 10, fontWeight: FontWeight.w600),
),
Text(
'${(engagementRate * 100).toInt()}%',
style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.w800),
),
],
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: engagementRate,
backgroundColor: Colors.white10,
valueColor: const AlwaysStoppedAnimation<Color>(UnionFlowColors.gold),
minHeight: 6,
),
),
],
),
],
),
),
],
),
);
}
Widget _buildSubStat(String label, String value, IconData icon, Color iconColor) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 12, color: iconColor),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(color: Colors.white70, fontSize: 10, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,337 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Gestionnaire de thèmes personnalisables pour le Dashboard
class DashboardThemeManager {
static const String _themeKey = 'dashboard_theme';
static DashboardThemeData _currentTheme = DashboardThemeData.royalTeal();
static SharedPreferences? _prefs;
/// Initialise le gestionnaire de thèmes
static Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
await _loadSavedTheme();
}
/// Charge le thème sauvegardé
static Future<void> _loadSavedTheme() async {
final themeName = _prefs?.getString(_themeKey) ?? 'royalTeal';
_currentTheme = _getThemeByName(themeName);
}
/// Obtient le thème actuel
static DashboardThemeData get currentTheme => _currentTheme;
/// Change le thème et le sauvegarde
static Future<void> setTheme(String themeName) async {
_currentTheme = _getThemeByName(themeName);
await _prefs?.setString(_themeKey, themeName);
}
/// Obtient un thème par son nom
static DashboardThemeData _getThemeByName(String name) {
switch (name) {
case 'royalTeal':
return DashboardThemeData.royalTeal();
case 'oceanBlue':
return DashboardThemeData.oceanBlue();
case 'forestGreen':
return DashboardThemeData.forestGreen();
case 'sunsetOrange':
return DashboardThemeData.sunsetOrange();
case 'purpleNight':
return DashboardThemeData.purpleNight();
case 'darkMode':
return DashboardThemeData.darkMode();
default:
return DashboardThemeData.royalTeal();
}
}
/// Obtient la liste des thèmes disponibles
static List<ThemeOption> get availableThemes => [
ThemeOption('royalTeal', 'Bleu Roi & Pétrole', DashboardThemeData.royalTeal()),
ThemeOption('oceanBlue', 'Bleu Océan', DashboardThemeData.oceanBlue()),
ThemeOption('forestGreen', 'Vert Forêt', DashboardThemeData.forestGreen()),
ThemeOption('sunsetOrange', 'Orange Coucher', DashboardThemeData.sunsetOrange()),
ThemeOption('purpleNight', 'Violet Nuit', DashboardThemeData.purpleNight()),
ThemeOption('darkMode', 'Mode Sombre', DashboardThemeData.darkMode()),
];
}
/// Option de thème
class ThemeOption {
final String key;
final String name;
final DashboardThemeData theme;
const ThemeOption(this.key, this.name, this.theme);
}
/// Données d'un thème de dashboard
class DashboardThemeData {
final String name;
final Color primaryColor;
final Color secondaryColor;
final Color primaryLight;
final Color primaryDark;
final Color secondaryLight;
final Color secondaryDark;
final Color backgroundColor;
final Color surfaceColor;
final Color cardColor;
final Color textPrimary;
final Color textSecondary;
final Color success;
final Color warning;
final Color error;
final Color info;
final bool isDark;
const DashboardThemeData({
required this.name,
required this.primaryColor,
required this.secondaryColor,
required this.primaryLight,
required this.primaryDark,
required this.secondaryLight,
required this.secondaryDark,
required this.backgroundColor,
required this.surfaceColor,
required this.cardColor,
required this.textPrimary,
required this.textSecondary,
required this.success,
required this.warning,
required this.error,
required this.info,
this.isDark = false,
});
/// Thème Bleu Roi & Pétrole (par défaut)
factory DashboardThemeData.royalTeal() {
return const DashboardThemeData(
name: 'Bleu Roi & Pétrole',
primaryColor: Color(0xFF4169E1),
secondaryColor: Color(0xFF008B8B),
primaryLight: Color(0xFF6A8EF7),
primaryDark: Color(0xFF2E4BC6),
secondaryLight: Color(0xFF20B2AA),
secondaryDark: Color(0xFF006666),
backgroundColor: Color(0xFFF9FAFB),
surfaceColor: Color(0xFFFFFFFF),
cardColor: Color(0xFFFFFFFF),
textPrimary: Color(0xFF111827),
textSecondary: Color(0xFF6B7280),
success: Color(0xFF10B981),
warning: Color(0xFFF59E0B),
error: Color(0xFFEF4444),
info: Color(0xFF3B82F6),
);
}
/// Thème Bleu Océan
factory DashboardThemeData.oceanBlue() {
return const DashboardThemeData(
name: 'Bleu Océan',
primaryColor: Color(0xFF0EA5E9),
secondaryColor: Color(0xFF0284C7),
primaryLight: Color(0xFF38BDF8),
primaryDark: Color(0xFF0369A1),
secondaryLight: Color(0xFF0EA5E9),
secondaryDark: Color(0xFF075985),
backgroundColor: Color(0xFFF0F9FF),
surfaceColor: Color(0xFFFFFFFF),
cardColor: Color(0xFFFFFFFF),
textPrimary: Color(0xFF0C4A6E),
textSecondary: Color(0xFF64748B),
success: Color(0xFF059669),
warning: Color(0xFFD97706),
error: Color(0xFFDC2626),
info: Color(0xFF2563EB),
);
}
/// Thème Vert Forêt
factory DashboardThemeData.forestGreen() {
return const DashboardThemeData(
name: 'Vert Forêt',
primaryColor: Color(0xFF059669),
secondaryColor: Color(0xFF047857),
primaryLight: Color(0xFF10B981),
primaryDark: Color(0xFF065F46),
secondaryLight: Color(0xFF059669),
secondaryDark: Color(0xFF064E3B),
backgroundColor: Color(0xFFF0FDF4),
surfaceColor: Color(0xFFFFFFFF),
cardColor: Color(0xFFFFFFFF),
textPrimary: Color(0xFF064E3B),
textSecondary: Color(0xFF6B7280),
success: Color(0xFF10B981),
warning: Color(0xFFF59E0B),
error: Color(0xFFEF4444),
info: Color(0xFF3B82F6),
);
}
/// Thème Orange Coucher de Soleil
factory DashboardThemeData.sunsetOrange() {
return const DashboardThemeData(
name: 'Orange Coucher',
primaryColor: Color(0xFFEA580C),
secondaryColor: Color(0xFFDC2626),
primaryLight: Color(0xFFF97316),
primaryDark: Color(0xFFC2410C),
secondaryLight: Color(0xFFEF4444),
secondaryDark: Color(0xFFB91C1C),
backgroundColor: Color(0xFFFFF7ED),
surfaceColor: Color(0xFFFFFFFF),
cardColor: Color(0xFFFFFFFF),
textPrimary: Color(0xFF9A3412),
textSecondary: Color(0xFF78716C),
success: Color(0xFF059669),
warning: Color(0xFFF59E0B),
error: Color(0xFFDC2626),
info: Color(0xFF2563EB),
);
}
/// Thème Violet Nuit
factory DashboardThemeData.purpleNight() {
return const DashboardThemeData(
name: 'Violet Nuit',
primaryColor: Color(0xFF7C3AED),
secondaryColor: Color(0xFF9333EA),
primaryLight: Color(0xFF8B5CF6),
primaryDark: Color(0xFF5B21B6),
secondaryLight: Color(0xFFA855F7),
secondaryDark: Color(0xFF7E22CE),
backgroundColor: Color(0xFFFAF5FF),
surfaceColor: Color(0xFFFFFFFF),
cardColor: Color(0xFFFFFFFF),
textPrimary: Color(0xFF581C87),
textSecondary: Color(0xFF6B7280),
success: Color(0xFF059669),
warning: Color(0xFFF59E0B),
error: Color(0xFFEF4444),
info: Color(0xFF3B82F6),
);
}
/// Thème Mode Sombre
factory DashboardThemeData.darkMode() {
return const DashboardThemeData(
name: 'Mode Sombre',
primaryColor: Color(0xFF60A5FA),
secondaryColor: Color(0xFF34D399),
primaryLight: Color(0xFF93C5FD),
primaryDark: Color(0xFF3B82F6),
secondaryLight: Color(0xFF6EE7B7),
secondaryDark: Color(0xFF10B981),
backgroundColor: Color(0xFF111827),
surfaceColor: Color(0xFF1F2937),
cardColor: Color(0xFF374151),
textPrimary: Color(0xFFF9FAFB),
textSecondary: Color(0xFFD1D5DB),
success: Color(0xFF34D399),
warning: Color(0xFFFBBF24),
error: Color(0xFFF87171),
info: Color(0xFF60A5FA),
isDark: true,
);
}
/// Gradient primaire
LinearGradient get primaryGradient => LinearGradient(
colors: [primaryColor, secondaryColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Gradient de carte
LinearGradient get cardGradient => LinearGradient(
colors: [
cardColor,
isDark ? surfaceColor : const Color(0xFFF8FAFC),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
/// Gradient d'en-tête
LinearGradient get headerGradient => LinearGradient(
colors: [primaryColor, primaryDark],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
/// Style de bouton primaire
ButtonStyle get primaryButtonStyle => ElevatedButton.styleFrom(
backgroundColor: primaryColor,
foregroundColor: isDark ? textPrimary : Colors.white,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
);
/// Style de bouton secondaire
ButtonStyle get secondaryButtonStyle => OutlinedButton.styleFrom(
foregroundColor: primaryColor,
side: BorderSide(color: primaryColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
);
/// Thème Flutter complet
ThemeData get flutterTheme => ThemeData(
useMaterial3: true,
brightness: isDark ? Brightness.dark : Brightness.light,
primaryColor: primaryColor,
colorScheme: ColorScheme.fromSeed(
seedColor: primaryColor,
brightness: isDark ? Brightness.dark : Brightness.light,
secondary: secondaryColor,
surface: surfaceColor,
background: backgroundColor,
),
scaffoldBackgroundColor: backgroundColor,
cardColor: cardColor,
appBarTheme: AppBarTheme(
backgroundColor: primaryColor,
foregroundColor: isDark ? textPrimary : Colors.white,
elevation: 0,
centerTitle: true,
),
elevatedButtonTheme: ElevatedButtonThemeData(style: primaryButtonStyle),
outlinedButtonTheme: OutlinedButtonThemeData(style: secondaryButtonStyle),
cardTheme: CardTheme(
color: cardColor,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
textTheme: TextTheme(
displayLarge: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: textPrimary,
),
displayMedium: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: textPrimary,
),
bodyLarge: TextStyle(
fontSize: 16,
color: textPrimary,
),
bodyMedium: TextStyle(
fontSize: 14,
color: textSecondary,
),
),
);
}

View File

@@ -0,0 +1,141 @@
import 'package:flutter/material.dart';
import '../tokens/app_colors.dart';
import '../tokens/app_typography.dart';
/// UnionFlow Mobile App - Thème Global
/// Utilise la charte stricte (Vert/Blanc/Noir OLED) et force la petite typographie.
class AppTheme {
// --- THÈME CLAIR (Mode Jour) ---
static final ThemeData lightTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: AppColors.primaryGreen,
scaffoldBackgroundColor: AppColors.lightSurface,
colorScheme: const ColorScheme.light(
primary: AppColors.primaryGreen,
secondary: AppColors.brandGreenLight,
surface: AppColors.lightBackground,
error: AppColors.error,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: AppColors.textPrimaryLight,
onError: Colors.white,
),
// Forcer la typographie standardisée
textTheme: const TextTheme(
titleMedium: AppTypography.headerSmall,
bodyMedium: AppTypography.bodyTextSmall,
bodySmall: AppTypography.subtitleSmall,
labelLarge: AppTypography.actionText,
labelSmall: AppTypography.badgeText,
),
// Personnalisation des AppBar (Garder la minimaliste)
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.lightBackground,
foregroundColor: AppColors.textPrimaryLight,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: AppColors.textPrimaryLight, size: 20),
titleTextStyle: AppTypography.headerSmall,
),
// Boutons par défaut
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
elevation: 0,
textStyle: AppTypography.actionText,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
minimumSize: const Size(64, 32),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
// BottomNavigationBar ultra-compacte
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.lightBackground,
selectedItemColor: AppColors.primaryGreen,
unselectedItemColor: AppColors.textSecondaryLight,
showSelectedLabels: false,
showUnselectedLabels: false,
elevation: 8,
type: BottomNavigationBarType.fixed,
),
dividerTheme: const DividerThemeData(
color: AppColors.lightBorder,
thickness: 1,
space: 1,
),
);
// --- THÈME SOMBRE (Mode Nuit OLED) ---
static final ThemeData darkTheme = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primaryColor: AppColors.primaryGreen,
scaffoldBackgroundColor: AppColors.darkBackground, // Noir OLED
colorScheme: const ColorScheme.dark(
primary: AppColors.primaryGreen,
secondary: AppColors.brandGreenLight,
surface: AppColors.darkSurface, // Gris très sombre
error: AppColors.error,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: AppColors.textPrimaryDark,
onError: Colors.white,
),
textTheme: const TextTheme(
titleMedium: AppTypography.headerSmall,
bodyMedium: AppTypography.bodyTextSmall,
bodySmall: AppTypography.subtitleSmall,
labelLarge: AppTypography.actionText,
labelSmall: AppTypography.badgeText,
).apply(
bodyColor: AppColors.textPrimaryDark,
displayColor: AppColors.textPrimaryDark,
),
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.darkBackground,
foregroundColor: AppColors.textPrimaryDark,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: AppColors.textPrimaryDark, size: 20),
titleTextStyle: AppTypography.headerSmall, // Remplace titleTextStyle
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
elevation: 0,
textStyle: AppTypography.actionText,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
minimumSize: const Size(64, 32),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.darkBackground,
selectedItemColor: AppColors.primaryGreen,
unselectedItemColor: AppColors.textSecondaryDark,
showSelectedLabels: false,
showUnselectedLabels: false,
elevation: 8,
type: BottomNavigationBarType.fixed,
),
dividerTheme: const DividerThemeData(
color: AppColors.darkBorder,
thickness: 1,
space: 1,
),
);
}

View File

@@ -0,0 +1,473 @@
/// Thème Sophistiqué UnionFlow
///
/// Implémentation complète du design system avec les dernières tendances UI/UX 2024-2025
/// Architecture modulaire et tokens de design cohérents
library app_theme_sophisticated;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../tokens/color_tokens.dart';
import '../tokens/typography_tokens.dart';
import '../tokens/spacing_tokens.dart';
/// Thème principal de l'application UnionFlow
class AppThemeSophisticated {
AppThemeSophisticated._();
// ═══════════════════════════════════════════════════════════════════════════
// THÈME PRINCIPAL - Configuration complète
// ═══════════════════════════════════════════════════════════════════════════
/// Thème clair principal
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
// Couleurs principales
colorScheme: _lightColorScheme,
// Typographie
textTheme: _textTheme,
// Configuration de l'AppBar
appBarTheme: _appBarTheme,
// Configuration des cartes
cardTheme: _cardTheme,
// Configuration des boutons
elevatedButtonTheme: _elevatedButtonTheme,
filledButtonTheme: _filledButtonTheme,
outlinedButtonTheme: _outlinedButtonTheme,
textButtonTheme: _textButtonTheme,
// Configuration des champs de saisie
inputDecorationTheme: _inputDecorationTheme,
// Configuration de la navigation
navigationBarTheme: _navigationBarTheme,
navigationDrawerTheme: _navigationDrawerTheme,
// Configuration des dialogues
dialogTheme: _dialogTheme,
// Configuration des snackbars
snackBarTheme: _snackBarTheme,
// Configuration des puces
chipTheme: _chipTheme,
// Configuration des listes
listTileTheme: _listTileTheme,
// Configuration des onglets
tabBarTheme: _tabBarTheme,
// Configuration des dividers
dividerTheme: _dividerTheme,
// Configuration des icônes
iconTheme: _iconTheme,
// Configuration des surfaces
scaffoldBackgroundColor: ColorTokens.surface,
canvasColor: ColorTokens.surface,
// Configuration des animations
pageTransitionsTheme: _pageTransitionsTheme,
// Configuration des extensions
extensions: const [
_customColors,
_customSpacing,
],
);
}
/// Thème sombre (suit le système ou sélection manuelle)
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: ColorScheme.dark(
primary: ColorTokens.primary,
onPrimary: Colors.white,
surface: const Color(0xFF121212),
onSurface: Colors.white,
error: ColorTokens.error,
),
scaffoldBackgroundColor: const Color(0xFF121212),
);
}
// ═══════════════════════════════════════════════════════════════════════════
// SCHÉMA DE COULEURS
// ═══════════════════════════════════════════════════════════════════════════
static const ColorScheme _lightColorScheme = ColorScheme.light(
// Couleurs primaires
primary: ColorTokens.primary,
onPrimary: ColorTokens.onPrimary,
primaryContainer: ColorTokens.primaryContainer,
onPrimaryContainer: ColorTokens.onPrimaryContainer,
// Couleurs secondaires
secondary: ColorTokens.secondary,
onSecondary: ColorTokens.onSecondary,
secondaryContainer: ColorTokens.secondaryContainer,
onSecondaryContainer: ColorTokens.onSecondaryContainer,
// Couleurs tertiaires
tertiary: ColorTokens.tertiary,
onTertiary: ColorTokens.onTertiary,
tertiaryContainer: ColorTokens.tertiaryContainer,
onTertiaryContainer: ColorTokens.onTertiaryContainer,
// Couleurs d'erreur
error: ColorTokens.error,
onError: ColorTokens.onError,
errorContainer: ColorTokens.errorContainer,
onErrorContainer: ColorTokens.onErrorContainer,
// Couleurs de surface
surface: ColorTokens.surface,
onSurface: ColorTokens.onSurface,
surfaceContainerHighest: ColorTokens.surfaceVariant,
onSurfaceVariant: ColorTokens.onSurfaceVariant,
// Couleurs de contour
outline: ColorTokens.outline,
outlineVariant: ColorTokens.outlineVariant,
// Couleurs d'ombre
shadow: ColorTokens.shadow,
scrim: ColorTokens.shadow,
// Couleurs d'inversion
inverseSurface: ColorTokens.onSurface,
onInverseSurface: ColorTokens.surface,
inversePrimary: ColorTokens.primaryLight,
);
// ═══════════════════════════════════════════════════════════════════════════
// THÈME TYPOGRAPHIQUE
// ═══════════════════════════════════════════════════════════════════════════
static const TextTheme _textTheme = TextTheme(
// Display styles
displayLarge: TypographyTokens.displayLarge,
displayMedium: TypographyTokens.displayMedium,
displaySmall: TypographyTokens.displaySmall,
// Headline styles
headlineLarge: TypographyTokens.headlineLarge,
headlineMedium: TypographyTokens.headlineMedium,
headlineSmall: TypographyTokens.headlineSmall,
// Title styles
titleLarge: TypographyTokens.titleLarge,
titleMedium: TypographyTokens.titleMedium,
titleSmall: TypographyTokens.titleSmall,
// Label styles
labelLarge: TypographyTokens.labelLarge,
labelMedium: TypographyTokens.labelMedium,
labelSmall: TypographyTokens.labelSmall,
// Body styles
bodyLarge: TypographyTokens.bodyLarge,
bodyMedium: TypographyTokens.bodyMedium,
bodySmall: TypographyTokens.bodySmall,
);
// ═══════════════════════════════════════════════════════════════════════════
// THÈMES DE COMPOSANTS
// ═══════════════════════════════════════════════════════════════════════════
/// Configuration AppBar moderne (sans AppBar traditionnelle)
static const AppBarTheme _appBarTheme = AppBarTheme(
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: Colors.transparent,
foregroundColor: ColorTokens.onSurface,
surfaceTintColor: Colors.transparent,
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
);
/// Configuration des cartes sophistiquées
static final CardTheme _cardTheme = CardTheme(
elevation: SpacingTokens.elevationSm,
shadowColor: ColorTokens.shadow,
surfaceTintColor: ColorTokens.surfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
),
margin: const EdgeInsets.all(SpacingTokens.cardMargin),
);
/// Configuration des boutons élevés
static final ElevatedButtonThemeData _elevatedButtonTheme = ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
elevation: SpacingTokens.elevationSm,
shadowColor: ColorTokens.shadow,
backgroundColor: ColorTokens.primary,
foregroundColor: ColorTokens.onPrimary,
textStyle: TypographyTokens.buttonMedium,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
minimumSize: const Size(
SpacingTokens.minButtonWidth,
SpacingTokens.buttonHeightMedium,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
);
/// Configuration des boutons remplis
static final FilledButtonThemeData _filledButtonTheme = FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: ColorTokens.primary,
foregroundColor: ColorTokens.onPrimary,
textStyle: TypographyTokens.buttonMedium,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
minimumSize: const Size(
SpacingTokens.minButtonWidth,
SpacingTokens.buttonHeightMedium,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
);
/// Configuration des boutons avec contour
static final OutlinedButtonThemeData _outlinedButtonTheme = OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: ColorTokens.primary,
textStyle: TypographyTokens.buttonMedium,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
minimumSize: const Size(
SpacingTokens.minButtonWidth,
SpacingTokens.buttonHeightMedium,
),
side: const BorderSide(
color: ColorTokens.outline,
width: 1.0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
);
/// Configuration des boutons texte
static final TextButtonThemeData _textButtonTheme = TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: ColorTokens.primary,
textStyle: TypographyTokens.buttonMedium,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.buttonPaddingHorizontal,
vertical: SpacingTokens.buttonPaddingVertical,
),
minimumSize: const Size(
SpacingTokens.minButtonWidth,
SpacingTokens.buttonHeightMedium,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
),
);
/// Configuration des champs de saisie
static final InputDecorationTheme _inputDecorationTheme = InputDecorationTheme(
filled: true,
fillColor: ColorTokens.surfaceContainer,
labelStyle: TypographyTokens.inputLabel,
hintStyle: TypographyTokens.inputHint,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderSide: const BorderSide(color: ColorTokens.outline),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderSide: const BorderSide(color: ColorTokens.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderSide: const BorderSide(color: ColorTokens.primary, width: 2.0),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
borderSide: const BorderSide(color: ColorTokens.error),
),
contentPadding: const EdgeInsets.all(SpacingTokens.formPadding),
);
/// Configuration de la barre de navigation
static final NavigationBarThemeData _navigationBarTheme = NavigationBarThemeData(
backgroundColor: ColorTokens.navigationBackground,
indicatorColor: ColorTokens.navigationIndicator,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return TypographyTokens.navigationLabelSelected;
}
return TypographyTokens.navigationLabel;
}),
iconTheme: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return const IconThemeData(color: ColorTokens.navigationSelected);
}
return const IconThemeData(color: ColorTokens.navigationUnselected);
}),
);
/// Configuration du drawer de navigation
static final NavigationDrawerThemeData _navigationDrawerTheme = NavigationDrawerThemeData(
backgroundColor: ColorTokens.surfaceContainer,
elevation: SpacingTokens.elevationMd,
shadowColor: ColorTokens.shadow,
surfaceTintColor: ColorTokens.surfaceContainer,
indicatorColor: ColorTokens.primaryContainer,
labelTextStyle: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return TypographyTokens.navigationLabelSelected;
}
return TypographyTokens.navigationLabel;
}),
);
/// Configuration des dialogues
static final DialogTheme _dialogTheme = DialogTheme(
backgroundColor: ColorTokens.surfaceContainer,
elevation: SpacingTokens.elevationLg,
shadowColor: ColorTokens.shadow,
surfaceTintColor: ColorTokens.surfaceContainer,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
),
titleTextStyle: TypographyTokens.headlineSmall,
contentTextStyle: TypographyTokens.bodyMedium,
);
/// Configuration des snackbars (fixed pour éviter "Floating SnackBar off screen" avec bottomNavigationBar)
static final SnackBarThemeData _snackBarTheme = SnackBarThemeData(
backgroundColor: ColorTokens.onSurface,
contentTextStyle: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.surface,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
behavior: SnackBarBehavior.fixed,
);
/// Configuration des puces
static final ChipThemeData _chipTheme = ChipThemeData(
backgroundColor: ColorTokens.surfaceVariant,
selectedColor: ColorTokens.primaryContainer,
labelStyle: TypographyTokens.labelMedium,
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.md,
vertical: SpacingTokens.sm,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
),
);
/// Configuration des éléments de liste
static const ListTileThemeData _listTileTheme = ListTileThemeData(
contentPadding: EdgeInsets.symmetric(
horizontal: SpacingTokens.xl,
vertical: SpacingTokens.md,
),
titleTextStyle: TypographyTokens.titleMedium,
subtitleTextStyle: TypographyTokens.bodyMedium,
leadingAndTrailingTextStyle: TypographyTokens.labelMedium,
minVerticalPadding: SpacingTokens.md,
);
/// Configuration des onglets
static final TabBarTheme _tabBarTheme = TabBarTheme(
labelColor: ColorTokens.primary,
unselectedLabelColor: ColorTokens.onSurfaceVariant,
labelStyle: TypographyTokens.titleSmall,
unselectedLabelStyle: TypographyTokens.titleSmall,
indicator: UnderlineTabIndicator(
borderSide: const BorderSide(
color: ColorTokens.primary,
width: 2.0,
),
borderRadius: BorderRadius.circular(SpacingTokens.radiusXs),
),
);
/// Configuration des dividers
static const DividerThemeData _dividerTheme = DividerThemeData(
color: ColorTokens.outline,
thickness: 1.0,
space: SpacingTokens.md,
);
/// Configuration des icônes
static const IconThemeData _iconTheme = IconThemeData(
color: ColorTokens.onSurfaceVariant,
size: 24.0,
);
/// Configuration des transitions de page
static const PageTransitionsTheme _pageTransitionsTheme = PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
);
/// Extensions personnalisées - Couleurs
static const CustomColors _customColors = CustomColors();
/// Extensions personnalisées - Espacements
static const CustomSpacing _customSpacing = CustomSpacing();
}
/// Extension de couleurs personnalisées
class CustomColors extends ThemeExtension<CustomColors> {
const CustomColors();
@override
CustomColors copyWith() => const CustomColors();
@override
CustomColors lerp(ThemeExtension<CustomColors>? other, double t) {
return const CustomColors();
}
}
/// Extension d'espacements personnalisés
class CustomSpacing extends ThemeExtension<CustomSpacing> {
const CustomSpacing();
@override
CustomSpacing copyWith() => const CustomSpacing();
@override
CustomSpacing lerp(ThemeExtension<CustomSpacing>? other, double t) {
return const CustomSpacing();
}
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
/// UnionFlow Mobile App - Couleurs Globales (Strict DRY)
/// Palette principale: Vert, Blanc (Jour), Noir (Nuit).
class AppColors {
// --- Branding ---
static const Color primaryGreen = Color(0xFF17BF63); // Vert vibrant style social (Fb/Tw) - Corrigé.
static const Color brandGreen = Color(0xFF2E7D32); // Vert professionnel et lisible
static const Color brandGreenLight = Color(0xFF4CAF50); // Vert d'accentuation
// --- Mode Jour (Light) ---
static const Color lightBackground = Color(0xFFFFFFFF); // Blanc pur
static const Color lightSurface = Color(0xFFF5F8FA); // Gris extrêmement léger pour séparer les cards
static const Color lightBorder = Color(0xFFE1E8ED); // Bordures style Twitter
// --- Mode Nuit (Dark OLED) ---
static const Color darkBackground = Color(0xFF000000); // Noir pur pour OLED
static const Color darkSurface = Color(0xFF15202B); // Gris sombre typique Twitter/Fb Dark
static const Color darkBorder = Color(0xFF38444D); // Bordure discrète sombre
// --- Texte ---
static const Color textPrimaryLight = Color(0xFF14171A); // Presque noir
static const Color textSecondaryLight = Color(0xFF657786); // Gris texte
static const Color textPrimaryDark = Color(0xFFE1E8ED); // Presque blanc
static const Color textSecondaryDark = Color(0xFF8899A6); // Gris clair nuit
// --- Sémantique (Succès, Erreur, Info) ---
static const Color error = Color(0xFFE0245E); // Rouge vif
static const Color success = Color(0xFF17BF63); // Vert validation
static const Color warning = Color(0xFFFFAD1F); // Orange
static const Color info = Color(0xFF1DA1F2); // Bleu info
// --- Utilitaires ---
static const Color transparent = Colors.transparent;
static const Color surface = lightSurface;
static const Color background = lightBackground;
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
/// UnionFlow Mobile App - Typographie Globale (Ultra Minimaliste)
/// RÈGLE : AUCUN gros titre. Tailles limitées entre 10px et 14px pour maximiser l'information.
class AppTypography {
static const String _fontFamily = 'Roboto'; // Peut être changé pour 'Inter' si ajouté au pubspec.yaml
// --- Titres (Max 14px) ---
static const TextStyle headerSmall = TextStyle(
fontFamily: _fontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w700, // Bold
letterSpacing: -0.2,
);
// --- Corps de texte (Max 12px) ---
static const TextStyle bodyTextSmall = TextStyle(
fontFamily: _fontFamily,
fontSize: 12.0,
fontWeight: FontWeight.w400, // Regular
height: 1.4,
);
// --- Boutons et Actions (Max 13px) ---
static const TextStyle actionText = TextStyle(
fontFamily: _fontFamily,
fontSize: 13.0,
fontWeight: FontWeight.w600, // SemiBold
letterSpacing: 0.1,
);
// --- Sous-titres, dates, labels (Max 11px) ---
static const TextStyle subtitleSmall = TextStyle(
fontFamily: _fontFamily,
fontSize: 11.0,
fontWeight: FontWeight.w300, // Light
letterSpacing: 0.2,
);
// --- Badges, Piles, Métriques très denses (Max 10px) ---
static const TextStyle badgeText = TextStyle(
fontFamily: _fontFamily,
fontSize: 10.0,
fontWeight: FontWeight.w500, // Medium
);
}

View File

@@ -0,0 +1,197 @@
/// Design Tokens - Couleurs UnionFlow
///
/// Palette de couleurs Bleu Roi + Bleu Pétrole
/// Inspirée des tendances UI/UX 2024-2025
/// Basée sur les principes de Material Design 3
///
/// MODE JOUR: Bleu Roi (#4169E1) - Royal Blue
/// MODE NUIT: Bleu Pétrole (#2C5F6F) - Petroleum Blue
library color_tokens;
import 'package:flutter/material.dart';
/// Tokens de couleurs UnionFlow - Design System Unifié
class ColorTokens {
ColorTokens._();
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS PRIMAIRES - MODE JOUR (Bleu Roi)
// ═══════════════════════════════════════════════════════════════════════════
/// Couleur primaire principale - Bleu Roi (Royal Blue)
static const Color primary = Color(0xFF4169E1); // Bleu roi
static const Color primaryLight = Color(0xFF6B8EF5); // Bleu roi clair
static const Color primaryDark = Color(0xFF2952C8); // Bleu roi sombre
static const Color primaryContainer = Color(0xFFE3ECFF); // Container bleu roi
static const Color onPrimary = Color(0xFFFFFFFF); // Texte sur primaire (blanc)
static const Color onPrimaryContainer = Color(0xFF001A41); // Texte sur container
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS PRIMAIRES - MODE NUIT (Bleu Pétrole)
// ═══════════════════════════════════════════════════════════════════════════
/// Couleur primaire mode nuit - Bleu Pétrole
static const Color primaryDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
static const Color primaryLightDarkMode = Color(0xFF3D7A8C); // Bleu pétrole clair
static const Color primaryDarkDarkMode = Color(0xFF1B4D5C); // Bleu pétrole sombre
static const Color primaryContainerDarkMode = Color(0xFF1E3A44); // Container mode nuit
static const Color onPrimaryDarkMode = Color(0xFFE5E7EB); // Texte sur primaire (gris clair)
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS SECONDAIRES - Indigo Moderne
// ═══════════════════════════════════════════════════════════════════════════
static const Color secondary = Color(0xFF6366F1); // Indigo moderne
static const Color secondaryLight = Color(0xFF8B8FF6); // Indigo clair
static const Color secondaryDark = Color(0xFF4F46E5); // Indigo sombre
static const Color secondaryContainer = Color(0xFFE0E7FF); // Container indigo
static const Color onSecondary = Color(0xFFFFFFFF);
static const Color onSecondaryContainer = Color(0xFF1E1B3A);
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS TERTIAIRES - Vert Émeraude
// ═══════════════════════════════════════════════════════════════════════════
static const Color tertiary = Color(0xFF10B981); // Vert émeraude
static const Color tertiaryLight = Color(0xFF34D399); // Vert clair
static const Color tertiaryDark = Color(0xFF059669); // Vert sombre
static const Color tertiaryContainer = Color(0xFFD1FAE5); // Container vert
static const Color onTertiary = Color(0xFFFFFFFF);
static const Color onTertiaryContainer = Color(0xFF002114);
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS NEUTRES - MODE JOUR
// ═══════════════════════════════════════════════════════════════════════════
static const Color surface = Color(0xFFFFFFFF); // Surface principale (blanc)
static const Color surfaceVariant = Color(0xFFF8F9FA); // Surface variante (gris très clair)
static const Color surfaceContainer = Color(0xFFFFFFFF); // Container surface
static const Color surfaceContainerHigh = Color(0xFFF8F9FA); // Container élevé
static const Color surfaceContainerHighest = Color(0xFFE5E7EB); // Container max
static const Color background = Color(0xFFF8F9FA); // Background général
static const Color onSurface = Color(0xFF1F2937); // Texte principal (gris très foncé)
static const Color onSurfaceVariant = Color(0xFF6B7280); // Texte secondaire (gris moyen)
static const Color textSecondary = Color(0xFF6B7280); // Texte secondaire (alias)
static const Color outline = Color(0xFFD1D5DB); // Bordures
static const Color outlineVariant = Color(0xFFE5E7EB); // Bordures claires
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS NEUTRES - MODE NUIT
// ═══════════════════════════════════════════════════════════════════════════
static const Color surfaceDarkMode = Color(0xFF1E1E1E); // Surface principale (gris très sombre)
static const Color surfaceVariantDarkMode = Color(0xFF2C2C2C); // Surface variante
static const Color backgroundDarkMode = Color(0xFF121212); // Background général (noir profond)
static const Color onSurfaceDarkMode = Color(0xFFE5E7EB); // Texte principal (gris très clair)
static const Color onSurfaceVariantDarkMode = Color(0xFF9CA3AF); // Texte secondaire (gris moyen)
static const Color outlineDarkMode = Color(0xFF4B5563); // Bordures mode nuit
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS SÉMANTIQUES - États et feedback
// ═══════════════════════════════════════════════════════════════════════════
/// Couleurs de succès
static const Color success = Color(0xFF10B981); // Vert succès
static const Color successLight = Color(0xFF34D399); // Vert clair
static const Color successDark = Color(0xFF059669); // Vert sombre
static const Color successContainer = Color(0xFFECFDF5); // Container succès
static const Color onSuccess = Color(0xFFFFFFFF);
static const Color onSuccessContainer = Color(0xFF002114);
/// Couleurs d'erreur
static const Color error = Color(0xFFDC2626); // Rouge erreur
static const Color errorLight = Color(0xFFEF4444); // Rouge clair
static const Color errorDark = Color(0xFFB91C1C); // Rouge sombre
static const Color errorContainer = Color(0xFFFEF2F2); // Container erreur
static const Color onError = Color(0xFFFFFFFF);
static const Color onErrorContainer = Color(0xFF410002);
/// Couleurs d'avertissement
static const Color warning = Color(0xFFF59E0B); // Orange avertissement
static const Color warningLight = Color(0xFFFBBF24); // Orange clair
static const Color warningDark = Color(0xFFD97706); // Orange sombre
static const Color warningContainer = Color(0xFFFEF3C7); // Container avertissement
static const Color onWarning = Color(0xFFFFFFFF);
static const Color onWarningContainer = Color(0xFF2D1B00);
/// Couleurs d'information
static const Color info = Color(0xFF0EA5E9); // Bleu info
static const Color infoLight = Color(0xFF38BDF8); // Bleu clair
static const Color infoDark = Color(0xFF0284C7); // Bleu sombre
static const Color infoContainer = Color(0xFFE0F2FE); // Container info
static const Color onInfo = Color(0xFFFFFFFF);
static const Color onInfoContainer = Color(0xFF001D36);
// ═══════════════════════════════════════════════════════════════════════════
// COULEURS SPÉCIALISÉES - Interface avancée
// ═══════════════════════════════════════════════════════════════════════════
/// Couleurs de navigation - Mode Jour
static const Color navigationBackground = Color(0xFFFFFFFF);
static const Color navigationSelected = Color(0xFF4169E1); // Bleu roi
static const Color navigationUnselected = Color(0xFF6B7280);
static const Color navigationIndicator = Color(0xFF4169E1); // Bleu roi
/// Couleurs de navigation - Mode Nuit
static const Color navigationBackgroundDarkMode = Color(0xFF1E1E1E);
static const Color navigationSelectedDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
static const Color navigationUnselectedDarkMode = Color(0xFF9CA3AF);
static const Color navigationIndicatorDarkMode = Color(0xFF2C5F6F); // Bleu pétrole
/// Couleurs d'élévation et ombres
static const Color shadow = Color(0x1A000000); // Ombre légère
static const Color shadowMedium = Color(0x33000000); // Ombre moyenne
static const Color shadowHigh = Color(0x4D000000); // Ombre forte
/// Couleurs de glassmorphism (tendance 2024-2025)
static const Color glassBackground = Color(0x80FFFFFF); // Fond verre
static const Color glassBorder = Color(0x33FFFFFF); // Bordure verre
static const Color glassOverlay = Color(0x0DFFFFFF); // Overlay verre
/// Couleurs de gradient - Mode Jour (Bleu Roi)
static const List<Color> primaryGradient = [
Color(0xFF4169E1), // Bleu roi
Color(0xFF6B8EF5), // Bleu roi clair
];
/// Couleurs de gradient - Mode Nuit (Bleu Pétrole)
static const List<Color> primaryGradientDarkMode = [
Color(0xFF2C5F6F), // Bleu pétrole
Color(0xFF3D7A8C), // Bleu pétrole clair
];
static const List<Color> secondaryGradient = [
Color(0xFF6366F1), // Indigo
Color(0xFF8B8FF6), // Indigo clair
];
static const List<Color> successGradient = [
Color(0xFF10B981), // Vert émeraude
Color(0xFF34D399), // Vert clair
];
// ═══════════════════════════════════════════════════════════════════════════
// MÉTHODES UTILITAIRES
// ═══════════════════════════════════════════════════════════════════════════
/// Obtient une couleur avec opacité
static Color withOpacity(Color color, double opacity) {
return color.withOpacity(opacity);
}
/// Obtient une couleur plus claire
static Color lighten(Color color, [double amount = 0.1]) {
final hsl = HSLColor.fromColor(color);
final lightness = (hsl.lightness + amount).clamp(0.0, 1.0);
return hsl.withLightness(lightness).toColor();
}
/// Obtient une couleur plus sombre
static Color darken(Color color, [double amount = 0.1]) {
final hsl = HSLColor.fromColor(color);
final lightness = (hsl.lightness - amount).clamp(0.0, 1.0);
return hsl.withLightness(lightness).toColor();
}
}

View File

@@ -0,0 +1,23 @@
/// Tokens de rayon pour le design system
/// Définit les rayons de bordure standardisés de l'application
library radius_tokens;
/// Tokens de rayon
class RadiusTokens {
RadiusTokens._();
/// Small - 4px
static const double sm = 4.0;
/// Medium - 8px
static const double md = 8.0;
/// Large - 12px
static const double lg = 12.0;
/// Extra large - 16px
static const double xl = 16.0;
/// Round - 50px
static const double round = 50.0;
}

View File

@@ -0,0 +1,150 @@
/// Tokens d'ombres pour le design system
/// Définit les ombres standardisées de l'application
library shadow_tokens;
import 'package:flutter/material.dart';
import 'color_tokens.dart';
/// Tokens d'ombres standardisés
///
/// Utilisation cohérente des ombres dans toute l'application.
/// Basé sur les principes de Material Design 3.
class ShadowTokens {
ShadowTokens._();
// ═══════════════════════════════════════════════════════════════════════════
// OMBRES STANDARDS
// ═══════════════════════════════════════════════════════════════════════════
/// Ombre minimale - Pour éléments subtils
static final List<BoxShadow> xs = [
const BoxShadow(
color: ColorTokens.shadow,
blurRadius: 4,
offset: Offset(0, 1),
),
];
/// Ombre petite - Pour cards et boutons
static final List<BoxShadow> sm = [
const BoxShadow(
color: ColorTokens.shadow,
blurRadius: 8,
offset: Offset(0, 2),
),
];
/// Ombre moyenne - Pour cards importantes
static final List<BoxShadow> md = [
const BoxShadow(
color: ColorTokens.shadow,
blurRadius: 12,
offset: Offset(0, 4),
),
];
/// Ombre large - Pour modals et dialogs
static final List<BoxShadow> lg = [
const BoxShadow(
color: ColorTokens.shadowMedium,
blurRadius: 16,
offset: Offset(0, 6),
),
];
/// Ombre très large - Pour éléments flottants
static final List<BoxShadow> xl = [
const BoxShadow(
color: ColorTokens.shadowMedium,
blurRadius: 24,
offset: Offset(0, 8),
),
];
/// Ombre extra large - Pour éléments héroïques
static final List<BoxShadow> xxl = [
const BoxShadow(
color: ColorTokens.shadowHigh,
blurRadius: 32,
offset: Offset(0, 12),
spreadRadius: -4,
),
];
// ═══════════════════════════════════════════════════════════════════════════
// OMBRES COLORÉES
// ═══════════════════════════════════════════════════════════════════════════
/// Ombre primaire - Pour éléments avec couleur primaire
static final List<BoxShadow> primary = [
BoxShadow(
color: ColorTokens.primary.withOpacity(0.15),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
/// Ombre secondaire - Pour éléments avec couleur secondaire
static final List<BoxShadow> secondary = [
BoxShadow(
color: ColorTokens.secondary.withOpacity(0.15),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
/// Ombre success - Pour éléments de succès
static final List<BoxShadow> success = [
BoxShadow(
color: ColorTokens.success.withOpacity(0.15),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
/// Ombre error - Pour éléments d'erreur
static final List<BoxShadow> error = [
BoxShadow(
color: ColorTokens.error.withOpacity(0.15),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
/// Ombre warning - Pour éléments d'avertissement
static final List<BoxShadow> warning = [
BoxShadow(
color: ColorTokens.warning.withOpacity(0.15),
blurRadius: 16,
offset: const Offset(0, 4),
),
];
// ═══════════════════════════════════════════════════════════════════════════
// OMBRES SPÉCIALES
// ═══════════════════════════════════════════════════════════════════════════
/// Ombre interne - Pour effets enfoncés
static final List<BoxShadow> inner = [
const BoxShadow(
color: ColorTokens.shadow,
blurRadius: 4,
offset: Offset(0, 2),
spreadRadius: -2,
),
];
/// Ombre diffuse - Pour glassmorphism
static final List<BoxShadow> diffuse = [
BoxShadow(
color: ColorTokens.shadow.withOpacity(0.05),
blurRadius: 20,
offset: const Offset(0, 4),
spreadRadius: 2,
),
];
/// Pas d'ombre
static const List<BoxShadow> none = [];
}

View File

@@ -0,0 +1,194 @@
/// Design Tokens - Espacements
///
/// Système d'espacement cohérent basé sur une grille de 4px
/// Optimisé pour la lisibilité et l'harmonie visuelle
library spacing_tokens;
/// Tokens d'espacement - Système de grille moderne
class SpacingTokens {
SpacingTokens._();
// ═══════════════════════════════════════════════════════════════════════════
// ESPACEMENT DE BASE - Grille 4px
// ═══════════════════════════════════════════════════════════════════════════
/// Unité de base (4px) - Fondation du système
static const double baseUnit = 4.0;
/// Espacement minimal (2px) - Détails fins
static const double xs = baseUnit * 0.5; // 2px
/// Espacement très petit (4px) - Éléments adjacents
static const double sm = baseUnit * 1; // 4px
/// Espacement petit (8px) - Espacement interne léger
static const double md = baseUnit * 2; // 8px
/// Espacement moyen (12px) - Espacement standard
static const double lg = baseUnit * 3; // 12px
/// Espacement large (16px) - Séparation de composants
static const double xl = baseUnit * 4; // 16px
/// Espacement très large (20px) - Séparation importante
static const double xxl = baseUnit * 5; // 20px
/// Espacement extra large (24px) - Sections principales
static const double xxxl = baseUnit * 6; // 24px
/// Espacement massif (32px) - Séparation majeure
static const double huge = baseUnit * 8; // 32px
/// Espacement géant (48px) - Espacement héroïque
static const double giant = baseUnit * 12; // 48px
// ═══════════════════════════════════════════════════════════════════════════
// ESPACEMENTS SPÉCIALISÉS - Composants spécifiques
// ═══════════════════════════════════════════════════════════════════════════
/// Padding des conteneurs
static const double containerPaddingSmall = lg; // 12px
static const double containerPaddingMedium = xl; // 16px
static const double containerPaddingLarge = xxxl; // 24px
/// Marges des cartes
static const double cardMargin = xl; // 16px
static const double cardPadding = xl; // 16px
static const double cardPaddingLarge = xxxl; // 24px
/// Espacement des listes
static const double listItemSpacing = md; // 8px
static const double listSectionSpacing = xxxl; // 24px
/// Espacement des boutons
static const double buttonPaddingHorizontal = xl; // 16px
static const double buttonPaddingVertical = lg; // 12px
static const double buttonSpacing = md; // 8px
/// Espacement des formulaires
static const double formFieldSpacing = xl; // 16px
static const double formSectionSpacing = xxxl; // 24px
static const double formPadding = xl; // 16px
/// Espacement de navigation
static const double navigationPadding = xl; // 16px
static const double navigationItemSpacing = md; // 8px
static const double navigationSectionSpacing = xxxl; // 24px
/// Espacement des en-têtes
static const double headerPadding = xl; // 16px
static const double headerHeight = 56.0; // Hauteur standard
static const double headerElevation = 4.0; // Élévation
/// Espacement des onglets
static const double tabPadding = xl; // 16px
static const double tabHeight = 48.0; // Hauteur standard
/// Espacement des dialogues
static const double dialogPadding = xxxl; // 24px
static const double dialogMargin = xl; // 16px
/// Espacement des snackbars
static const double snackbarMargin = xl; // 16px
static const double snackbarPadding = xl; // 16px
// ═══════════════════════════════════════════════════════════════════════════
// RAYONS DE BORDURE - Système cohérent
// ═══════════════════════════════════════════════════════════════════════════
/// Rayon minimal (2px) - Détails subtils
static const double radiusXs = 2.0;
/// Rayon petit (4px) - Éléments fins
static const double radiusSm = 4.0;
/// Rayon moyen (8px) - Standard
static const double radiusMd = 8.0;
/// Rayon large (12px) - Cartes et composants
static const double radiusLg = 12.0;
/// Rayon très large (16px) - Conteneurs principaux
static const double radiusXl = 16.0;
/// Rayon extra large (20px) - Éléments héroïques
static const double radiusXxl = 20.0;
/// Rayon circulaire (999px) - Boutons ronds
static const double radiusCircular = 999.0;
// ═══════════════════════════════════════════════════════════════════════════
// ÉLÉVATIONS - Système d'ombres
// ═══════════════════════════════════════════════════════════════════════════
/// Élévation minimale
static const double elevationXs = 1.0;
/// Élévation petite
static const double elevationSm = 2.0;
/// Élévation moyenne
static const double elevationMd = 4.0;
/// Élévation large
static const double elevationLg = 8.0;
/// Élévation très large
static const double elevationXl = 12.0;
/// Élévation maximale
static const double elevationMax = 24.0;
// ═══════════════════════════════════════════════════════════════════════════
// DIMENSIONS FIXES - Composants standardisés
// ═══════════════════════════════════════════════════════════════════════════
/// Hauteurs de boutons
static const double buttonHeightSmall = 32.0;
static const double buttonHeightMedium = 40.0;
static const double buttonHeightLarge = 48.0;
/// Hauteurs d'éléments de liste
static const double listItemHeightSmall = 48.0;
static const double listItemHeightMedium = 56.0;
static const double listItemHeightLarge = 72.0;
/// Largeurs minimales
static const double minTouchTarget = 44.0; // Cible tactile minimale
static const double minButtonWidth = 64.0; // Largeur minimale bouton
/// Largeurs maximales
static const double maxContentWidth = 600.0; // Largeur max contenu
static const double maxDialogWidth = 400.0; // Largeur max dialogue
// ═══════════════════════════════════════════════════════════════════════════
// MÉTHODES UTILITAIRES
// ═══════════════════════════════════════════════════════════════════════════
/// Calcule un espacement basé sur l'unité de base
static double spacing(double multiplier) {
return baseUnit * multiplier;
}
/// Obtient un espacement responsive basé sur la largeur d'écran
static double responsiveSpacing(double screenWidth) {
if (screenWidth < 600) {
return xl; // Mobile
} else if (screenWidth < 1200) {
return xxxl; // Tablette
} else {
return huge; // Desktop
}
}
/// Obtient un padding responsive
static double responsivePadding(double screenWidth) {
if (screenWidth < 600) {
return xl; // 16px mobile
} else if (screenWidth < 1200) {
return xxxl; // 24px tablette
} else {
return huge; // 32px desktop
}
}
}

View File

@@ -0,0 +1,296 @@
/// Design Tokens - Typographie
///
/// Système typographique sophistiqué basé sur les tendances 2024-2025
/// Hiérarchie claire et lisibilité optimale pour applications professionnelles
library typography_tokens;
import 'package:flutter/material.dart';
import 'color_tokens.dart';
/// Tokens typographiques - Système de texte moderne
class TypographyTokens {
TypographyTokens._();
// ═══════════════════════════════════════════════════════════════════════════
// FAMILLES DE POLICES
// ═══════════════════════════════════════════════════════════════════════════
/// Police principale - Inter (moderne et lisible)
static const String primaryFontFamily = 'Inter';
/// Police secondaire - SF Pro Display (élégante)
static const String secondaryFontFamily = 'SF Pro Display';
/// Police monospace - JetBrains Mono (code et données)
static const String monospaceFontFamily = 'JetBrains Mono';
// ═══════════════════════════════════════════════════════════════════════════
// ÉCHELLE TYPOGRAPHIQUE - Basée sur Material Design 3
// ═══════════════════════════════════════════════════════════════════════════
/// Display - Titres principaux et héros
static const TextStyle displayLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 57.0,
fontWeight: FontWeight.w400,
letterSpacing: -0.25,
height: 1.12,
color: ColorTokens.onSurface,
);
static const TextStyle displayMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 45.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.0,
height: 1.16,
color: ColorTokens.onSurface,
);
static const TextStyle displaySmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 36.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.0,
height: 1.22,
color: ColorTokens.onSurface,
);
/// Headline - Titres de sections
static const TextStyle headlineLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 32.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.0,
height: 1.25,
color: ColorTokens.onSurface,
);
static const TextStyle headlineMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 28.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.0,
height: 1.29,
color: ColorTokens.onSurface,
);
static const TextStyle headlineSmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 24.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.0,
height: 1.33,
color: ColorTokens.onSurface,
);
/// Title - Titres de composants
static const TextStyle titleLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 22.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.0,
height: 1.27,
color: ColorTokens.onSurface,
);
static const TextStyle titleMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.15,
height: 1.50,
color: ColorTokens.onSurface,
);
static const TextStyle titleSmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: ColorTokens.onSurface,
);
/// Label - Étiquettes et boutons
static const TextStyle labelLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
height: 1.43,
color: ColorTokens.onSurface,
);
static const TextStyle labelMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 12.0,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
height: 1.33,
color: ColorTokens.onSurface,
);
static const TextStyle labelSmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 11.0,
fontWeight: FontWeight.w500,
letterSpacing: 0.5,
height: 1.45,
color: ColorTokens.onSurface,
);
/// Body - Texte de contenu
static const TextStyle bodyLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.50,
color: ColorTokens.onSurface,
);
static const TextStyle bodyMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
height: 1.43,
color: ColorTokens.onSurface,
);
static const TextStyle bodySmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 12.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.4,
height: 1.33,
color: ColorTokens.onSurface,
);
// ═══════════════════════════════════════════════════════════════════════════
// STYLES SPÉCIALISÉS - Interface UnionFlow
// ═══════════════════════════════════════════════════════════════════════════
/// Navigation - Styles pour menu et navigation
static const TextStyle navigationLabel = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
height: 1.43,
color: ColorTokens.navigationUnselected,
);
static const TextStyle navigationLabelSelected = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.43,
color: ColorTokens.navigationSelected,
);
/// Cartes et composants
static const TextStyle cardTitle = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 18.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.0,
height: 1.33,
color: ColorTokens.onSurface,
);
static const TextStyle cardSubtitle = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.25,
height: 1.43,
color: ColorTokens.onSurfaceVariant,
);
static const TextStyle cardValue = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 24.0,
fontWeight: FontWeight.w700,
letterSpacing: 0.0,
height: 1.25,
color: ColorTokens.primary,
);
/// Boutons
static const TextStyle buttonLarge = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.25,
color: ColorTokens.onPrimary,
);
static const TextStyle buttonMedium = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.1,
height: 1.29,
color: ColorTokens.onPrimary,
);
static const TextStyle buttonSmall = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 12.0,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
height: 1.33,
color: ColorTokens.onPrimary,
);
/// Formulaires
static const TextStyle inputLabel = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 14.0,
fontWeight: FontWeight.w500,
letterSpacing: 0.1,
height: 1.43,
color: ColorTokens.onSurfaceVariant,
);
static const TextStyle inputText = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.50,
color: ColorTokens.onSurface,
);
static const TextStyle inputHint = TextStyle(
fontFamily: primaryFontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
letterSpacing: 0.5,
height: 1.50,
color: ColorTokens.onSurfaceVariant,
);
// ═══════════════════════════════════════════════════════════════════════════
// MÉTHODES UTILITAIRES
// ═══════════════════════════════════════════════════════════════════════════
/// Applique une couleur à un style
static TextStyle withColor(TextStyle style, Color color) {
return style.copyWith(color: color);
}
/// Applique un poids de police
static TextStyle withWeight(TextStyle style, FontWeight weight) {
return style.copyWith(fontWeight: weight);
}
/// Applique une taille de police
static TextStyle withSize(TextStyle style, double size) {
return style.copyWith(fontSize: size);
}
}

View File

@@ -0,0 +1,185 @@
import 'package:flutter/material.dart';
/// UnionFlow Color System - Palette Signature
/// Inspirée des valeurs de solidarité, prospérité et modernité africaine
class UnionFlowColors {
UnionFlowColors._();
// ═══════════════════════════════════════════════════════════════
// COULEURS PRIMAIRES (Identité UnionFlow)
// ═══════════════════════════════════════════════════════════════
/// Vert UnionFlow - Symbole de croissance et prospérité
static const Color unionGreen = Color(0xFF0F6B4F);
static const Color unionGreenLight = Color(0xFF1F8A67);
static const Color unionGreenPale = Color(0xFFEEF5F2);
/// Or - Symbole de richesse et communauté
static const Color gold = Color(0xFFD4A017);
static const Color goldLight = Color(0xFFE8C568);
static const Color goldPale = Color(0xFFFFF9E6);
/// Indigo - Modernité et confiance
static const Color indigo = Color(0xFF1E2A44);
static const Color indigoLight = Color(0xFF3A4A6B);
// ═══════════════════════════════════════════════════════════════
// COULEURS SECONDAIRES (Accents culturels)
// ═══════════════════════════════════════════════════════════════
/// Terracotta - Chaleur et tradition
static const Color terracotta = Color(0xFFE07A5F);
static const Color terracottaLight = Color(0xFFF2AC99);
static const Color terracottaPale = Color(0xFFFFF3F0);
/// Ambre - Énergie et optimisme
static const Color amber = Color(0xFFF4A261);
static const Color amberLight = Color(0xFFF8C590);
/// Sable - Neutralité élégante
static const Color sand = Color(0xFFE9DCC9);
static const Color sandLight = Color(0xFFF5F0E8);
// ═══════════════════════════════════════════════════════════════
// COULEURS SÉMANTIQUES
// ═══════════════════════════════════════════════════════════════
/// Succès - Validation, confirmation
static const Color success = Color(0xFF22C55E);
static const Color successLight = Color(0xFF86EFAC);
static const Color successPale = Color(0xFFF0FDF4);
/// Attention - Avertissements
static const Color warning = Color(0xFFF59E0B);
static const Color warningLight = Color(0xFFFBBF24);
static const Color warningPale = Color(0xFFFEFCE8);
/// Erreur - Actions négatives
static const Color error = Color(0xFFEF4444);
static const Color errorLight = Color(0xFFFCA5A5);
static const Color errorPale = Color(0xFFFEF2F2);
/// Info - Informations neutres
static const Color info = Color(0xFF3B82F6);
static const Color infoLight = Color(0xFF93C5FD);
static const Color infoPale = Color(0xFFEFF6FF);
// ═══════════════════════════════════════════════════════════════
// COULEURS DE SURFACE
// ═══════════════════════════════════════════════════════════════
/// Background principal
static const Color background = Color(0xFFF7F9FA);
/// Surface des cards
static const Color surface = Color(0xFFFFFFFF);
/// Surface variant (légèrement teintée)
static const Color surfaceVariant = Color(0xFFF5F7F8);
/// Surface avec effet glassmorphism
static const Color surfaceGlass = Color(0xFFFAFBFC);
// ═══════════════════════════════════════════════════════════════
// TEXTE & BORDURES
// ═══════════════════════════════════════════════════════════════
/// Texte principal
static const Color textPrimary = Color(0xFF111827);
/// Texte secondaire
static const Color textSecondary = Color(0xFF6B7280);
/// Texte tertiaire
static const Color textTertiary = Color(0xFF9CA3AF);
/// Bordures subtiles
static const Color border = Color(0xFFE5E7EB);
/// Bordures moyennes
static const Color borderMedium = Color(0xFFD1D5DB);
/// Bordures fortes
static const Color borderStrong = Color(0xFF9CA3AF);
// ═══════════════════════════════════════════════════════════════
// GRADIENTS SIGNATURE
// ═══════════════════════════════════════════════════════════════
/// Gradient principal (Vert → Or)
static const LinearGradient primaryGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [unionGreen, unionGreenLight],
);
/// Gradient chaleureux (Terracotta → Ambre)
static const LinearGradient warmGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [terracotta, amber],
);
/// Gradient or (Gold → Gold Light)
static const LinearGradient goldGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [gold, goldLight],
);
/// Gradient subtil pour backgrounds
static const LinearGradient subtleGradient = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFFFAFBFC), Color(0xFFF7F9FA)],
);
// ═══════════════════════════════════════════════════════════════
// OMBRES SIGNATURE
// ═══════════════════════════════════════════════════════════════
/// Ombre douce (cards, buttons)
static List<BoxShadow> get softShadow => [
BoxShadow(
color: const Color(0xFF000000).withOpacity(0.08),
blurRadius: 24,
offset: const Offset(0, 8),
),
];
/// Ombre moyenne (modals, floating elements)
static List<BoxShadow> get mediumShadow => [
BoxShadow(
color: const Color(0xFF000000).withOpacity(0.12),
blurRadius: 32,
offset: const Offset(0, 12),
),
];
/// Ombre forte (dialogs, overlays)
static List<BoxShadow> get strongShadow => [
BoxShadow(
color: const Color(0xFF000000).withOpacity(0.16),
blurRadius: 48,
offset: const Offset(0, 16),
),
];
/// Ombre colorée verte (pour CTAs)
static List<BoxShadow> get greenGlowShadow => [
BoxShadow(
color: unionGreen.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 8),
),
];
/// Ombre colorée dorée (pour éléments premium)
static List<BoxShadow> get goldGlowShadow => [
BoxShadow(
color: gold.withOpacity(0.25),
blurRadius: 20,
offset: const Offset(0, 8),
),
];
}

View File

@@ -0,0 +1,107 @@
library unionflow_design_system;
// ═══════════════════════════════════════════════════════════════════════════
// IMPORTS de base pour le Design System
// ═══════════════════════════════════════════════════════════════════════════
import 'package:flutter/material.dart';
import 'tokens/app_colors.dart';
import 'tokens/app_typography.dart';
import 'tokens/spacing_tokens.dart';
// ═══════════════════════════════════════════════════════════════════════════
// EXPORTS - Point d'entrée unique (DRY)
// ═══════════════════════════════════════════════════════════════════════════
export 'tokens/app_colors.dart';
export 'tokens/app_typography.dart';
export 'tokens/spacing_tokens.dart';
export 'theme/app_theme.dart';
export 'components/components.dart';
// ═══════════════════════════════════════════════════════════════════════════
// COMPATIBILITÉ - Shims pour les anciens tokens (Migration progressive)
// ═══════════════════════════════════════════════════════════════════════════
/// Shim de compatibilité pour ColorTokens
class ColorTokens {
static const Color primary = AppColors.primaryGreen;
static const Color primaryContainer = AppColors.lightSurface;
static const Color onPrimary = Colors.white;
static const Color onPrimaryContainer = AppColors.textPrimaryLight;
static const Color secondary = AppColors.brandGreen;
static const Color secondaryContainer = AppColors.lightSurface;
static const Color onSecondary = Colors.white;
static const Color tertiary = AppColors.brandGreenLight;
static const Color tertiaryContainer = AppColors.lightSurface;
static const Color onTertiary = Colors.white;
static const Color surface = AppColors.lightSurface;
static const Color surfaceVariant = AppColors.lightSurface;
static const Color background = AppColors.lightBackground;
static const Color onSurface = AppColors.textPrimaryLight;
static const Color onSurfaceVariant = AppColors.textSecondaryLight;
static const Color outline = AppColors.lightBorder;
static const Color outlineVariant = AppColors.lightBorder;
static const Color error = AppColors.error;
static const Color onError = Colors.white;
static const Color success = AppColors.success;
static const Color onSuccess = Colors.white;
static const Color info = Color(0xFF2196F3);
static const Color warning = Color(0xFFFFC107);
static const Color shadow = Color(0x1A000000);
static const List<Color> primaryGradient = [
AppColors.primaryGreen,
AppColors.brandGreenLight,
];
}
/// Shim de compatibilité pour ShadowTokens
class ShadowTokens {
static const List<BoxShadow> sm = [
BoxShadow(
color: Color(0x1A000000),
blurRadius: 4,
offset: Offset(0, 2),
),
];
static const List<BoxShadow> md = [
BoxShadow(
color: Color(0x26000000),
blurRadius: 8,
offset: Offset(0, 4),
),
];
static const List<BoxShadow> primary = md;
}
/// Shim de compatibilité pour RadiusTokens
class RadiusTokens {
static const double sm = SpacingTokens.radiusSm;
static const double md = SpacingTokens.radiusMd;
static const double lg = SpacingTokens.radiusLg;
static const double xl = SpacingTokens.radiusXl;
static const double circular = SpacingTokens.radiusCircular;
static const double round = SpacingTokens.radiusCircular; // Ajouté pour compatibilité
}
/// Shim de compatibilité pour TypographyTokens
class TypographyTokens {
static const TextStyle displayLarge = AppTypography.headerSmall;
static const TextStyle displayMedium = AppTypography.headerSmall;
static const TextStyle displaySmall = AppTypography.headerSmall;
static const TextStyle headlineLarge = AppTypography.headerSmall;
static const TextStyle headlineMedium = AppTypography.headerSmall;
static const TextStyle headlineSmall = AppTypography.headerSmall;
static const TextStyle titleLarge = AppTypography.headerSmall;
static const TextStyle titleMedium = AppTypography.headerSmall;
static const TextStyle titleSmall = AppTypography.headerSmall;
static const TextStyle bodyLarge = AppTypography.bodyTextSmall;
static const TextStyle bodyMedium = AppTypography.bodyTextSmall;
static const TextStyle bodySmall = AppTypography.subtitleSmall;
static const TextStyle labelLarge = AppTypography.actionText;
static const TextStyle labelMedium = AppTypography.badgeText;
static const TextStyle labelSmall = AppTypography.badgeText;
static const TextStyle buttonLarge = AppTypography.actionText;
static const TextStyle cardValue = AppTypography.headerSmall;
}

View File

@@ -0,0 +1,28 @@
/// UnionFlow Design System V2 - Design Signature Original
/// Export centralisé de tous les composants et tokens du nouveau design system
library unionflow_design_v2;
// ═══════════════════════════════════════════════════════════════
// TOKENS
// ═══════════════════════════════════════════════════════════════
export 'tokens/unionflow_colors.dart';
// ═══════════════════════════════════════════════════════════════
// COMPOSANTS SIGNATURE
// ═══════════════════════════════════════════════════════════════
export 'components/union_balance_card.dart';
export 'components/union_progress_card.dart';
export 'components/union_action_button.dart';
export 'components/union_transaction_tile.dart';
export 'components/union_line_chart.dart';
export 'components/union_pie_chart.dart';
export 'components/union_stat_widget.dart';
export 'components/animated_fade_in.dart';
export 'components/animated_slide_in.dart';
export 'components/union_glass_card.dart';
export 'components/african_pattern_background.dart';
export 'components/union_unified_account_card.dart';
export 'components/union_period_filter.dart';
export 'components/union_export_button.dart';
export 'components/union_notification_badge.dart';