feat(ui): dark mode adaptatif sur 15 pages/widgets restants

Pattern AppColors pair (isDark ternaries) appliqué sur :
- login_page : SnackBar error color Color(0xFFDC2626) → AppColors.error
  (gradient brand intentionnel non modifié)
- help_support : barre de recherche + ExpansionTile + chevrons → scheme adaptatif
- system_settings : état 'Accès réservé' + unselectedLabelColor TabBar
- epargne : date/description/boutons OutlinedButton foregroundColor adaptatifs
- conversation_tile, connected_recent_activities, connected_upcoming_events
- dashboard_notifications_widget
- budgets_list_page, pending_approvals_page, approve/reject_dialog
- create_organization_page, edit_organization_page, about_page

Les couleurs sémantiques (error, success, warning, primary) restent inchangées.
Les blancs/gradients intentionnels (AppBars brand, logos payment) préservés.
This commit is contained in:
dahoud
2026-04-15 20:14:59 +00:00
parent b2f29922d3
commit ba779a7a40
15 changed files with 1002 additions and 1146 deletions

View File

@@ -6,6 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/widgets/core_card.dart'; import '../../../../shared/widgets/core_card.dart';
import '../../../../shared/widgets/info_badge.dart'; import '../../../../shared/widgets/info_badge.dart';
import '../../../../shared/widgets/powered_by_lions_dev.dart';
/// Page À propos - UnionFlow Mobile /// Page À propos - UnionFlow Mobile
@@ -40,7 +41,8 @@ class _AboutPageState extends State<AboutPage> {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar( appBar: UFAppBar(
title: 'À PROPOS', title: 'À propos',
moduleGradient: ModuleColors.systemeGradient,
actions: [ actions: [
IconButton( IconButton(
icon: const Icon(Icons.share_outlined, size: 20), icon: const Icon(Icons.share_outlined, size: 20),
@@ -48,7 +50,9 @@ class _AboutPageState extends State<AboutPage> {
), ),
], ],
), ),
body: SingleChildScrollView( body: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -75,27 +79,33 @@ class _AboutPageState extends State<AboutPage> {
// Support et contact // Support et contact
_buildSupportSection(), _buildSupportSection(),
const SizedBox(height: 16),
// Branding « Powered by Lions Dev » (logo adaptatif dark/light)
const Center(child: PoweredByLionsDev()),
const SizedBox(height: 80), const SizedBox(height: 80),
], ],
), ),
), ),
),
); );
} }
/// Header épuré /// Header épuré
Widget _buildHeader() { Widget _buildHeader() {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Center( return Center(
child: Column( child: Column(
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.1), color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: const Icon( child: const Icon(
Icons.account_balance, Icons.account_balance,
color: AppColors.primaryGreen, color: AppColors.primary,
size: 32, size: 32,
), ),
), ),
@@ -112,8 +122,8 @@ class _AboutPageState extends State<AboutPage> {
if (_packageInfo != null) if (_packageInfo != null)
InfoBadge( InfoBadge(
text: 'VERSION ${_packageInfo!.version}', text: 'VERSION ${_packageInfo!.version}',
backgroundColor: AppColors.lightSurface, backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
textColor: AppColors.textSecondaryLight, textColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
), ),
], ],
), ),
@@ -149,7 +159,7 @@ class _AboutPageState extends State<AboutPage> {
children: [ children: [
Text( Text(
label, label,
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
), ),
Flexible( Flexible(
child: Text( child: Text(
@@ -175,16 +185,17 @@ class _AboutPageState extends State<AboutPage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildTeamMember( _buildTeamMember(
'UnionFlow Team', 'Lions Dev',
'Architecture & Dev', 'Intégrateur de solutions digitales innovantes — lions.dev',
Icons.code, Icons.code,
AppColors.primaryGreen, AppColors.primary,
onTap: () => _launchUrl('https://www.lions.dev'),
), ),
_buildTeamMember( _buildTeamMember(
'Design System', 'UnionFlow',
'UI / UX Focus', 'Mouvement d\'entraide & solidarité',
Icons.design_services, Icons.account_balance,
AppColors.brandGreenLight, AppColors.primaryLight,
), ),
], ],
), ),
@@ -192,8 +203,8 @@ class _AboutPageState extends State<AboutPage> {
} }
/// Membre de l'équipe /// Membre de l'équipe
Widget _buildTeamMember(String name, String role, IconData icon, Color color) { Widget _buildTeamMember(String name, String role, IconData icon, Color color, {VoidCallback? onTap}) {
return Padding( final content = Padding(
padding: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.symmetric(vertical: 6),
child: Row( child: Row(
children: [ children: [
@@ -215,9 +226,17 @@ class _AboutPageState extends State<AboutPage> {
], ],
), ),
), ),
if (onTap != null)
Icon(Icons.open_in_new, color: color, size: 14),
], ],
), ),
); );
if (onTap == null) return content;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(8),
child: content,
);
} }
/// Section fonctionnalités /// Section fonctionnalités
@@ -231,8 +250,8 @@ class _AboutPageState extends State<AboutPage> {
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primaryGreen), _buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primary),
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.brandGreenLight), _buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.primaryLight),
_buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success), _buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success),
_buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning), _buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning),
], ],
@@ -284,6 +303,7 @@ class _AboutPageState extends State<AboutPage> {
/// Élément de lien /// Élément de lien
Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) { Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
@@ -291,7 +311,7 @@ class _AboutPageState extends State<AboutPage> {
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
child: Row( child: Row(
children: [ children: [
Icon(icon, color: AppColors.primaryGreen, size: 16), Icon(icon, color: AppColors.primary, size: 16),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
@@ -302,7 +322,7 @@ class _AboutPageState extends State<AboutPage> {
], ],
), ),
), ),
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14), Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14),
], ],
), ),
), ),
@@ -327,7 +347,7 @@ class _AboutPageState extends State<AboutPage> {
child: Column( child: Column(
children: [ children: [
Text('© 2024 UNIONFLOW', style: AppTypography.badgeText), Text('© 2024 UNIONFLOW', style: AppTypography.badgeText),
Text('Fait avec ❤️ pour les syndicats', style: AppTypography.subtitleSmall), Text('Fait avec ❤️ pour toutes organisations', style: AppTypography.subtitleSmall),
], ],
), ),
), ),
@@ -338,6 +358,7 @@ class _AboutPageState extends State<AboutPage> {
/// Élément de support /// Élément de support
Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) { Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
@@ -356,7 +377,7 @@ class _AboutPageState extends State<AboutPage> {
], ],
), ),
), ),
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14), Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14),
], ],
), ),
), ),
@@ -398,8 +419,8 @@ class _AboutPageState extends State<AboutPage> {
_launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile'); _launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Envoyer un email'), child: const Text('Envoyer un email'),
), ),
@@ -429,8 +450,8 @@ class _AboutPageState extends State<AboutPage> {
_launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile'); _launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Envoyer une suggestion'), child: const Text('Envoyer une suggestion'),
), ),
@@ -460,8 +481,8 @@ class _AboutPageState extends State<AboutPage> {
_launchStoreForRating(); _launchStoreForRating();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Évaluer maintenant'), child: const Text('Évaluer maintenant'),
), ),

View File

