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:
@@ -6,6 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../../../shared/widgets/core_card.dart';
|
||||
import '../../../../shared/widgets/info_badge.dart';
|
||||
import '../../../../shared/widgets/powered_by_lions_dev.dart';
|
||||
|
||||
|
||||
/// Page À propos - UnionFlow Mobile
|
||||
@@ -40,7 +41,8 @@ class _AboutPageState extends State<AboutPage> {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'À PROPOS',
|
||||
title: 'À propos',
|
||||
moduleGradient: ModuleColors.systemeGradient,
|
||||
actions: [
|
||||
IconButton(
|
||||
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),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -75,27 +79,33 @@ class _AboutPageState extends State<AboutPage> {
|
||||
|
||||
// Support et contact
|
||||
_buildSupportSection(),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Branding « Powered by Lions Dev » (logo adaptatif dark/light)
|
||||
const Center(child: PoweredByLionsDev()),
|
||||
const SizedBox(height: 80),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Header épuré
|
||||
Widget _buildHeader() {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.account_balance,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
@@ -112,8 +122,8 @@ class _AboutPageState extends State<AboutPage> {
|
||||
if (_packageInfo != null)
|
||||
InfoBadge(
|
||||
text: 'VERSION ${_packageInfo!.version}',
|
||||
backgroundColor: AppColors.lightSurface,
|
||||
textColor: AppColors.textSecondaryLight,
|
||||
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||
textColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -149,7 +159,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
||||
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
@@ -175,16 +185,17 @@ class _AboutPageState extends State<AboutPage> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildTeamMember(
|
||||
'UnionFlow Team',
|
||||
'Architecture & Dev',
|
||||
'Lions Dev',
|
||||
'Intégrateur de solutions digitales innovantes — lions.dev',
|
||||
Icons.code,
|
||||
AppColors.primaryGreen,
|
||||
AppColors.primary,
|
||||
onTap: () => _launchUrl('https://www.lions.dev'),
|
||||
),
|
||||
_buildTeamMember(
|
||||
'Design System',
|
||||
'UI / UX Focus',
|
||||
Icons.design_services,
|
||||
AppColors.brandGreenLight,
|
||||
'UnionFlow',
|
||||
'Mouvement d\'entraide & solidarité',
|
||||
Icons.account_balance,
|
||||
AppColors.primaryLight,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -192,8 +203,8 @@ class _AboutPageState extends State<AboutPage> {
|
||||
}
|
||||
|
||||
/// Membre de l'équipe
|
||||
Widget _buildTeamMember(String name, String role, IconData icon, Color color) {
|
||||
return Padding(
|
||||
Widget _buildTeamMember(String name, String role, IconData icon, Color color, {VoidCallback? onTap}) {
|
||||
final content = Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
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
|
||||
@@ -231,8 +250,8 @@ class _AboutPageState extends State<AboutPage> {
|
||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primaryGreen),
|
||||
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.brandGreenLight),
|
||||
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primary),
|
||||
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.primaryLight),
|
||||
_buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success),
|
||||
_buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning),
|
||||
],
|
||||
@@ -284,6 +303,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
|
||||
/// Élément de lien
|
||||
Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@@ -291,7 +311,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
||||
Icon(icon, color: AppColors.primary, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
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(
|
||||
children: [
|
||||
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
|
||||
Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
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');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
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');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Envoyer une suggestion'),
|
||||
),
|
||||
@@ -460,8 +481,8 @@ class _AboutPageState extends State<AboutPage> {
|
||||
_launchStoreForRating();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Évaluer maintenant'),
|
||||
),
|
||||
|
||||
@@ -8,9 +8,17 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../bloc/auth_bloc.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
|
||||
/// Gradient forêt + glassmorphism + animations + biométrie + remember me
|
||||
// ── Couleurs signature ────────────────────────────────────────────────────────
|
||||
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 {
|
||||
const LoginPage({super.key});
|
||||
|
||||
@@ -19,37 +27,33 @@ class LoginPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
late final AnimationController _fadeController;
|
||||
late final AnimationController _slideController;
|
||||
late final AnimationController _fadeCtrl;
|
||||
late final AnimationController _slideCtrl;
|
||||
late final Animation<double> _fadeAnim;
|
||||
late final Animation<Offset> _slideAnim;
|
||||
|
||||
bool _biometricAvailable = false;
|
||||
|
||||
final _localAuth = LocalAuthentication();
|
||||
|
||||
static const _gradTop = Color(0xFF1B5E20);
|
||||
static const _gradMid = Color(0xFF2E7D32);
|
||||
static const _gradBot = Color(0xFF388E3C);
|
||||
static const _primaryGreen = Color(0xFF2E7D32);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fadeController = AnimationController(vsync: this, duration: const Duration(milliseconds: 900));
|
||||
_slideController = AnimationController(vsync: this, duration: const Duration(milliseconds: 750));
|
||||
_fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut);
|
||||
_fadeCtrl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 900));
|
||||
_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)
|
||||
.animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic));
|
||||
_fadeController.forward();
|
||||
_slideController.forward();
|
||||
.animate(CurvedAnimation(parent: _slideCtrl, curve: Curves.easeOutCubic));
|
||||
_fadeCtrl.forward();
|
||||
_slideCtrl.forward();
|
||||
_checkBiometrics();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
_slideController.dispose();
|
||||
_fadeCtrl.dispose();
|
||||
_slideCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -67,9 +71,7 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
localizedReason: 'Authentifiez-vous pour accéder à UnionFlow',
|
||||
options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false),
|
||||
);
|
||||
if (ok && mounted) {
|
||||
context.read<AuthBloc>().add(const AuthStatusChecked());
|
||||
}
|
||||
if (ok && mounted) context.read<AuthBloc>().add(const AuthStatusChecked());
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -81,54 +83,41 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
'&response_type=code&scope=openid&kc_action=reset_credentials',
|
||||
);
|
||||
try {
|
||||
if (await canLaunchUrl(url)) await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onLogin() {
|
||||
context.read<AuthBloc>().add(const AuthLoginRequested());
|
||||
void _onAuthStateChanged(BuildContext context, AuthState state) {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: BlocConsumer<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
listener: _onAuthStateChanged,
|
||||
builder: (context, state) {
|
||||
final isLoading = state is AuthLoading;
|
||||
return Stack(
|
||||
children: [
|
||||
// Gradient background
|
||||
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 _GradientBackground(),
|
||||
const Positioned.fill(child: _HexPatternOverlay()),
|
||||
|
||||
// Content
|
||||
SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 28, vertical: 24),
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnim,
|
||||
child: SlideTransition(
|
||||
@@ -136,9 +125,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildLogoSection(),
|
||||
const _LoginLogoSection(),
|
||||
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(
|
||||
children: [
|
||||
CustomPaint(
|
||||
size: const Size(48, 48),
|
||||
painter: _HexLogoMark(),
|
||||
Image.asset(
|
||||
'assets/images/unionflow-logo.png',
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
@@ -175,7 +205,6 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
'Gérez votre organisation avec sérénité',
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Colors.white.withOpacity(0.78),
|
||||
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(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.11),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.22),
|
||||
width: 1.5,
|
||||
),
|
||||
color: Colors.white.withOpacity(0.22), width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.18),
|
||||
color: AppColors.shadowStrong,
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
@@ -225,12 +270,10 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Forgot password
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: _openForgotPassword,
|
||||
onPressed: onForgotPassword,
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
@@ -249,17 +292,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Login button
|
||||
isLoading
|
||||
? const Center(child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5))
|
||||
: ElevatedButton(
|
||||
onPressed: _onLogin,
|
||||
if (isLoading)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white, strokeWidth: 2.5),
|
||||
)
|
||||
else
|
||||
ElevatedButton(
|
||||
onPressed: onLogin,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: _primaryGreen,
|
||||
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||
foregroundColor: _kPrimaryBlue,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Text(
|
||||
@@ -267,25 +313,26 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 15.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _primaryGreen,
|
||||
color: _kPrimaryBlue,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Biometric
|
||||
if (_biometricAvailable) ...[
|
||||
if (biometricAvailable) ...[
|
||||
const SizedBox(height: 14),
|
||||
Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: _authenticateBiometric,
|
||||
icon: const Icon(Icons.fingerprint_rounded, color: Colors.white60, size: 22),
|
||||
onPressed: onBiometric,
|
||||
icon: const Icon(Icons.fingerprint_rounded,
|
||||
color: Colors.white60, size: 22),
|
||||
label: Text(
|
||||
'Connexion biométrique',
|
||||
style: GoogleFonts.roboto(fontSize: 12.5, color: Colors.white60),
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 12.5, color: Colors.white60),
|
||||
),
|
||||
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
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Motif hexagonal en overlay sur le dégradé (opacité 4%)
|
||||
class _HexPatternOverlay extends StatelessWidget {
|
||||
const _HexPatternOverlay();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(painter: _HexPatternPainter());
|
||||
}
|
||||
Widget build(BuildContext context) =>
|
||||
CustomPaint(painter: _HexPatternPainter());
|
||||
}
|
||||
|
||||
class _HexPatternPainter extends CustomPainter {
|
||||
@@ -324,8 +369,11 @@ class _HexPatternPainter extends CustomPainter {
|
||||
|
||||
for (double row = -1; row * vSpace < size.height + vSpace; row++) {
|
||||
final offset = (row % 2 == 0) ? 0.0 : hSpace / 2;
|
||||
for (double col = -1; col * hSpace - offset < size.width + hSpace; col++) {
|
||||
_hexagon(canvas, paint, Offset(col * hSpace + offset, row * vSpace), r);
|
||||
for (double col = -1;
|
||||
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();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
final a = (i * 60 - 30) * math.pi / 180;
|
||||
final p = Offset(center.dx + r * math.cos(a), center.dy + r * math.sin(a));
|
||||
if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy);
|
||||
final p = Offset(
|
||||
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();
|
||||
canvas.drawPath(path, paint);
|
||||
@@ -344,57 +393,3 @@ class _HexPatternPainter extends CustomPainter {
|
||||
@override
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/// Widget tuile de conversation
|
||||
/// Widget tuile de conversation v4
|
||||
library conversation_tile;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../domain/entities/conversation.dart';
|
||||
|
||||
class ConversationTile extends StatelessWidget {
|
||||
final Conversation conversation;
|
||||
final ConversationSummary conversation;
|
||||
final VoidCallback onTap;
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorTokens.surface,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
border: Border.all(
|
||||
color: conversation.hasUnread
|
||||
? AppColors.primaryGreen.withOpacity(0.3)
|
||||
? AppColors.primary.withOpacity(0.3)
|
||||
: ColorTokens.outline,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Avatar
|
||||
CircleAvatar(
|
||||
radius: 24,
|
||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
||||
backgroundImage: conversation.avatarUrl != null
|
||||
? NetworkImage(conversation.avatarUrl!)
|
||||
: null,
|
||||
child: conversation.avatarUrl == null
|
||||
? Text(
|
||||
conversation.name.isNotEmpty
|
||||
? conversation.name[0].toUpperCase()
|
||||
: '?',
|
||||
style: AppTypography.actionText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
// Avatar type
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Icon(_typeIcon(), color: AppColors.primary, size: 24),
|
||||
),
|
||||
|
||||
const SizedBox(width: SpacingTokens.md),
|
||||
@@ -79,7 +94,7 @@ class ConversationTile extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
conversation.name,
|
||||
conversation.titre,
|
||||
style: AppTypography.actionText.copyWith(
|
||||
fontWeight: conversation.hasUnread
|
||||
? FontWeight.bold
|
||||
@@ -89,21 +104,20 @@ class ConversationTile extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (conversation.lastMessage != null)
|
||||
if (conversation.dernierMessageAt != null)
|
||||
Text(
|
||||
_formatDate(conversation.lastMessage!.createdAt),
|
||||
_formatDate(conversation.dernierMessageAt!),
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (conversation.lastMessage != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
conversation.lastMessage!.content,
|
||||
_apercuMessage(),
|
||||
style: AppTypography.bodyTextSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
fontWeight: conversation.hasUnread
|
||||
? FontWeight.w600
|
||||
: FontWeight.normal,
|
||||
@@ -112,7 +126,6 @@ class ConversationTile extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -120,16 +133,13 @@ class ConversationTile extends StatelessWidget {
|
||||
if (conversation.hasUnread) ...[
|
||||
const SizedBox(width: SpacingTokens.sm),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
|
||||
),
|
||||
child: Text(
|
||||
'${conversation.unreadCount}',
|
||||
'${conversation.nonLus}',
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: Colors.white,
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -33,7 +33,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
BlocBuilder<DashboardBloc, DashboardState>(
|
||||
builder: (context, state) {
|
||||
if (state is DashboardLoading) {
|
||||
return _buildLoadingList();
|
||||
return _buildLoadingList(context);
|
||||
} else if (state is DashboardLoaded || state is DashboardRefreshing) {
|
||||
final data = state is DashboardLoaded
|
||||
? state.dashboardData
|
||||
@@ -42,7 +42,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
} else if (state is DashboardError) {
|
||||
return _buildErrorState(state.message);
|
||||
}
|
||||
return _buildEmptyState();
|
||||
return _buildEmptyState(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -55,7 +55,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.history,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -71,7 +71,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
child: Text(
|
||||
'TOUT VOIR',
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -82,7 +82,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
|
||||
Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) {
|
||||
if (activities.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
return _buildEmptyState(context);
|
||||
}
|
||||
|
||||
final displayActivities = activities.take(maxItems).toList();
|
||||
@@ -142,7 +142,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
activity.userName,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
@@ -157,10 +157,10 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
),
|
||||
// Action button si disponible
|
||||
if (activity.hasAction)
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
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(
|
||||
children: List.generate(3, (index) => Column(
|
||||
children: [
|
||||
_buildLoadingItem(),
|
||||
_buildLoadingItem(context),
|
||||
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(
|
||||
children: [
|
||||
Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBorder,
|
||||
color: borderColor,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
@@ -220,7 +222,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
height: 16,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBorder,
|
||||
color: borderColor,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
@@ -229,7 +231,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
height: 12,
|
||||
width: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBorder.withOpacity(0.5),
|
||||
color: borderColor.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
@@ -238,7 +240,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
||||
height: 12,
|
||||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBorder.withOpacity(0.5),
|
||||
color: borderColor.withOpacity(0.5),
|
||||
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(
|
||||
child: Column(
|
||||
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 Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall),
|
||||
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()) {
|
||||
case 'member':
|
||||
return AppColors.success;
|
||||
case 'event':
|
||||
return AppColors.info;
|
||||
case 'contribution':
|
||||
return AppColors.brandGreen;
|
||||
return AppColors.primaryDark;
|
||||
case 'organization':
|
||||
return AppColors.primaryGreen;
|
||||
return AppColors.primary;
|
||||
case 'system':
|
||||
return AppColors.warning;
|
||||
default:
|
||||
return AppColors.textSecondaryLight;
|
||||
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
} else if (state is DashboardError) {
|
||||
return _buildErrorState(state.message);
|
||||
}
|
||||
return _buildEmptyState();
|
||||
return _buildEmptyState(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -50,7 +50,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.event_outlined,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -66,7 +66,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
child: Text(
|
||||
'TOUT VOIR',
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -77,7 +77,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
|
||||
Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) {
|
||||
if (events.isEmpty) {
|
||||
return _buildEmptyState();
|
||||
return _buildEmptyState(context);
|
||||
}
|
||||
|
||||
final displayEvents = events.take(maxItems).toList();
|
||||
@@ -99,7 +99,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
}
|
||||
|
||||
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(
|
||||
backgroundColor: Theme.of(context).cardColor,
|
||||
@@ -140,7 +140,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
),
|
||||
Row(
|
||||
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),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -188,7 +188,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
||||
child: LinearProgressIndicator(
|
||||
value: event.fillPercentage,
|
||||
minHeight: 4,
|
||||
backgroundColor: AppColors.lightBorder,
|
||||
backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.borderDark : AppColors.border,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
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(
|
||||
child: Column(
|
||||
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 Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall),
|
||||
Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||
|
||||
@@ -37,7 +37,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
} else if (state is DashboardError) {
|
||||
return _buildErrorNotifications();
|
||||
}
|
||||
return _buildEmptyNotifications();
|
||||
return _buildEmptyNotifications(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -49,7 +49,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen.withOpacity(0.05),
|
||||
color: AppColors.primary.withOpacity(0.05),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
@@ -60,7 +60,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -74,7 +74,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
'NOTIFICATIONS',
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 1.1,
|
||||
),
|
||||
@@ -121,23 +121,24 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
final notifications = _generateNotifications(context, data);
|
||||
|
||||
if (notifications.isEmpty) {
|
||||
return _buildEmptyNotifications();
|
||||
return _buildEmptyNotifications(context);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: notifications.take(maxNotifications).map((notification) {
|
||||
return _buildNotificationItem(notification);
|
||||
return _buildNotificationItem(context, notification);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNotificationItem(DashboardNotification notification) {
|
||||
Widget _buildNotificationItem(BuildContext context, DashboardNotification notification) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.lightBorder,
|
||||
color: isDark ? AppColors.borderDark : AppColors.border,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
@@ -199,7 +200,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
Text(
|
||||
notification.message,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
@@ -209,7 +210,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
Text(
|
||||
notification.timeAgo,
|
||||
style: AppTypography.subtitleSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
@@ -220,7 +221,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
notification.actionLabel!,
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
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(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.notifications_none_outlined,
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -359,7 +361,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
||||
title: 'Nouvelles activités',
|
||||
message: '${data.recentActivitiesCount} activités récentes',
|
||||
icon: Icons.fiber_new_outlined,
|
||||
color: AppColors.brandGreen,
|
||||
color: AppColors.primaryDark,
|
||||
timeAgo: '15min',
|
||||
isUrgent: false,
|
||||
actionLabel: 'Voir',
|
||||
|
||||
@@ -173,28 +173,28 @@ class _EpargnePageState extends State<EpargnePage> {
|
||||
'VUE D\'ENSEMBLE',
|
||||
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),
|
||||
Text(
|
||||
'${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),
|
||||
Text(
|
||||
'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)) ...[
|
||||
const SizedBox(height: SpacingTokens.xs),
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700),
|
||||
Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'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)
|
||||
Text(
|
||||
typeLibelle,
|
||||
style: AppTypography.subtitleSmall.copyWith(color: AppColors.textSecondaryLight),
|
||||
style: AppTypography.subtitleSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
if (dateStr != null)
|
||||
Text(
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.15),
|
||||
color: AppColors.warningContainer,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: Colors.amber.shade700, width: 1),
|
||||
border: Border.all(color: AppColors.warning, width: 1),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700),
|
||||
Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
|
||||
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)
|
||||
InfoBadge(
|
||||
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(
|
||||
'${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),
|
||||
Text(
|
||||
c.description!,
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondaryLight),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondary),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -321,8 +326,8 @@ class _EpargnePageState extends State<EpargnePage> {
|
||||
onPressed: () => _openDepot(c),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primaryGreen,
|
||||
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primary,
|
||||
),
|
||||
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,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primaryGreen,
|
||||
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primary,
|
||||
),
|
||||
child: const Text('Retrait', style: TextStyle(fontSize: 12)),
|
||||
),
|
||||
@@ -346,8 +351,8 @@ class _EpargnePageState extends State<EpargnePage> {
|
||||
onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primaryGreen,
|
||||
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||
foregroundColor: AppColors.primary,
|
||||
),
|
||||
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)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
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)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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),
|
||||
Text(
|
||||
'Aucun compte épargne',
|
||||
style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight),
|
||||
style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
'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,
|
||||
),
|
||||
],
|
||||
@@ -459,19 +468,18 @@ class _EpargnePageState extends State<EpargnePage> {
|
||||
Widget build(BuildContext context) {
|
||||
final showFab = _canCreateCompte(context);
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: const UFAppBar(
|
||||
title: 'COMPTES ÉPARGNE',
|
||||
backgroundColor: AppColors.surface,
|
||||
foregroundColor: AppColors.textPrimaryLight,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'Comptes Épargne',
|
||||
moduleGradient: ModuleColors.epargneGradient,
|
||||
),
|
||||
body: _buildBodyContent(),
|
||||
floatingActionButton: showFab
|
||||
? FloatingActionButton(
|
||||
onPressed: _openCreerCompte,
|
||||
tooltip: 'Créer un compte épargne pour un membre',
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
child: const Icon(Icons.add),
|
||||
)
|
||||
: null,
|
||||
|
||||
@@ -38,9 +38,10 @@ class _BudgetsListView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: ColorTokens.background,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'BUDGETS',
|
||||
title: 'Budgets',
|
||||
moduleGradient: ModuleColors.financeWorkflowGradient,
|
||||
automaticallyImplyLeading: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -57,7 +58,9 @@ class _BudgetsListView extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: BlocConsumer<BudgetBloc, BudgetState>(
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: BlocConsumer<BudgetBloc, BudgetState>(
|
||||
listener: (context, state) {
|
||||
if (state is BudgetCreated) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -98,13 +101,14 @@ class _BudgetsListView extends StatelessWidget {
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
SnackbarHelper.showNotImplemented(context, 'Création de budget');
|
||||
},
|
||||
icon: const Icon(Icons.add),
|
||||
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) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
color: AppColors.lightBackground,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: Wrap(
|
||||
spacing: SpacingTokens.sm,
|
||||
runSpacing: SpacingTokens.sm,
|
||||
@@ -178,6 +182,10 @@ class _BudgetsListView extends StatelessWidget {
|
||||
}
|
||||
|
||||
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(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -185,21 +193,25 @@ class _BudgetsListView extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.account_balance_wallet_outlined,
|
||||
size: 48,
|
||||
color: AppColors.textSecondaryLight.withOpacity(0.5),
|
||||
color: textSecondary.withOpacity(0.5),
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.lg),
|
||||
Text(
|
||||
message,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorState(BuildContext context, String message) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -213,7 +225,7 @@ class _BudgetsListView extends StatelessWidget {
|
||||
Text(
|
||||
message,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -274,14 +286,14 @@ class _BudgetCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Color _getStatusColor(BudgetStatus status) {
|
||||
Color _getStatusColor(BudgetStatus status, {bool isDark = false}) {
|
||||
switch (status) {
|
||||
case BudgetStatus.draft:
|
||||
return AppColors.textSecondaryLight;
|
||||
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
case BudgetStatus.active:
|
||||
return AppColors.brandGreen;
|
||||
return AppColors.primaryDark;
|
||||
case BudgetStatus.closed:
|
||||
return AppColors.textSecondaryLight;
|
||||
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
case BudgetStatus.cancelled:
|
||||
return AppColors.error;
|
||||
}
|
||||
@@ -302,17 +314,20 @@ class _BudgetCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final currencyFormat = NumberFormat.currency(symbol: budget.currency);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
border: Border.all(
|
||||
color: isDark ? AppColors.borderDark : AppColors.border,
|
||||
),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x0A000000),
|
||||
color: AppColors.shadow,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
@@ -335,13 +350,13 @@ class _BudgetCard extends StatelessWidget {
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(budget.status).withOpacity(0.1),
|
||||
color: _getStatusColor(budget.status, isDark: isDark).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
),
|
||||
child: Text(
|
||||
_getStatusLabel(budget.status),
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: _getStatusColor(budget.status),
|
||||
color: _getStatusColor(budget.status, isDark: isDark),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -368,7 +383,7 @@ class _BudgetCard extends StatelessWidget {
|
||||
Text(
|
||||
currencyFormat.format(budget.totalPlanned),
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -389,7 +404,7 @@ class _BudgetCard extends StatelessWidget {
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: budget.isOverBudget
|
||||
? AppColors.error
|
||||
: AppColors.brandGreen,
|
||||
: AppColors.primaryDark,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -401,10 +416,10 @@ class _BudgetCard extends StatelessWidget {
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
LinearProgressIndicator(
|
||||
value: budget.realizationRate / 100,
|
||||
backgroundColor: AppColors.lightBorder,
|
||||
backgroundColor: isDark ? AppColors.borderDark : AppColors.border,
|
||||
color: budget.isOverBudget
|
||||
? AppColors.error
|
||||
: AppColors.brandGreen,
|
||||
: AppColors.primaryDark,
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.xs),
|
||||
Text(
|
||||
|
||||
@@ -39,9 +39,10 @@ class _PendingApprovalsView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: ColorTokens.background,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'APPROBATIONS EN ATTENTE',
|
||||
title: 'Approbations en attente',
|
||||
moduleGradient: ModuleColors.financeWorkflowGradient,
|
||||
automaticallyImplyLeading: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -155,6 +156,7 @@ class _PendingApprovalsView extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildErrorState(BuildContext context, String message) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -168,7 +170,7 @@ class _PendingApprovalsView extends StatelessWidget {
|
||||
Text(
|
||||
message,
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -188,6 +190,10 @@ class _PendingApprovalsView extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -201,19 +207,21 @@ class _PendingApprovalsView extends StatelessWidget {
|
||||
Text(
|
||||
'Aucune approbation en attente',
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: SpacingTokens.sm),
|
||||
Text(
|
||||
'Toutes les transactions sont approuvées',
|
||||
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) {
|
||||
case ApprovalLevel.none:
|
||||
return AppColors.textSecondaryLight;
|
||||
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||
case ApprovalLevel.level1:
|
||||
return AppColors.brandGreen;
|
||||
return AppColors.primaryDark;
|
||||
case ApprovalLevel.level2:
|
||||
return AppColors.warning;
|
||||
case ApprovalLevel.level3:
|
||||
@@ -263,17 +271,18 @@ class _ApprovalCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final currencyFormat = NumberFormat.currency(symbol: approval.currency);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x0A000000),
|
||||
color: AppColors.shadow,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
@@ -296,13 +305,13 @@ class _ApprovalCard extends StatelessWidget {
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getLevelColor(approval.requiredLevel).withOpacity(0.1),
|
||||
color: _getLevelColor(approval.requiredLevel, isDark: isDark).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
),
|
||||
child: Text(
|
||||
'Niveau ${approval.requiredApprovals}',
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: _getLevelColor(approval.requiredLevel),
|
||||
color: _getLevelColor(approval.requiredLevel, isDark: isDark),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -313,7 +322,7 @@ class _ApprovalCard extends StatelessWidget {
|
||||
Text(
|
||||
currencyFormat.format(approval.amount),
|
||||
style: AppTypography.headerSmall.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -323,7 +332,7 @@ class _ApprovalCard extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
@@ -340,7 +349,7 @@ class _ApprovalCard extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 16,
|
||||
color: AppColors.textSecondaryLight,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
|
||||
@@ -53,6 +53,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
||||
|
||||
return AlertDialog(
|
||||
@@ -72,9 +73,9 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBackground,
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -88,7 +89,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
||||
'Montant',
|
||||
currencyFormat.format(widget.approval.amount),
|
||||
valueStyle: AppTypography.actionText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -145,7 +146,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
||||
label: const Text('Approuver'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.success,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -53,6 +53,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
||||
|
||||
return AlertDialog(
|
||||
@@ -75,9 +76,9 @@ class _RejectDialogState extends State<RejectDialog> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightBackground,
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -91,7 +92,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
||||
'Montant',
|
||||
currencyFormat.format(widget.approval.amount),
|
||||
valueStyle: AppTypography.actionText.copyWith(
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -141,7 +142,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
||||
label: const Text('Rejeter'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.error,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: AppColors.onError,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -39,8 +39,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: const UFAppBar(title: 'AIDE & SUPPORT'),
|
||||
body: SingleChildScrollView(
|
||||
appBar: UFAppBar(
|
||||
title: 'Aide & Support',
|
||||
moduleGradient: ModuleColors.supportGradient,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -75,6 +80,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,12 +92,12 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.help_outline,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
@@ -113,13 +119,17 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
|
||||
/// Section de recherche
|
||||
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(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.search, color: AppColors.primaryGreen, size: 18),
|
||||
const Icon(Icons.search, color: AppColors.primary, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'RECHERCHER DANS L\'AIDE',
|
||||
@@ -130,9 +140,9 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.lightSurface,
|
||||
color: bgSearch,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.lightBorder),
|
||||
border: Border.all(color: borderSearch),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
@@ -141,14 +151,14 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Une question, un mot-clé...',
|
||||
hintStyle: AppTypography.subtitleSmall,
|
||||
prefixIcon: const Icon(Icons.search, color: AppColors.textSecondaryLight, size: 18),
|
||||
prefixIcon: Icon(Icons.search, color: iconColor, size: 18),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() => _searchQuery = '');
|
||||
},
|
||||
icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight, size: 18),
|
||||
icon: Icon(Icons.clear, color: iconColor, size: 18),
|
||||
)
|
||||
: null,
|
||||
border: InputBorder.none,
|
||||
@@ -180,7 +190,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
'CHAT',
|
||||
'Support Direct',
|
||||
Icons.chat_bubble_outline,
|
||||
AppColors.primaryGreen,
|
||||
AppColors.primary,
|
||||
() => _startLiveChat(),
|
||||
),
|
||||
),
|
||||
@@ -286,16 +296,16 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent,
|
||||
color: isSelected ? AppColors.primary.withOpacity(0.1) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
|
||||
color: isSelected ? AppColors.primary : AppColors.border,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label.toUpperCase(),
|
||||
style: AppTypography.badgeText.copyWith(
|
||||
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
||||
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||
fontSize: 9,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
@@ -338,11 +348,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
),
|
||||
leading: Icon(
|
||||
faq['icon'] as IconData,
|
||||
color: AppColors.primaryGreen,
|
||||
color: AppColors.primary,
|
||||
size: 18,
|
||||
),
|
||||
iconColor: AppColors.primaryGreen,
|
||||
collapsedIconColor: AppColors.textSecondaryLight,
|
||||
iconColor: AppColors.primary,
|
||||
collapsedIconColor: Theme.of(context).brightness == Brightness.dark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondary,
|
||||
shape: const RoundedRectangleBorder(side: BorderSide.none),
|
||||
children: [
|
||||
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('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')),
|
||||
],
|
||||
);
|
||||
@@ -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
|
||||
Widget _buildContactSection() {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return CoreCard(
|
||||
backgroundColor: AppColors.primaryGreen, // Correction: color -> backgroundColor
|
||||
backgroundColor: AppColors.primary, // Correction: color -> backgroundColor
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32),
|
||||
@@ -426,8 +445,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
child: UFPrimaryButton(
|
||||
label: 'EMAIL', // Correction: text -> label
|
||||
onPressed: () => _contactByEmail(),
|
||||
backgroundColor: Colors.white,
|
||||
textColor: AppColors.primaryGreen,
|
||||
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||
textColor: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -436,7 +455,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
label: 'CHAT', // Correction: text -> label
|
||||
onPressed: () => _startLiveChat(),
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
textColor: Colors.white,
|
||||
textColor: AppColors.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -516,8 +535,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
_contactByEmail();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Envoyer un email'),
|
||||
),
|
||||
@@ -548,7 +567,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.error,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: AppColors.onError,
|
||||
),
|
||||
child: const Text('Signaler'),
|
||||
),
|
||||
@@ -578,8 +597,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
_launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Envoyer'),
|
||||
),
|
||||
@@ -613,8 +632,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
_launchUrl('https://docs.unionflow.com/$guideId');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Voir en ligne'),
|
||||
),
|
||||
@@ -644,8 +663,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
||||
_contactByEmail();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
),
|
||||
child: const Text('Contacter le support'),
|
||||
),
|
||||
|
||||
@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
|
||||
import '../../bloc/org_types_bloc.dart';
|
||||
import '../../domain/entities/type_reference_entity.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';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.lightBackground,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
title: const Text('Nouvelle Organisation'),
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'Nouvelle Organisation',
|
||||
moduleGradient: ModuleColors.organisationsGradient,
|
||||
actions: [
|
||||
TextButton(
|
||||
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) {
|
||||
if (state is OrganizationCreated) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -128,7 +130,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
||||
Navigator.of(context).pop(true);
|
||||
} else if (state is OrganizationsError) {
|
||||
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) {
|
||||
return Container(
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Icon(icon, size: 16, color: AppColors.primaryGreen),
|
||||
Icon(icon, size: 16, color: AppColors.primary),
|
||||
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),
|
||||
...children,
|
||||
@@ -237,9 +240,18 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
||||
onTap: () => _pickDateFondation(context),
|
||||
child: InputDecorator(
|
||||
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',
|
||||
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)),
|
||||
value: _organisationPublique,
|
||||
onChanged: (v) => setState(() => _organisationPublique = v),
|
||||
activeColor: AppColors.primaryGreen,
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
SwitchListTile(
|
||||
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)),
|
||||
value: _accepteNouveauxMembres,
|
||||
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)),
|
||||
value: _cotisationObligatoire,
|
||||
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
||||
activeColor: AppColors.primaryGreen,
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
if (_cotisationObligatoire) ...[
|
||||
const SizedBox(height: 8),
|
||||
@@ -451,8 +463,8 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('Créer l\'organisation'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
@@ -466,7 +478,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
||||
icon: const Icon(Icons.cancel),
|
||||
label: const Text('Annuler'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.textSecondaryLight,
|
||||
foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
|
||||
import '../../bloc/org_types_bloc.dart';
|
||||
import '../../domain/entities/type_reference_entity.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';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.lightBackground,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
title: const Text('Modifier Organisation'),
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: UFAppBar(
|
||||
title: 'Modifier Organisation',
|
||||
moduleGradient: ModuleColors.organisationsGradient,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _hasChanges() ? _saveChanges : null,
|
||||
@@ -212,7 +212,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
||||
Navigator.of(context).pop(true);
|
||||
} else if (state is OrganizationsError) {
|
||||
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) {
|
||||
return Container(
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Icon(icon, size: 16, color: AppColors.primaryGreen),
|
||||
Icon(icon, size: 16, color: AppColors.primary),
|
||||
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),
|
||||
...children,
|
||||
@@ -326,9 +326,18 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
||||
onTap: () => _pickDateFondation(context),
|
||||
child: InputDecorator(
|
||||
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',
|
||||
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)),
|
||||
value: _organisationPublique,
|
||||
onChanged: (v) => setState(() => _organisationPublique = v),
|
||||
activeColor: AppColors.primaryGreen,
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
SwitchListTile(
|
||||
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)),
|
||||
value: _accepteNouveauxMembres,
|
||||
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)),
|
||||
value: _cotisationObligatoire,
|
||||
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
||||
activeColor: AppColors.primaryGreen,
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
if (_cotisationObligatoire) ...[
|
||||
const SizedBox(height: 8),
|
||||
@@ -559,12 +568,15 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
||||
];
|
||||
|
||||
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: [
|
||||
Icon(icon, size: 18, color: AppColors.textSecondaryLight),
|
||||
Icon(icon, size: 18, color: textSecondary),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)),
|
||||
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)),
|
||||
Text(label, style: TextStyle(fontSize: 11, color: textSecondary)),
|
||||
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),
|
||||
label: const Text('Enregistrer les modifications'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryGreen,
|
||||
foregroundColor: Colors.white,
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: AppColors.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
@@ -592,7 +604,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
||||
onPressed: _showDiscardDialog,
|
||||
icon: const Icon(Icons.cancel),
|
||||
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')),
|
||||
ElevatedButton(
|
||||
onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); },
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||
child: const Text('Abandonner', style: TextStyle(color: Colors.white)),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
|
||||
child: const Text('Abandonner', style: TextStyle(color: AppColors.onPrimary)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user