@@ -8,9 +8,17 @@ import 'package:url_launcher/url_launcher.dart';
import '../bloc/auth_bloc.dart'; import '../bloc/auth_bloc.dart';
import '../../../../core/config/environment.dart'; import '../../../../core/config/environment.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/widgets/powered_by_lions_dev.dart';
/// UnionFlow — Écran de connexion premium // ── Couleurs signature ────────────────────────────────────────────────────────
/// Gradient forêt + glassmorphism + animations + biométrie + remember me const _kGradTop = Color(0xFF1D4ED8);
const _kGradMid = Color(0xFF2563EB);
const _kGradBot = Color(0xFF7616E8);
const _kPrimaryBlue = Color(0xFF2563EB);
/// UnionFlow — Écran de connexion.
/// Gradient signature (#1D4ED8 → #2563EB → #7616E8) + glassmorphism.
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({super.key}); const LoginPage({super.key});
@@ -19,37 +27,33 @@ class LoginPage extends StatefulWidget {
} }
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin { class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
late final AnimationController _fadeController; late final AnimationController _fadeCtrl;
late final AnimationController _slideController; late final AnimationController _slideCtrl;
late final Animation<double> _fadeAnim; late final Animation<double> _fadeAnim;
late final Animation<Offset> _slideAnim; late final Animation<Offset> _slideAnim;
bool _biometricAvailable = false; bool _biometricAvailable = false;
final _localAuth = LocalAuthentication(); final _localAuth = LocalAuthentication();
static const _gradTop = Color(0xFF1B5E20);
static const _gradMid = Color(0xFF2E7D32);
static const _gradBot = Color(0xFF388E3C);
static const _primaryGreen = Color(0xFF2E7D32);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_fadeController = AnimationController(vsync: this, duration: const Duration(milliseconds: 900)); _fadeCtrl = AnimationController(
_slideController = AnimationController(vsync: this, duration: const Duration(milliseconds: 750)); vsync: this, duration: const Duration(milliseconds: 900));
_fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut); _slideCtrl = AnimationController(
vsync: this, duration: const Duration(milliseconds: 750));
_fadeAnim = CurvedAnimation(parent: _fadeCtrl, curve: Curves.easeOut);
_slideAnim = Tween<Offset>(begin: const Offset(0, 0.12), end: Offset.zero) _slideAnim = Tween<Offset>(begin: const Offset(0, 0.12), end: Offset.zero)
.animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic)); .animate(CurvedAnimation(parent: _slideCtrl, curve: Curves.easeOutCubic));
_fadeController.forward(); _fadeCtrl.forward();
_slideController.forward(); _slideCtrl.forward();
_checkBiometrics(); _checkBiometrics();
} }
@override @override
void dispose() { void dispose() {
_fadeController.dispose(); _fadeCtrl.dispose();
_slideController.dispose(); _slideCtrl.dispose();
super.dispose(); super.dispose();
} }
@@ -67,9 +71,7 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
localizedReason: 'Authentifiez-vous pour accéder à UnionFlow', localizedReason: 'Authentifiez-vous pour accéder à UnionFlow',
options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false), options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false),
); );
if (ok && mounted) { if (ok && mounted) context.read<AuthBloc>().add(const AuthStatusChecked());
context.read<AuthBloc>().add(const AuthStatusChecked());
}
} catch (_) {} } catch (_) {}
} }
@@ -81,54 +83,41 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
'&response_type=code&scope=openid&kc_action=reset_credentials', '&response_type=code&scope=openid&kc_action=reset_credentials',
); );
try { try {
if (await canLaunchUrl(url)) await launchUrl(url, mode: LaunchMode.externalApplication); if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
} catch (_) {} } catch (_) {}
} }
void _onLogin() { void _onAuthStateChanged(BuildContext context, AuthState state) {
context.read<AuthBloc>().add(const AuthLoginRequested()); if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: AppColors.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: BlocConsumer<AuthBloc, AuthState>( body: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) { listener: _onAuthStateChanged,
if (state is AuthError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: const Color(0xFFB71C1C),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
},
builder: (context, state) { builder: (context, state) {
final isLoading = state is AuthLoading; final isLoading = state is AuthLoading;
return Stack( return Stack(
children: [ children: [
// Gradient background const _GradientBackground(),
Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [_gradTop, _gradMid, _gradBot],
stops: [0.0, 0.55, 1.0],
),
),
),
// Subtle hexagon pattern overlay
const Positioned.fill(child: _HexPatternOverlay()), const Positioned.fill(child: _HexPatternOverlay()),
// Content
SafeArea( SafeArea(
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24), padding: const EdgeInsets.symmetric(
horizontal: 28, vertical: 24),
child: FadeTransition( child: FadeTransition(
opacity: _fadeAnim, opacity: _fadeAnim,
child: SlideTransition( child: SlideTransition(
@@ -136,9 +125,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_buildLogoSection(), const _LoginLogoSection(),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildGlassCard(isLoading), _LoginGlassCard(
isLoading: isLoading,
onLogin: () => context
.read<AuthBloc>()
.add(const AuthLoginRequested()),
onForgotPassword: _openForgotPassword,
biometricAvailable: _biometricAvailable,
onBiometric: _authenticateBiometric,
),
const SizedBox(height: 24),
// Branding « Powered by Lions Dev » — fond toujours dark
const PoweredByLionsDev(forceBrightness: Brightness.dark),
], ],
), ),
), ),
@@ -152,13 +152,43 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
), ),
); );
} }
}
Widget _buildLogoSection() { // ─────────────────────────────────────────────────────────────────────────────
// Widgets extraits
// ─────────────────────────────────────────────────────────────────────────────
class _GradientBackground extends StatelessWidget {
const _GradientBackground();
@override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [_kGradTop, _kGradMid, _kGradBot],
stops: [0.0, 0.55, 1.0],
),
),
child: SizedBox.expand(),
);
}
}
class _LoginLogoSection extends StatelessWidget {
const _LoginLogoSection();
@override
Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
CustomPaint( Image.asset(
size: const Size(48, 48), 'assets/images/unionflow-logo.png',
painter: _HexLogoMark(), width: 80,
height: 80,
fit: BoxFit.contain,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
@@ -175,7 +205,6 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
'Gérez votre organisation avec sérénité', 'Gérez votre organisation avec sérénité',
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w400,
color: Colors.white.withOpacity(0.78), color: Colors.white.withOpacity(0.78),
letterSpacing: 0.2, letterSpacing: 0.2,
), ),
@@ -184,19 +213,35 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
], ],
); );
} }
}
Widget _buildGlassCard(bool isLoading) { class _LoginGlassCard extends StatelessWidget {
const _LoginGlassCard({
required this.isLoading,
required this.onLogin,
required this.onForgotPassword,
required this.biometricAvailable,
required this.onBiometric,
});
final bool isLoading;
final VoidCallback onLogin;
final VoidCallback onForgotPassword;
final bool biometricAvailable;
final VoidCallback onBiometric;
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.11), color: Colors.white.withOpacity(0.11),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.22), color: Colors.white.withOpacity(0.22), width: 1.5),
width: 1.5,
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.18), color: AppColors.shadowStrong,
blurRadius: 40, blurRadius: 40,
offset: const Offset(0, 10), offset: const Offset(0, 10),
), ),
@@ -225,12 +270,10 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Forgot password
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: TextButton( child: TextButton(
onPressed: _openForgotPassword, onPressed: onForgotPassword,
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
minimumSize: Size.zero, minimumSize: Size.zero,
@@ -249,17 +292,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (isLoading)
// Login button const Center(
isLoading child: CircularProgressIndicator(
? const Center(child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5)) color: Colors.white, strokeWidth: 2.5),
: ElevatedButton( )
onPressed: _onLogin, else
ElevatedButton(
onPressed: onLogin,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.white, backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
foregroundColor: _primaryGreen, foregroundColor: _kPrimaryBlue,
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
elevation: 0, elevation: 0,
), ),
child: Text( child: Text(
@@ -267,25 +313,26 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 15.5, fontSize: 15.5,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
color: _primaryGreen, color: _kPrimaryBlue,
letterSpacing: 0.2, letterSpacing: 0.2,
), ),
), ),
), ),
if (biometricAvailable) ...[
// Biometric
if (_biometricAvailable) ...[
const SizedBox(height: 14), const SizedBox(height: 14),
Center( Center(
child: TextButton.icon( child: TextButton.icon(
onPressed: _authenticateBiometric, onPressed: onBiometric,
icon: const Icon(Icons.fingerprint_rounded, color: Colors.white60, size: 22), icon: const Icon(Icons.fingerprint_rounded,
color: Colors.white60, size: 22),
label: Text( label: Text(
'Connexion biométrique', 'Connexion biométrique',
style: GoogleFonts.roboto(fontSize: 12.5, color: Colors.white60), style: GoogleFonts.roboto(
fontSize: 12.5, color: Colors.white60),
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
), ),
), ),
), ),
@@ -300,14 +347,12 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
// Painters // Painters
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
/// Motif hexagonal en overlay sur le dégradé (opacité 4%)
class _HexPatternOverlay extends StatelessWidget { class _HexPatternOverlay extends StatelessWidget {
const _HexPatternOverlay(); const _HexPatternOverlay();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) =>
return CustomPaint(painter: _HexPatternPainter()); CustomPaint(painter: _HexPatternPainter());
}
} }
class _HexPatternPainter extends CustomPainter { class _HexPatternPainter extends CustomPainter {
@@ -324,8 +369,11 @@ class _HexPatternPainter extends CustomPainter {
for (double row = -1; row * vSpace < size.height + vSpace; row++) { for (double row = -1; row * vSpace < size.height + vSpace; row++) {
final offset = (row % 2 == 0) ? 0.0 : hSpace / 2; final offset = (row % 2 == 0) ? 0.0 : hSpace / 2;
for (double col = -1; col * hSpace - offset < size.width + hSpace; col++) { for (double col = -1;
_hexagon(canvas, paint, Offset(col * hSpace + offset, row * vSpace), r); col * hSpace - offset < size.width + hSpace;
col++) {
_hexagon(canvas, paint,
Offset(col * hSpace + offset, row * vSpace), r);
} }
} }
} }
@@ -334,8 +382,9 @@ class _HexPatternPainter extends CustomPainter {
final path = Path(); final path = Path();
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
final a = (i * 60 - 30) * math.pi / 180; final a = (i * 60 - 30) * math.pi / 180;
final p = Offset(center.dx + r * math.cos(a), center.dy + r * math.sin(a)); final p = Offset(
if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy); center.dx + r * math.cos(a), center.dy + r * math.sin(a));
i == 0 ? path.moveTo(p.dx, p.dy) : path.lineTo(p.dx, p.dy);
} }
path.close(); path.close();
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
@@ -344,57 +393,3 @@ class _HexPatternPainter extends CustomPainter {
@override @override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false; bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
} }
/// Logo hexagonal avec initiales "UF"
class _HexLogoMark extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final cx = size.width / 2;
final cy = size.height / 2;
final r = size.width / 2 - 2;
// Fond hexagonal blanc semi-transparent
final bgPaint = Paint()
..color = Colors.white.withOpacity(0.18)
..style = PaintingStyle.fill;
final borderPaint = Paint()
..color = Colors.white.withOpacity(0.6)
..style = PaintingStyle.stroke
..strokeWidth = 1.8;
final path = Path();
for (int i = 0; i < 6; i++) {
final a = (i * 60 - 30) * math.pi / 180;
final p = Offset(cx + r * math.cos(a), cy + r * math.sin(a));
if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy);
}
path.close();
canvas.drawPath(path, bgPaint);
canvas.drawPath(path, borderPaint);
// Lignes stylisées "UF" dessinées (plus propre qu'un TextPainter dans un painter)
final linePaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 2.8
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
// Lettre U
final uPath = Path()
..moveTo(cx - 13, cy - 10)
..lineTo(cx - 13, cy + 5)
..quadraticBezierTo(cx - 13, cy + 12, cx - 7, cy + 12)
..quadraticBezierTo(cx - 1, cy + 12, cx - 1, cy + 5)
..lineTo(cx - 1, cy - 10);
canvas.drawPath(uPath, linePaint);
// Lettre F
canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 3, cy + 12), linePaint);
canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 13, cy - 10), linePaint);
canvas.drawLine(Offset(cx + 3, cy + 1), Offset(cx + 11, cy + 1), linePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -1,13 +1,14 @@
/// Widget tuile de conversation /// Widget tuile de conversation v4
library conversation_tile; library conversation_tile;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../domain/entities/conversation.dart'; import '../../domain/entities/conversation.dart';
class ConversationTile extends StatelessWidget { class ConversationTile extends StatelessWidget {
final Conversation conversation; final ConversationSummary conversation;
final VoidCallback onTap; final VoidCallback onTap;
const ConversationTile({ const ConversationTile({
@@ -31,41 +32,55 @@ class ConversationTile extends StatelessWidget {
} }
} }
IconData _typeIcon() {
switch (conversation.typeConversation) {
case 'DIRECTE':
return Icons.person_outline;
case 'ROLE_CANAL':
return Icons.account_circle_outlined;
case 'GROUPE':
return Icons.group_outlined;
default:
return Icons.chat_bubble_outline;
}
}
String _apercuMessage() {
if (conversation.dernierMessageApercu == null) return 'Aucun message';
final type = conversation.dernierMessageType ?? 'TEXTE';
if (type == 'VOCAL') return '🎙️ Note vocale';
if (type == 'IMAGE') return '📷 Image';
return conversation.dernierMessageApercu!;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
child: Container( child: Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorTokens.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all( border: Border.all(
color: conversation.hasUnread color: conversation.hasUnread
? AppColors.primaryGreen.withOpacity(0.3) ? AppColors.primary.withOpacity(0.3)
: ColorTokens.outline, : ColorTokens.outline,
), ),
), ),
child: Row( child: Row(
children: [ children: [
// Avatar // Avatar type
CircleAvatar( Container(
radius: 24, width: 48,
backgroundColor: AppColors.primaryGreen.withOpacity(0.1), height: 48,
backgroundImage: conversation.avatarUrl != null decoration: BoxDecoration(
? NetworkImage(conversation.avatarUrl!) color: AppColors.primary.withOpacity(0.1),
: null, borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
child: conversation.avatarUrl == null
? Text(
conversation.name.isNotEmpty
? conversation.name[0].toUpperCase()
: '?',
style: AppTypography.actionText.copyWith(
color: AppColors.primaryGreen,
), ),
) child: Icon(_typeIcon(), color: AppColors.primary, size: 24),
: null,
), ),
const SizedBox(width: SpacingTokens.md), const SizedBox(width: SpacingTokens.md),
@@ -79,7 +94,7 @@ class ConversationTile extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
conversation.name, conversation.titre,
style: AppTypography.actionText.copyWith( style: AppTypography.actionText.copyWith(
fontWeight: conversation.hasUnread fontWeight: conversation.hasUnread
? FontWeight.bold ? FontWeight.bold
@@ -89,21 +104,20 @@ class ConversationTile extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
if (conversation.lastMessage != null) if (conversation.dernierMessageAt != null)
Text( Text(
_formatDate(conversation.lastMessage!.createdAt), _formatDate(conversation.dernierMessageAt!),
style: AppTypography.subtitleSmall.copyWith( style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
), ),
), ),
], ],
), ),
if (conversation.lastMessage != null) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
conversation.lastMessage!.content, _apercuMessage(),
style: AppTypography.bodyTextSmall.copyWith( style: AppTypography.bodyTextSmall.copyWith(
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
fontWeight: conversation.hasUnread fontWeight: conversation.hasUnread
? FontWeight.w600 ? FontWeight.w600
: FontWeight.normal, : FontWeight.normal,
@@ -112,7 +126,6 @@ class ConversationTile extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ],
],
), ),
), ),
@@ -120,16 +133,13 @@ class ConversationTile extends StatelessWidget {
if (conversation.hasUnread) ...[ if (conversation.hasUnread) ...[
const SizedBox(width: SpacingTokens.sm), const SizedBox(width: SpacingTokens.sm),
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryGreen, color: AppColors.primary,
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular), borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
), ),
child: Text( child: Text(
'${conversation.unreadCount}', '${conversation.nonLus}',
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -137,27 +147,6 @@ class ConversationTile extends StatelessWidget {
), ),
), ),
], ],
// Icônes statut
if (conversation.isPinned || conversation.isMuted) ...[
const SizedBox(width: SpacingTokens.sm),
Column(
children: [
if (conversation.isPinned)
Icon(
Icons.push_pin,
size: 16,
color: AppColors.textSecondaryLight,
),
if (conversation.isMuted)
Icon(
Icons.volume_off,
size: 16,
color: AppColors.textSecondaryLight,
),
],
),
],
], ],
), ),
), ),

View File

@@ -33,7 +33,7 @@ class ConnectedRecentActivities extends StatelessWidget {
BlocBuilder<DashboardBloc, DashboardState>( BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, state) { builder: (context, state) {
if (state is DashboardLoading) { if (state is DashboardLoading) {
return _buildLoadingList(); return _buildLoadingList(context);
} else if (state is DashboardLoaded || state is DashboardRefreshing) { } else if (state is DashboardLoaded || state is DashboardRefreshing) {
final data = state is DashboardLoaded final data = state is DashboardLoaded
? state.dashboardData ? state.dashboardData
@@ -42,7 +42,7 @@ class ConnectedRecentActivities extends StatelessWidget {
} else if (state is DashboardError) { } else if (state is DashboardError) {
return _buildErrorState(state.message); return _buildErrorState(state.message);
} }
return _buildEmptyState(); return _buildEmptyState(context);
}, },
), ),
], ],
@@ -55,7 +55,7 @@ class ConnectedRecentActivities extends StatelessWidget {
children: [ children: [
const Icon( const Icon(
Icons.history, Icons.history,
color: AppColors.primaryGreen, color: AppColors.primary,
size: 18, size: 18,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -71,7 +71,7 @@ class ConnectedRecentActivities extends StatelessWidget {
child: Text( child: Text(
'TOUT VOIR', 'TOUT VOIR',
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -82,7 +82,7 @@ class ConnectedRecentActivities extends StatelessWidget {
Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) { Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) {
if (activities.isEmpty) { if (activities.isEmpty) {
return _buildEmptyState(); return _buildEmptyState(context);
} }
final displayActivities = activities.take(maxItems).toList(); final displayActivities = activities.take(maxItems).toList();
@@ -142,7 +142,7 @@ class ConnectedRecentActivities extends StatelessWidget {
activity.userName, activity.userName,
style: AppTypography.subtitleSmall.copyWith( style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: AppColors.primaryGreen, color: AppColors.primary,
fontSize: 9, fontSize: 9,
), ),
), ),
@@ -157,10 +157,10 @@ class ConnectedRecentActivities extends StatelessWidget {
), ),
// Action button si disponible // Action button si disponible
if (activity.hasAction) if (activity.hasAction)
const Icon( Icon(
Icons.chevron_right, Icons.chevron_right,
size: 14, size: 14,
color: AppColors.textSecondaryLight, color: Theme.of(context).colorScheme.onSurfaceVariant,
), ),
], ],
), ),
@@ -189,25 +189,27 @@ class ConnectedRecentActivities extends StatelessWidget {
} }
} }
Widget _buildLoadingList() { Widget _buildLoadingList(BuildContext context) {
return Column( return Column(
children: List.generate(3, (index) => Column( children: List.generate(3, (index) => Column(
children: [ children: [
_buildLoadingItem(), _buildLoadingItem(context),
if (index < 2) const SizedBox(height: 12), if (index < 2) const SizedBox(height: 12),
], ],
)), )),
); );
} }
Widget _buildLoadingItem() { Widget _buildLoadingItem(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final borderColor = isDark ? AppColors.borderDark : AppColors.border;
return Row( return Row(
children: [ children: [
Container( Container(
width: 28, width: 28,
height: 28, height: 28,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBorder, color: borderColor,
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(14),
), ),
), ),
@@ -220,7 +222,7 @@ class ConnectedRecentActivities extends StatelessWidget {
height: 16, height: 16,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBorder, color: borderColor,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -229,7 +231,7 @@ class ConnectedRecentActivities extends StatelessWidget {
height: 12, height: 12,
width: 200, width: 200,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBorder.withOpacity(0.5), color: borderColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -238,7 +240,7 @@ class ConnectedRecentActivities extends StatelessWidget {
height: 12, height: 12,
width: 120, width: 120,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBorder.withOpacity(0.5), color: borderColor.withOpacity(0.5),
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
@@ -261,11 +263,12 @@ class ConnectedRecentActivities extends StatelessWidget {
); );
} }
Widget _buildEmptyState() { Widget _buildEmptyState(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Center( return Center(
child: Column( child: Column(
children: [ children: [
const Icon(Icons.history, color: AppColors.textSecondaryLight, size: 32), Icon(Icons.history, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall), const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall),
Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
@@ -291,20 +294,20 @@ class ConnectedRecentActivities extends StatelessWidget {
} }
} }
Color _getActivityColor(String type) { Color _getActivityColor(String type, {bool isDark = false}) {
switch (type.toLowerCase()) { switch (type.toLowerCase()) {
case 'member': case 'member':
return AppColors.success; return AppColors.success;
case 'event': case 'event':
return AppColors.info; return AppColors.info;
case 'contribution': case 'contribution':
return AppColors.brandGreen; return AppColors.primaryDark;
case 'organization': case 'organization':
return AppColors.primaryGreen; return AppColors.primary;
case 'system': case 'system':
return AppColors.warning; return AppColors.warning;
default: default:
return AppColors.textSecondaryLight; return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
} }
} }
} }

View File

@@ -37,7 +37,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
} else if (state is DashboardError) { } else if (state is DashboardError) {
return _buildErrorState(state.message); return _buildErrorState(state.message);
} }
return _buildEmptyState(); return _buildEmptyState(context);
}, },
), ),
], ],
@@ -50,7 +50,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
children: [ children: [
const Icon( const Icon(
Icons.event_outlined, Icons.event_outlined,
color: AppColors.primaryGreen, color: AppColors.primary,
size: 18, size: 18,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -66,7 +66,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
child: Text( child: Text(
'TOUT VOIR', 'TOUT VOIR',
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -77,7 +77,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) { Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) {
if (events.isEmpty) { if (events.isEmpty) {
return _buildEmptyState(); return _buildEmptyState(context);
} }
final displayEvents = events.take(maxItems).toList(); final displayEvents = events.take(maxItems).toList();
@@ -99,7 +99,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
} }
Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) { Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) {
final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primaryGreen); final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primary);
return CoreCard( return CoreCard(
backgroundColor: Theme.of(context).cardColor, backgroundColor: Theme.of(context).cardColor,
@@ -140,7 +140,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
const Icon(Icons.location_on_outlined, size: 10, color: AppColors.textSecondaryLight), Icon(Icons.location_on_outlined, size: 10, color: Theme.of(context).colorScheme.onSurfaceVariant),
const SizedBox(width: 4), const SizedBox(width: 4),
Expanded( Expanded(
child: Text( child: Text(
@@ -188,7 +188,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: event.fillPercentage, value: event.fillPercentage,
minHeight: 4, minHeight: 4,
backgroundColor: AppColors.lightBorder, backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.borderDark : AppColors.border,
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success), event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success),
), ),
@@ -230,11 +230,12 @@ class ConnectedUpcomingEvents extends StatelessWidget {
); );
} }
Widget _buildEmptyState() { Widget _buildEmptyState(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Center( return Center(
child: Column( child: Column(
children: [ children: [
const Icon(Icons.event_outlined, color: AppColors.textSecondaryLight, size: 32), Icon(Icons.event_outlined, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall), const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall),
Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)), Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),

View File

@@ -37,7 +37,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
} else if (state is DashboardError) { } else if (state is DashboardError) {
return _buildErrorNotifications(); return _buildErrorNotifications();
} }
return _buildEmptyNotifications(); return _buildEmptyNotifications(context);
}, },
), ),
], ],
@@ -49,7 +49,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.05), color: AppColors.primary.withOpacity(0.05),
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12), topLeft: Radius.circular(12),
topRight: Radius.circular(12), topRight: Radius.circular(12),
@@ -60,7 +60,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(6), padding: const EdgeInsets.all(6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryGreen, color: AppColors.primary,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
child: const Icon( child: const Icon(
@@ -74,7 +74,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
child: Text( child: Text(
'NOTIFICATIONS', 'NOTIFICATIONS',
style: AppTypography.subtitleSmall.copyWith( style: AppTypography.subtitleSmall.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.1, letterSpacing: 1.1,
), ),
@@ -121,23 +121,24 @@ class DashboardNotificationsWidget extends StatelessWidget {
final notifications = _generateNotifications(context, data); final notifications = _generateNotifications(context, data);
if (notifications.isEmpty) { if (notifications.isEmpty) {
return _buildEmptyNotifications(); return _buildEmptyNotifications(context);
} }
return Column( return Column(
children: notifications.take(maxNotifications).map((notification) { children: notifications.take(maxNotifications).map((notification) {
return _buildNotificationItem(notification); return _buildNotificationItem(context, notification);
}).toList(), }).toList(),
); );
} }
Widget _buildNotificationItem(DashboardNotification notification) { Widget _buildNotificationItem(BuildContext context, DashboardNotification notification) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide( bottom: BorderSide(
color: AppColors.lightBorder, color: isDark ? AppColors.borderDark : AppColors.border,
width: 1, width: 1,
), ),
), ),
@@ -199,7 +200,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
Text( Text(
notification.message, notification.message,
style: AppTypography.subtitleSmall.copyWith( style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
fontSize: 10, fontSize: 10,
), ),
), ),
@@ -209,7 +210,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
Text( Text(
notification.timeAgo, notification.timeAgo,
style: AppTypography.subtitleSmall.copyWith( style: AppTypography.subtitleSmall.copyWith(
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
fontSize: 9, fontSize: 9,
), ),
), ),
@@ -220,7 +221,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
child: Text( child: Text(
notification.actionLabel!, notification.actionLabel!,
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 9, fontSize: 9,
), ),
@@ -268,15 +269,16 @@ class DashboardNotificationsWidget extends StatelessWidget {
); );
} }
Widget _buildEmptyNotifications() { Widget _buildEmptyNotifications(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container( return Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
const Icon( Icon(
Icons.notifications_none_outlined, Icons.notifications_none_outlined,
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
size: 24, size: 24,
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -359,7 +361,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
title: 'Nouvelles activités', title: 'Nouvelles activités',
message: '${data.recentActivitiesCount} activités récentes', message: '${data.recentActivitiesCount} activités récentes',
icon: Icons.fiber_new_outlined, icon: Icons.fiber_new_outlined,
color: AppColors.brandGreen, color: AppColors.primaryDark,
timeAgo: '15min', timeAgo: '15min',
isUrgent: false, isUrgent: false,
actionLabel: 'Voir', actionLabel: 'Voir',

View File

@@ -173,28 +173,28 @@ class _EpargnePageState extends State<EpargnePage> {
'VUE D\'ENSEMBLE', 'VUE D\'ENSEMBLE',
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold), style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
), ),
const Icon(Icons.account_balance_wallet, color: AppColors.primaryGreen, size: 24), const Icon(Icons.account_balance_wallet, color: AppColors.primary, size: 24),
], ],
), ),
const SizedBox(height: SpacingTokens.md), const SizedBox(height: SpacingTokens.md),
Text( Text(
'${total.toStringAsFixed(0)} XOF', '${total.toStringAsFixed(0)} XOF',
style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primaryGreen), style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primary),
), ),
const SizedBox(height: SpacingTokens.xs), const SizedBox(height: SpacingTokens.xs),
Text( Text(
'Solde disponible total • ${_comptes.length} compte${_comptes.length > 1 ? 's' : ''}', 'Solde disponible total • ${_comptes.length} compte${_comptes.length > 1 ? 's' : ''}',
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
), ),
if (_comptes.any((c) => c.soldeBloque > 0)) ...[ if (_comptes.any((c) => c.soldeBloque > 0)) ...[
const SizedBox(height: SpacingTokens.xs), const SizedBox(height: SpacingTokens.xs),
Row( Row(
children: [ children: [
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700), Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'Certains fonds sont sous surveillance LCB-FT', 'Certains fonds sont sous surveillance LCB-FT',
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: Colors.amber.shade700), style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.warning),
), ),
], ],
), ),
@@ -234,12 +234,17 @@ class _EpargnePageState extends State<EpargnePage> {
if (typeLibelle != null) if (typeLibelle != null)
Text( Text(
typeLibelle, typeLibelle,
style: AppTypography.subtitleSmall.copyWith(color: AppColors.textSecondaryLight), style: AppTypography.subtitleSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
), ),
if (dateStr != null) if (dateStr != null)
Text( Text(
dateStr, dateStr,
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight), style: AppTypography.bodyTextSmall.copyWith(
fontSize: 10,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
),
), ),
], ],
), ),
@@ -252,16 +257,16 @@ class _EpargnePageState extends State<EpargnePage> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.15), color: AppColors.warningContainer,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.amber.shade700, width: 1), border: Border.all(color: AppColors.warning, width: 1),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700), Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
const SizedBox(width: 3), const SizedBox(width: 3),
Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: Colors.amber.shade700)), Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: AppColors.warning)),
], ],
), ),
), ),
@@ -270,7 +275,7 @@ class _EpargnePageState extends State<EpargnePage> {
if (c.statut != null) if (c.statut != null)
InfoBadge( InfoBadge(
text: c.statut!, text: c.statut!,
backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondaryLight, backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondary,
), ),
], ],
), ),
@@ -284,7 +289,7 @@ class _EpargnePageState extends State<EpargnePage> {
Text('SOLDE ACTUEL', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)), Text('SOLDE ACTUEL', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
Text( Text(
'${c.soldeActuel.toStringAsFixed(0)} XOF', '${c.soldeActuel.toStringAsFixed(0)} XOF',
style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primaryGreen), style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primary),
), ),
], ],
), ),
@@ -305,7 +310,7 @@ class _EpargnePageState extends State<EpargnePage> {
const SizedBox(height: SpacingTokens.sm), const SizedBox(height: SpacingTokens.sm),
Text( Text(
c.description!, c.description!,
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondaryLight), style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondary),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -321,8 +326,8 @@ class _EpargnePageState extends State<EpargnePage> {
onPressed: () => _openDepot(c), onPressed: () => _openDepot(c),
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
backgroundColor: AppColors.primaryGreen.withOpacity(0.1), backgroundColor: AppColors.primary.withOpacity(0.1),
foregroundColor: AppColors.primaryGreen, foregroundColor: AppColors.primary,
), ),
child: const Text('Dépôt', style: TextStyle(fontSize: 12)), child: const Text('Dépôt', style: TextStyle(fontSize: 12)),
), ),
@@ -333,8 +338,8 @@ class _EpargnePageState extends State<EpargnePage> {
onPressed: soldeDispo > 0 ? () => _openRetrait(c) : null, onPressed: soldeDispo > 0 ? () => _openRetrait(c) : null,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
backgroundColor: AppColors.primaryGreen.withOpacity(0.1), backgroundColor: AppColors.primary.withOpacity(0.1),
foregroundColor: AppColors.primaryGreen, foregroundColor: AppColors.primary,
), ),
child: const Text('Retrait', style: TextStyle(fontSize: 12)), child: const Text('Retrait', style: TextStyle(fontSize: 12)),
), ),
@@ -346,8 +351,8 @@ class _EpargnePageState extends State<EpargnePage> {
onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null, onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
backgroundColor: AppColors.primaryGreen.withOpacity(0.1), backgroundColor: AppColors.primary.withOpacity(0.1),
foregroundColor: AppColors.primaryGreen, foregroundColor: AppColors.primary,
), ),
child: const Text('Transférer', style: TextStyle(fontSize: 12)), child: const Text('Transférer', style: TextStyle(fontSize: 12)),
), ),
@@ -365,7 +370,9 @@ class _EpargnePageState extends State<EpargnePage> {
label: const Text('Détail', style: TextStyle(fontSize: 12)), label: const Text('Détail', style: TextStyle(fontSize: 12)),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
foregroundColor: AppColors.textPrimaryLight, foregroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimaryDark
: AppColors.textPrimary,
), ),
), ),
), ),
@@ -377,7 +384,9 @@ class _EpargnePageState extends State<EpargnePage> {
label: const Text('Historique', style: TextStyle(fontSize: 12)), label: const Text('Historique', style: TextStyle(fontSize: 12)),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm), padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
foregroundColor: AppColors.textPrimaryLight, foregroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimaryDark
: AppColors.textPrimary,
), ),
), ),
), ),
@@ -424,17 +433,17 @@ class _EpargnePageState extends State<EpargnePage> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.savings_outlined, size: 64, color: AppColors.textSecondaryLight), Icon(Icons.savings_outlined, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
const SizedBox(height: SpacingTokens.lg), const SizedBox(height: SpacingTokens.lg),
Text( Text(
'Aucun compte épargne', 'Aucun compte épargne',
style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight), style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: SpacingTokens.sm), const SizedBox(height: SpacingTokens.sm),
Text( Text(
'Votre organisation peut ouvrir un compte épargne pour vous. Contactez-la pour en bénéficier.', 'Votre organisation peut ouvrir un compte épargne pour vous. Contactez-la pour en bénéficier.',
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight), style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -459,19 +468,18 @@ class _EpargnePageState extends State<EpargnePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showFab = _canCreateCompte(context); final showFab = _canCreateCompte(context);
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: const UFAppBar( appBar: UFAppBar(
title: 'COMPTES ÉPARGNE', title: 'Comptes Épargne',
backgroundColor: AppColors.surface, moduleGradient: ModuleColors.epargneGradient,
foregroundColor: AppColors.textPrimaryLight,
), ),
body: _buildBodyContent(), body: _buildBodyContent(),
floatingActionButton: showFab floatingActionButton: showFab
? FloatingActionButton( ? FloatingActionButton(
onPressed: _openCreerCompte, onPressed: _openCreerCompte,
tooltip: 'Créer un compte épargne pour un membre', tooltip: 'Créer un compte épargne pour un membre',
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
child: const Icon(Icons.add), child: const Icon(Icons.add),
) )
: null, : null,

View File

@@ -38,9 +38,10 @@ class _BudgetsListView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: ColorTokens.background, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar( appBar: UFAppBar(
title: 'BUDGETS', title: 'Budgets',
moduleGradient: ModuleColors.financeWorkflowGradient,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
actions: [ actions: [
IconButton( IconButton(
@@ -57,7 +58,9 @@ class _BudgetsListView extends StatelessWidget {
), ),
], ],
), ),
body: BlocConsumer<BudgetBloc, BudgetState>( body: SafeArea(
top: false,
child: BlocConsumer<BudgetBloc, BudgetState>(
listener: (context, state) { listener: (context, state) {
if (state is BudgetCreated) { if (state is BudgetCreated) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -98,13 +101,14 @@ class _BudgetsListView extends StatelessWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
}, },
), ),
),
floatingActionButton: FloatingActionButton.extended( floatingActionButton: FloatingActionButton.extended(
onPressed: () { onPressed: () {
SnackbarHelper.showNotImplemented(context, 'Création de budget'); SnackbarHelper.showNotImplemented(context, 'Création de budget');
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: const Text('Nouveau budget'), label: const Text('Nouveau budget'),
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
), ),
); );
} }
@@ -143,7 +147,7 @@ class _BudgetsListView extends StatelessWidget {
Widget _buildFilterChips(BuildContext context, BudgetsLoaded state) { Widget _buildFilterChips(BuildContext context, BudgetsLoaded state) {
return Container( return Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
color: AppColors.lightBackground, color: Theme.of(context).scaffoldBackgroundColor,
child: Wrap( child: Wrap(
spacing: SpacingTokens.sm, spacing: SpacingTokens.sm,
runSpacing: SpacingTokens.sm, runSpacing: SpacingTokens.sm,
@@ -178,6 +182,10 @@ class _BudgetsListView extends StatelessWidget {
} }
Widget _buildEmptyState(String message) { Widget _buildEmptyState(String message) {
return Builder(
builder: (context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -185,21 +193,25 @@ class _BudgetsListView extends StatelessWidget {
Icon( Icon(
Icons.account_balance_wallet_outlined, Icons.account_balance_wallet_outlined,
size: 48, size: 48,
color: AppColors.textSecondaryLight.withOpacity(0.5), color: textSecondary.withOpacity(0.5),
), ),
const SizedBox(height: SpacingTokens.lg), const SizedBox(height: SpacingTokens.lg),
Text( Text(
message, message,
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.textSecondaryLight, color: textSecondary,
), ),
), ),
], ],
), ),
); );
},
);
} }
Widget _buildErrorState(BuildContext context, String message) { Widget _buildErrorState(BuildContext context, String message) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -213,7 +225,7 @@ class _BudgetsListView extends StatelessWidget {
Text( Text(
message, message,
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.textSecondaryLight, color: textSecondary,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -274,14 +286,14 @@ class _BudgetCard extends StatelessWidget {
} }
} }
Color _getStatusColor(BudgetStatus status) { Color _getStatusColor(BudgetStatus status, {bool isDark = false}) {
switch (status) { switch (status) {
case BudgetStatus.draft: case BudgetStatus.draft:
return AppColors.textSecondaryLight; return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
case BudgetStatus.active: case BudgetStatus.active:
return AppColors.brandGreen; return AppColors.primaryDark;
case BudgetStatus.closed: case BudgetStatus.closed:
return AppColors.textSecondaryLight; return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
case BudgetStatus.cancelled: case BudgetStatus.cancelled:
return AppColors.error; return AppColors.error;
} }
@@ -302,17 +314,20 @@ class _BudgetCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final currencyFormat = NumberFormat.currency(symbol: budget.currency); final currencyFormat = NumberFormat.currency(symbol: budget.currency);
return Container( return Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(color: AppColors.lightBorder), border: Border.all(
color: isDark ? AppColors.borderDark : AppColors.border,
),
boxShadow: const [ boxShadow: const [
BoxShadow( BoxShadow(
color: Color(0x0A000000), color: AppColors.shadow,
blurRadius: 8, blurRadius: 8,
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
@@ -335,13 +350,13 @@ class _BudgetCard extends StatelessWidget {
vertical: 4, vertical: 4,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getStatusColor(budget.status).withOpacity(0.1), color: _getStatusColor(budget.status, isDark: isDark).withOpacity(0.1),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
), ),
child: Text( child: Text(
_getStatusLabel(budget.status), _getStatusLabel(budget.status),
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: _getStatusColor(budget.status), color: _getStatusColor(budget.status, isDark: isDark),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -368,7 +383,7 @@ class _BudgetCard extends StatelessWidget {
Text( Text(
currencyFormat.format(budget.totalPlanned), currencyFormat.format(budget.totalPlanned),
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -389,7 +404,7 @@ class _BudgetCard extends StatelessWidget {
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: budget.isOverBudget color: budget.isOverBudget
? AppColors.error ? AppColors.error
: AppColors.brandGreen, : AppColors.primaryDark,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -401,10 +416,10 @@ class _BudgetCard extends StatelessWidget {
const SizedBox(height: SpacingTokens.sm), const SizedBox(height: SpacingTokens.sm),
LinearProgressIndicator( LinearProgressIndicator(
value: budget.realizationRate / 100, value: budget.realizationRate / 100,
backgroundColor: AppColors.lightBorder, backgroundColor: isDark ? AppColors.borderDark : AppColors.border,
color: budget.isOverBudget color: budget.isOverBudget
? AppColors.error ? AppColors.error
: AppColors.brandGreen, : AppColors.primaryDark,
), ),
const SizedBox(height: SpacingTokens.xs), const SizedBox(height: SpacingTokens.xs),
Text( Text(

View File

@@ -39,9 +39,10 @@ class _PendingApprovalsView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: ColorTokens.background, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar( appBar: UFAppBar(
title: 'APPROBATIONS EN ATTENTE', title: 'Approbations en attente',
moduleGradient: ModuleColors.financeWorkflowGradient,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
actions: [ actions: [
IconButton( IconButton(
@@ -155,6 +156,7 @@ class _PendingApprovalsView extends StatelessWidget {
} }
Widget _buildErrorState(BuildContext context, String message) { Widget _buildErrorState(BuildContext context, String message) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -168,7 +170,7 @@ class _PendingApprovalsView extends StatelessWidget {
Text( Text(
message, message,
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -188,6 +190,10 @@ class _PendingApprovalsView extends StatelessWidget {
} }
Widget _buildEmptyState() { Widget _buildEmptyState() {
return Builder(
builder: (context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -201,19 +207,21 @@ class _PendingApprovalsView extends StatelessWidget {
Text( Text(
'Aucune approbation en attente', 'Aucune approbation en attente',
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.textSecondaryLight, color: textSecondary,
), ),
), ),
const SizedBox(height: SpacingTokens.sm), const SizedBox(height: SpacingTokens.sm),
Text( Text(
'Toutes les transactions sont approuvées', 'Toutes les transactions sont approuvées',
style: AppTypography.bodyTextSmall.copyWith( style: AppTypography.bodyTextSmall.copyWith(
color: AppColors.textSecondaryLight, color: textSecondary,
), ),
), ),
], ],
), ),
); );
},
);
} }
} }
@@ -248,12 +256,12 @@ class _ApprovalCard extends StatelessWidget {
} }
} }
Color _getLevelColor(ApprovalLevel level) { Color _getLevelColor(ApprovalLevel level, {bool isDark = false}) {
switch (level) { switch (level) {
case ApprovalLevel.none: case ApprovalLevel.none:
return AppColors.textSecondaryLight; return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
case ApprovalLevel.level1: case ApprovalLevel.level1:
return AppColors.brandGreen; return AppColors.primaryDark;
case ApprovalLevel.level2: case ApprovalLevel.level2:
return AppColors.warning; return AppColors.warning;
case ApprovalLevel.level3: case ApprovalLevel.level3:
@@ -263,17 +271,18 @@ class _ApprovalCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final currencyFormat = NumberFormat.currency(symbol: approval.currency); final currencyFormat = NumberFormat.currency(symbol: approval.currency);
return Container( return Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(color: AppColors.lightBorder), border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
boxShadow: const [ boxShadow: const [
BoxShadow( BoxShadow(
color: Color(0x0A000000), color: AppColors.shadow,
blurRadius: 8, blurRadius: 8,
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
@@ -296,13 +305,13 @@ class _ApprovalCard extends StatelessWidget {
vertical: 4, vertical: 4,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getLevelColor(approval.requiredLevel).withOpacity(0.1), color: _getLevelColor(approval.requiredLevel, isDark: isDark).withOpacity(0.1),
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
), ),
child: Text( child: Text(
'Niveau ${approval.requiredApprovals}', 'Niveau ${approval.requiredApprovals}',
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: _getLevelColor(approval.requiredLevel), color: _getLevelColor(approval.requiredLevel, isDark: isDark),
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -313,7 +322,7 @@ class _ApprovalCard extends StatelessWidget {
Text( Text(
currencyFormat.format(approval.amount), currencyFormat.format(approval.amount),
style: AppTypography.headerSmall.copyWith( style: AppTypography.headerSmall.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -323,7 +332,7 @@ class _ApprovalCard extends StatelessWidget {
Icon( Icon(
Icons.person_outline, Icons.person_outline,
size: 16, size: 16,
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Expanded( Expanded(
@@ -340,7 +349,7 @@ class _ApprovalCard extends StatelessWidget {
Icon( Icon(
Icons.access_time, Icons.access_time,
size: 16, size: 16,
color: AppColors.textSecondaryLight, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(

View File

@@ -53,6 +53,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency); final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
return AlertDialog( return AlertDialog(
@@ -72,9 +73,9 @@ class _ApproveDialogState extends State<ApproveDialog> {
Container( Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBackground, color: isDark ? AppColors.surfaceDark : AppColors.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
border: Border.all(color: AppColors.lightBorder), border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -88,7 +89,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
'Montant', 'Montant',
currencyFormat.format(widget.approval.amount), currencyFormat.format(widget.approval.amount),
valueStyle: AppTypography.actionText.copyWith( valueStyle: AppTypography.actionText.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -145,7 +146,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
label: const Text('Approuver'), label: const Text('Approuver'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.success, backgroundColor: AppColors.success,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
), ),
], ],

View File

@@ -53,6 +53,7 @@ class _RejectDialogState extends State<RejectDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency); final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
return AlertDialog( return AlertDialog(
@@ -75,9 +76,9 @@ class _RejectDialogState extends State<RejectDialog> {
Container( Container(
padding: const EdgeInsets.all(SpacingTokens.md), padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightBackground, color: isDark ? AppColors.surfaceDark : AppColors.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm), borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
border: Border.all(color: AppColors.lightBorder), border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -91,7 +92,7 @@ class _RejectDialogState extends State<RejectDialog> {
'Montant', 'Montant',
currencyFormat.format(widget.approval.amount), currencyFormat.format(widget.approval.amount),
valueStyle: AppTypography.actionText.copyWith( valueStyle: AppTypography.actionText.copyWith(
color: AppColors.primaryGreen, color: AppColors.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -141,7 +142,7 @@ class _RejectDialogState extends State<RejectDialog> {
label: const Text('Rejeter'), label: const Text('Rejeter'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.error, backgroundColor: AppColors.error,
foregroundColor: Colors.white, foregroundColor: AppColors.onError,
), ),
), ),
], ],

View File

@@ -39,8 +39,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: const UFAppBar(title: 'AIDE & SUPPORT'), appBar: UFAppBar(
body: SingleChildScrollView( title: 'Aide & Support',
moduleGradient: ModuleColors.supportGradient,
),
body: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -75,6 +80,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
], ],
), ),
), ),
),
); );
} }
@@ -86,12 +92,12 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.1), color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: const Icon( child: const Icon(
Icons.help_outline, Icons.help_outline,
color: AppColors.primaryGreen, color: AppColors.primary,
size: 32, size: 32,
), ),
), ),
@@ -113,13 +119,17 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
/// Section de recherche /// Section de recherche
Widget _buildSearchSection() { Widget _buildSearchSection() {
final isDark = Theme.of(context).brightness == Brightness.dark;
final bgSearch = isDark ? AppColors.surfaceVariantDark : AppColors.surface;
final borderSearch = isDark ? AppColors.borderDark : AppColors.border;
final iconColor = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return CoreCard( return CoreCard(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
const Icon(Icons.search, color: AppColors.primaryGreen, size: 18), const Icon(Icons.search, color: AppColors.primary, size: 18),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'RECHERCHER DANS L\'AIDE', 'RECHERCHER DANS L\'AIDE',
@@ -130,9 +140,9 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.lightSurface, color: bgSearch,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.lightBorder), border: Border.all(color: borderSearch),
), ),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
@@ -141,14 +151,14 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Une question, un mot-clé...', hintText: 'Une question, un mot-clé...',
hintStyle: AppTypography.subtitleSmall, hintStyle: AppTypography.subtitleSmall,
prefixIcon: const Icon(Icons.search, color: AppColors.textSecondaryLight, size: 18), prefixIcon: Icon(Icons.search, color: iconColor, size: 18),
suffixIcon: _searchQuery.isNotEmpty suffixIcon: _searchQuery.isNotEmpty
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
_searchController.clear(); _searchController.clear();
setState(() => _searchQuery = ''); setState(() => _searchQuery = '');
}, },
icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight, size: 18), icon: Icon(Icons.clear, color: iconColor, size: 18),
) )
: null, : null,
border: InputBorder.none, border: InputBorder.none,
@@ -180,7 +190,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
'CHAT', 'CHAT',
'Support Direct', 'Support Direct',
Icons.chat_bubble_outline, Icons.chat_bubble_outline,
AppColors.primaryGreen, AppColors.primary,
() => _startLiveChat(), () => _startLiveChat(),
), ),
), ),
@@ -286,16 +296,16 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent, color: isSelected ? AppColors.primary.withOpacity(0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
border: Border.all( border: Border.all(
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder, color: isSelected ? AppColors.primary : AppColors.border,
), ),
), ),
child: Text( child: Text(
label.toUpperCase(), label.toUpperCase(),
style: AppTypography.badgeText.copyWith( style: AppTypography.badgeText.copyWith(
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight, color: isSelected ? AppColors.primary : AppColors.textSecondary,
fontSize: 9, fontSize: 9,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
), ),
@@ -338,11 +348,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
), ),
leading: Icon( leading: Icon(
faq['icon'] as IconData, faq['icon'] as IconData,
color: AppColors.primaryGreen, color: AppColors.primary,
size: 18, size: 18,
), ),
iconColor: AppColors.primaryGreen, iconColor: AppColors.primary,
collapsedIconColor: AppColors.textSecondaryLight, collapsedIconColor: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
shape: const RoundedRectangleBorder(side: BorderSide.none), shape: const RoundedRectangleBorder(side: BorderSide.none),
children: [ children: [
Padding( Padding(
@@ -370,7 +382,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
), ),
), ),
_buildGuideItem('Introduction', 'Démarrer avec UnionFlow', Icons.play_circle_outline, AppColors.success, () => _openGuide('getting-started')), _buildGuideItem('Introduction', 'Démarrer avec UnionFlow', Icons.play_circle_outline, AppColors.success, () => _openGuide('getting-started')),
_buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primaryGreen, () => _openGuide('members')), _buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primary, () => _openGuide('members')),
_buildGuideItem('Organisations', 'Structures & Syndicats', Icons.business_outlined, AppColors.info, () => _openGuide('organizations')), _buildGuideItem('Organisations', 'Structures & Syndicats', Icons.business_outlined, AppColors.info, () => _openGuide('organizations')),
], ],
); );
@@ -394,7 +406,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
], ],
), ),
), ),
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 16), Icon(
Icons.chevron_right,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
size: 16,
),
], ],
), ),
); );
@@ -402,8 +420,9 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
/// Section contact /// Section contact
Widget _buildContactSection() { Widget _buildContactSection() {
final isDark = Theme.of(context).brightness == Brightness.dark;
return CoreCard( return CoreCard(
backgroundColor: AppColors.primaryGreen, // Correction: color -> backgroundColor backgroundColor: AppColors.primary, // Correction: color -> backgroundColor
child: Column( child: Column(
children: [ children: [
const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32), const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32),
@@ -426,8 +445,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
child: UFPrimaryButton( child: UFPrimaryButton(
label: 'EMAIL', // Correction: text -> label label: 'EMAIL', // Correction: text -> label
onPressed: () => _contactByEmail(), onPressed: () => _contactByEmail(),
backgroundColor: Colors.white, backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
textColor: AppColors.primaryGreen, textColor: AppColors.primary,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -436,7 +455,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
label: 'CHAT', // Correction: text -> label label: 'CHAT', // Correction: text -> label
onPressed: () => _startLiveChat(), onPressed: () => _startLiveChat(),
backgroundColor: Colors.white.withOpacity(0.2), backgroundColor: Colors.white.withOpacity(0.2),
textColor: Colors.white, textColor: AppColors.onPrimary,
), ),
), ),
], ],
@@ -516,8 +535,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
_contactByEmail(); _contactByEmail();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Envoyer un email'), child: const Text('Envoyer un email'),
), ),
@@ -548,7 +567,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.error, backgroundColor: AppColors.error,
foregroundColor: Colors.white, foregroundColor: AppColors.onError,
), ),
child: const Text('Signaler'), child: const Text('Signaler'),
), ),
@@ -578,8 +597,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
_launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile'); _launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Envoyer'), child: const Text('Envoyer'),
), ),
@@ -613,8 +632,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
_launchUrl('https://docs.unionflow.com/$guideId'); _launchUrl('https://docs.unionflow.com/$guideId');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Voir en ligne'), child: const Text('Voir en ligne'),
), ),
@@ -644,8 +663,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
_contactByEmail(); _contactByEmail();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
), ),
child: const Text('Contacter le support'), child: const Text('Contacter le support'),
), ),

View File

@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
import '../../bloc/org_types_bloc.dart'; import '../../bloc/org_types_bloc.dart';
import '../../domain/entities/type_reference_entity.dart'; import '../../domain/entities/type_reference_entity.dart';
import '../../../../shared/design_system/tokens/app_colors.dart'; import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import '../../../../core/di/injection_container.dart'; import '../../../../core/di/injection_container.dart';
const List<String> _devises = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES']; const List<String> _devises = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
@@ -106,12 +108,10 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.lightBackground, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar( appBar: UFAppBar(
backgroundColor: AppColors.primaryGreen, title: 'Nouvelle Organisation',
foregroundColor: Colors.white, moduleGradient: ModuleColors.organisationsGradient,
title: const Text('Nouvelle Organisation'),
elevation: 0,
actions: [ actions: [
TextButton( TextButton(
onPressed: _isFormValid() ? _saveOrganisation : null, onPressed: _isFormValid() ? _saveOrganisation : null,
@@ -119,7 +119,9 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
), ),
], ],
), ),
body: BlocListener<OrganizationsBloc, OrganizationsState>( body: SafeArea(
top: false,
child: BlocListener<OrganizationsBloc, OrganizationsState>(
listener: (context, state) { listener: (context, state) {
if (state is OrganizationCreated) { if (state is OrganizationCreated) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -128,7 +130,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
} else if (state is OrganizationsError) { } else if (state is OrganizationsError) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message), backgroundColor: Colors.red), SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
); );
} }
}, },
@@ -161,20 +163,21 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
), ),
), ),
), ),
),
); );
} }
Widget _buildSection(String title, IconData icon, List<Widget> children) { Widget _buildSection(String title, IconData icon, List<Widget> children) {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row(children: [ Row(children: [
Icon(icon, size: 16, color: AppColors.primaryGreen), Icon(icon, size: 16, color: AppColors.primary),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)), Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)),
]), ]),
const SizedBox(height: 10), const SizedBox(height: 10),
...children, ...children,
@@ -237,9 +240,18 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
onTap: () => _pickDateFondation(context), onTap: () => _pickDateFondation(context),
child: InputDecorator( child: InputDecorator(
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)), decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
child: Text( child: Builder(
builder: (ctx) {
final isDark = Theme.of(ctx).brightness == Brightness.dark;
return Text(
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight), style: TextStyle(
color: _dateFondation != null
? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary)
: (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary),
),
);
},
), ),
), ),
), ),
@@ -355,7 +367,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)), subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
value: _organisationPublique, value: _organisationPublique,
onChanged: (v) => setState(() => _organisationPublique = v), onChanged: (v) => setState(() => _organisationPublique = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
SwitchListTile( SwitchListTile(
dense: true, dense: true,
@@ -364,7 +376,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)), subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
value: _accepteNouveauxMembres, value: _accepteNouveauxMembres,
onChanged: (v) => setState(() => _accepteNouveauxMembres = v), onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
]; ];
@@ -392,7 +404,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)), title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
value: _cotisationObligatoire, value: _cotisationObligatoire,
onChanged: (v) => setState(() => _cotisationObligatoire = v), onChanged: (v) => setState(() => _cotisationObligatoire = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
if (_cotisationObligatoire) ...[ if (_cotisationObligatoire) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -451,8 +463,8 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: const Text('Créer l\'organisation'), label: const Text('Créer l\'organisation'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
), ),
@@ -466,7 +478,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
icon: const Icon(Icons.cancel), icon: const Icon(Icons.cancel),
label: const Text('Annuler'), label: const Text('Annuler'),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.textSecondaryLight, foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary,
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
), ),
), ),

View File

@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
import '../../bloc/org_types_bloc.dart'; import '../../bloc/org_types_bloc.dart';
import '../../domain/entities/type_reference_entity.dart'; import '../../domain/entities/type_reference_entity.dart';
import '../../../../shared/design_system/tokens/app_colors.dart'; import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import '../../../../core/di/injection_container.dart'; import '../../../../core/di/injection_container.dart';
const List<String> _devisesEdit = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES']; const List<String> _devisesEdit = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
@@ -185,12 +187,10 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.lightBackground, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar( appBar: UFAppBar(
backgroundColor: AppColors.primaryGreen, title: 'Modifier Organisation',
foregroundColor: Colors.white, moduleGradient: ModuleColors.organisationsGradient,
title: const Text('Modifier Organisation'),
elevation: 0,
actions: [ actions: [
TextButton( TextButton(
onPressed: _hasChanges() ? _saveChanges : null, onPressed: _hasChanges() ? _saveChanges : null,
@@ -212,7 +212,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
Navigator.of(context).pop(true); Navigator.of(context).pop(true);
} else if (state is OrganizationsError) { } else if (state is OrganizationsError) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message), backgroundColor: Colors.red), SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
); );
} }
}, },
@@ -254,14 +254,14 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
Widget _buildSection(String title, IconData icon, List<Widget> children) { Widget _buildSection(String title, IconData icon, List<Widget> children) {
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row(children: [ Row(children: [
Icon(icon, size: 16, color: AppColors.primaryGreen), Icon(icon, size: 16, color: AppColors.primary),
const SizedBox(width: 6), const SizedBox(width: 6),
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)), Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)),
]), ]),
const SizedBox(height: 10), const SizedBox(height: 10),
...children, ...children,
@@ -326,9 +326,18 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
onTap: () => _pickDateFondation(context), onTap: () => _pickDateFondation(context),
child: InputDecorator( child: InputDecorator(
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)), decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
child: Text( child: Builder(
builder: (ctx) {
final isDark = Theme.of(ctx).brightness == Brightness.dark;
return Text(
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date', _dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight), style: TextStyle(
color: _dateFondation != null
? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary)
: (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary),
),
);
},
), ),
), ),
), ),
@@ -452,7 +461,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)), subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
value: _organisationPublique, value: _organisationPublique,
onChanged: (v) => setState(() => _organisationPublique = v), onChanged: (v) => setState(() => _organisationPublique = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
SwitchListTile( SwitchListTile(
dense: true, dense: true,
@@ -461,7 +470,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)), subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
value: _accepteNouveauxMembres, value: _accepteNouveauxMembres,
onChanged: (v) => setState(() => _accepteNouveauxMembres = v), onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
]; ];
@@ -490,7 +499,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)), title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
value: _cotisationObligatoire, value: _cotisationObligatoire,
onChanged: (v) => setState(() => _cotisationObligatoire = v), onChanged: (v) => setState(() => _cotisationObligatoire = v),
activeColor: AppColors.primaryGreen, activeColor: AppColors.primary,
), ),
if (_cotisationObligatoire) ...[ if (_cotisationObligatoire) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
@@ -559,12 +568,15 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
]; ];
Widget _buildReadOnlyRow(IconData icon, String label, String value) { Widget _buildReadOnlyRow(IconData icon, String label, String value) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
return Row(children: [ return Row(children: [
Icon(icon, size: 18, color: AppColors.textSecondaryLight), Icon(icon, size: 18, color: textSecondary),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)), Text(label, style: TextStyle(fontSize: 11, color: textSecondary)),
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)), Text(value, style: TextStyle(fontSize: 13, color: textPrimary, fontWeight: FontWeight.w600)),
])), ])),
]); ]);
} }
@@ -578,8 +590,8 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
label: const Text('Enregistrer les modifications'), label: const Text('Enregistrer les modifications'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen, backgroundColor: AppColors.primary,
foregroundColor: Colors.white, foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
), ),
@@ -592,7 +604,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
onPressed: _showDiscardDialog, onPressed: _showDiscardDialog,
icon: const Icon(Icons.cancel), icon: const Icon(Icons.cancel),
label: const Text('Annuler'), label: const Text('Annuler'),
style: OutlinedButton.styleFrom(foregroundColor: AppColors.textSecondaryLight, padding: const EdgeInsets.symmetric(vertical: 10)), style: OutlinedButton.styleFrom(foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary, padding: const EdgeInsets.symmetric(vertical: 10)),
), ),
), ),
]); ]);
@@ -681,8 +693,8 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Continuer l\'édition')), TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Continuer l\'édition')),
ElevatedButton( ElevatedButton(
onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); }, onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); },
style: ElevatedButton.styleFrom(backgroundColor: Colors.red), style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
child: const Text('Abandonner', style: TextStyle(color: Colors.white)), child: const Text('Abandonner', style: TextStyle(color: AppColors.onPrimary)),
), ),
], ],
), ),