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/design_system/unionflow_design_system.dart';
|
||||||
import '../../../../shared/widgets/core_card.dart';
|
import '../../../../shared/widgets/core_card.dart';
|
||||||
import '../../../../shared/widgets/info_badge.dart';
|
import '../../../../shared/widgets/info_badge.dart';
|
||||||
|
import '../../../../shared/widgets/powered_by_lions_dev.dart';
|
||||||
|
|
||||||
|
|
||||||
/// Page À propos - UnionFlow Mobile
|
/// Page À propos - UnionFlow Mobile
|
||||||
@@ -40,7 +41,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'À PROPOS',
|
title: 'À propos',
|
||||||
|
moduleGradient: ModuleColors.systemeGradient,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.share_outlined, size: 20),
|
icon: const Icon(Icons.share_outlined, size: 20),
|
||||||
@@ -48,7 +50,9 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -75,27 +79,33 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
|
|
||||||
// Support et contact
|
// Support et contact
|
||||||
_buildSupportSection(),
|
_buildSupportSection(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Branding « Powered by Lions Dev » (logo adaptatif dark/light)
|
||||||
|
const Center(child: PoweredByLionsDev()),
|
||||||
const SizedBox(height: 80),
|
const SizedBox(height: 80),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Header épuré
|
/// Header épuré
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.account_balance,
|
Icons.account_balance,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -112,8 +122,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
if (_packageInfo != null)
|
if (_packageInfo != null)
|
||||||
InfoBadge(
|
InfoBadge(
|
||||||
text: 'VERSION ${_packageInfo!.version}',
|
text: 'VERSION ${_packageInfo!.version}',
|
||||||
backgroundColor: AppColors.lightSurface,
|
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||||
textColor: AppColors.textSecondaryLight,
|
textColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -149,7 +159,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -175,16 +185,17 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildTeamMember(
|
_buildTeamMember(
|
||||||
'UnionFlow Team',
|
'Lions Dev',
|
||||||
'Architecture & Dev',
|
'Intégrateur de solutions digitales innovantes — lions.dev',
|
||||||
Icons.code,
|
Icons.code,
|
||||||
AppColors.primaryGreen,
|
AppColors.primary,
|
||||||
|
onTap: () => _launchUrl('https://www.lions.dev'),
|
||||||
),
|
),
|
||||||
_buildTeamMember(
|
_buildTeamMember(
|
||||||
'Design System',
|
'UnionFlow',
|
||||||
'UI / UX Focus',
|
'Mouvement d\'entraide & solidarité',
|
||||||
Icons.design_services,
|
Icons.account_balance,
|
||||||
AppColors.brandGreenLight,
|
AppColors.primaryLight,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -192,8 +203,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Membre de l'équipe
|
/// Membre de l'équipe
|
||||||
Widget _buildTeamMember(String name, String role, IconData icon, Color color) {
|
Widget _buildTeamMember(String name, String role, IconData icon, Color color, {VoidCallback? onTap}) {
|
||||||
return Padding(
|
final content = Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -215,9 +226,17 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (onTap != null)
|
||||||
|
Icon(Icons.open_in_new, color: color, size: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (onTap == null) return content;
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: content,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Section fonctionnalités
|
/// Section fonctionnalités
|
||||||
@@ -231,8 +250,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold, letterSpacing: 1.1),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primaryGreen),
|
_buildFeatureItem('Membres', 'Administration complète', Icons.people, AppColors.primary),
|
||||||
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.brandGreenLight),
|
_buildFeatureItem('Organisations', 'Syndicats & Fédérations', Icons.business, AppColors.primaryLight),
|
||||||
_buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success),
|
_buildFeatureItem('Événements', 'Planification & Suivi', Icons.event, AppColors.success),
|
||||||
_buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning),
|
_buildFeatureItem('Sécurité', 'Auth Keycloak OIDC', Icons.security, AppColors.warning),
|
||||||
],
|
],
|
||||||
@@ -284,6 +303,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
|
|
||||||
/// Élément de lien
|
/// Élément de lien
|
||||||
Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
Widget _buildLinkItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -291,7 +311,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
Icon(icon, color: AppColors.primary, size: 16),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -302,7 +322,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14),
|
Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -327,7 +347,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text('© 2024 UNIONFLOW', style: AppTypography.badgeText),
|
Text('© 2024 UNIONFLOW', style: AppTypography.badgeText),
|
||||||
Text('Fait avec ❤️ pour les syndicats', style: AppTypography.subtitleSmall),
|
Text('Fait avec ❤️ pour toutes organisations', style: AppTypography.subtitleSmall),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -338,6 +358,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
|
|
||||||
/// Élément de support
|
/// Élément de support
|
||||||
Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
Widget _buildSupportItem(String title, String subtitle, IconData icon, VoidCallback onTap) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -356,7 +377,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 14),
|
Icon(Icons.chevron_right, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -398,8 +419,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
_launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile');
|
_launchUrl('mailto:support@unionflow.com?subject=Rapport de bug - UnionFlow Mobile');
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Envoyer un email'),
|
child: const Text('Envoyer un email'),
|
||||||
),
|
),
|
||||||
@@ -429,8 +450,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
_launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile');
|
_launchUrl('mailto:support@unionflow.com?subject=Suggestion d\'amélioration - UnionFlow Mobile');
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Envoyer une suggestion'),
|
child: const Text('Envoyer une suggestion'),
|
||||||
),
|
),
|
||||||
@@ -460,8 +481,8 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
_launchStoreForRating();
|
_launchStoreForRating();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Évaluer maintenant'),
|
child: const Text('Évaluer maintenant'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
|
|
||||||
import '../bloc/auth_bloc.dart';
|
import '../bloc/auth_bloc.dart';
|
||||||
import '../../../../core/config/environment.dart';
|
import '../../../../core/config/environment.dart';
|
||||||
|
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||||
|
import '../../../../shared/widgets/powered_by_lions_dev.dart';
|
||||||
|
|
||||||
/// UnionFlow — Écran de connexion premium
|
// ── Couleurs signature ────────────────────────────────────────────────────────
|
||||||
/// Gradient forêt + glassmorphism + animations + biométrie + remember me
|
const _kGradTop = Color(0xFF1D4ED8);
|
||||||
|
const _kGradMid = Color(0xFF2563EB);
|
||||||
|
const _kGradBot = Color(0xFF7616E8);
|
||||||
|
const _kPrimaryBlue = Color(0xFF2563EB);
|
||||||
|
|
||||||
|
/// UnionFlow — Écran de connexion.
|
||||||
|
/// Gradient signature (#1D4ED8 → #2563EB → #7616E8) + glassmorphism.
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
|
|
||||||
@@ -19,37 +27,33 @@ class LoginPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
||||||
late final AnimationController _fadeController;
|
late final AnimationController _fadeCtrl;
|
||||||
late final AnimationController _slideController;
|
late final AnimationController _slideCtrl;
|
||||||
late final Animation<double> _fadeAnim;
|
late final Animation<double> _fadeAnim;
|
||||||
late final Animation<Offset> _slideAnim;
|
late final Animation<Offset> _slideAnim;
|
||||||
|
|
||||||
bool _biometricAvailable = false;
|
bool _biometricAvailable = false;
|
||||||
|
|
||||||
final _localAuth = LocalAuthentication();
|
final _localAuth = LocalAuthentication();
|
||||||
|
|
||||||
static const _gradTop = Color(0xFF1B5E20);
|
|
||||||
static const _gradMid = Color(0xFF2E7D32);
|
|
||||||
static const _gradBot = Color(0xFF388E3C);
|
|
||||||
static const _primaryGreen = Color(0xFF2E7D32);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fadeController = AnimationController(vsync: this, duration: const Duration(milliseconds: 900));
|
_fadeCtrl = AnimationController(
|
||||||
_slideController = AnimationController(vsync: this, duration: const Duration(milliseconds: 750));
|
vsync: this, duration: const Duration(milliseconds: 900));
|
||||||
_fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut);
|
_slideCtrl = AnimationController(
|
||||||
|
vsync: this, duration: const Duration(milliseconds: 750));
|
||||||
|
_fadeAnim = CurvedAnimation(parent: _fadeCtrl, curve: Curves.easeOut);
|
||||||
_slideAnim = Tween<Offset>(begin: const Offset(0, 0.12), end: Offset.zero)
|
_slideAnim = Tween<Offset>(begin: const Offset(0, 0.12), end: Offset.zero)
|
||||||
.animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic));
|
.animate(CurvedAnimation(parent: _slideCtrl, curve: Curves.easeOutCubic));
|
||||||
_fadeController.forward();
|
_fadeCtrl.forward();
|
||||||
_slideController.forward();
|
_slideCtrl.forward();
|
||||||
_checkBiometrics();
|
_checkBiometrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_fadeController.dispose();
|
_fadeCtrl.dispose();
|
||||||
_slideController.dispose();
|
_slideCtrl.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +71,7 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
localizedReason: 'Authentifiez-vous pour accéder à UnionFlow',
|
localizedReason: 'Authentifiez-vous pour accéder à UnionFlow',
|
||||||
options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false),
|
options: const AuthenticationOptions(stickyAuth: true, biometricOnly: false),
|
||||||
);
|
);
|
||||||
if (ok && mounted) {
|
if (ok && mounted) context.read<AuthBloc>().add(const AuthStatusChecked());
|
||||||
context.read<AuthBloc>().add(const AuthStatusChecked());
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,54 +83,41 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
'&response_type=code&scope=openid&kc_action=reset_credentials',
|
'&response_type=code&scope=openid&kc_action=reset_credentials',
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
if (await canLaunchUrl(url)) await launchUrl(url, mode: LaunchMode.externalApplication);
|
if (await canLaunchUrl(url)) {
|
||||||
|
await launchUrl(url, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLogin() {
|
void _onAuthStateChanged(BuildContext context, AuthState state) {
|
||||||
context.read<AuthBloc>().add(const AuthLoginRequested());
|
if (state is AuthError) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: BlocConsumer<AuthBloc, AuthState>(
|
body: BlocConsumer<AuthBloc, AuthState>(
|
||||||
listener: (context, state) {
|
listener: _onAuthStateChanged,
|
||||||
if (state is AuthError) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(state.message),
|
|
||||||
backgroundColor: const Color(0xFFB71C1C),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final isLoading = state is AuthLoading;
|
final isLoading = state is AuthLoading;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
// Gradient background
|
const _GradientBackground(),
|
||||||
Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [_gradTop, _gradMid, _gradBot],
|
|
||||||
stops: [0.0, 0.55, 1.0],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Subtle hexagon pattern overlay
|
|
||||||
const Positioned.fill(child: _HexPatternOverlay()),
|
const Positioned.fill(child: _HexPatternOverlay()),
|
||||||
|
|
||||||
// Content
|
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 28, vertical: 24),
|
||||||
child: FadeTransition(
|
child: FadeTransition(
|
||||||
opacity: _fadeAnim,
|
opacity: _fadeAnim,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
@@ -136,9 +125,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildLogoSection(),
|
const _LoginLogoSection(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildGlassCard(isLoading),
|
_LoginGlassCard(
|
||||||
|
isLoading: isLoading,
|
||||||
|
onLogin: () => context
|
||||||
|
.read<AuthBloc>()
|
||||||
|
.add(const AuthLoginRequested()),
|
||||||
|
onForgotPassword: _openForgotPassword,
|
||||||
|
biometricAvailable: _biometricAvailable,
|
||||||
|
onBiometric: _authenticateBiometric,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Branding « Powered by Lions Dev » — fond toujours dark
|
||||||
|
const PoweredByLionsDev(forceBrightness: Brightness.dark),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -152,13 +152,43 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildLogoSection() {
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Widgets extraits
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class _GradientBackground extends StatelessWidget {
|
||||||
|
const _GradientBackground();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [_kGradTop, _kGradMid, _kGradBot],
|
||||||
|
stops: [0.0, 0.55, 1.0],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox.expand(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginLogoSection extends StatelessWidget {
|
||||||
|
const _LoginLogoSection();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
CustomPaint(
|
Image.asset(
|
||||||
size: const Size(48, 48),
|
'assets/images/unionflow-logo.png',
|
||||||
painter: _HexLogoMark(),
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
@@ -175,7 +205,6 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
'Gérez votre organisation avec sérénité',
|
'Gérez votre organisation avec sérénité',
|
||||||
style: GoogleFonts.roboto(
|
style: GoogleFonts.roboto(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Colors.white.withOpacity(0.78),
|
color: Colors.white.withOpacity(0.78),
|
||||||
letterSpacing: 0.2,
|
letterSpacing: 0.2,
|
||||||
),
|
),
|
||||||
@@ -184,19 +213,35 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildGlassCard(bool isLoading) {
|
class _LoginGlassCard extends StatelessWidget {
|
||||||
|
const _LoginGlassCard({
|
||||||
|
required this.isLoading,
|
||||||
|
required this.onLogin,
|
||||||
|
required this.onForgotPassword,
|
||||||
|
required this.biometricAvailable,
|
||||||
|
required this.onBiometric,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool isLoading;
|
||||||
|
final VoidCallback onLogin;
|
||||||
|
final VoidCallback onForgotPassword;
|
||||||
|
final bool biometricAvailable;
|
||||||
|
final VoidCallback onBiometric;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.11),
|
color: Colors.white.withOpacity(0.11),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Colors.white.withOpacity(0.22),
|
color: Colors.white.withOpacity(0.22), width: 1.5),
|
||||||
width: 1.5,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.18),
|
color: AppColors.shadowStrong,
|
||||||
blurRadius: 40,
|
blurRadius: 40,
|
||||||
offset: const Offset(0, 10),
|
offset: const Offset(0, 10),
|
||||||
),
|
),
|
||||||
@@ -225,12 +270,10 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Forgot password
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: _openForgotPassword,
|
onPressed: onForgotPassword,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
minimumSize: Size.zero,
|
minimumSize: Size.zero,
|
||||||
@@ -249,17 +292,20 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
if (isLoading)
|
||||||
// Login button
|
const Center(
|
||||||
isLoading
|
child: CircularProgressIndicator(
|
||||||
? const Center(child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2.5))
|
color: Colors.white, strokeWidth: 2.5),
|
||||||
: ElevatedButton(
|
)
|
||||||
onPressed: _onLogin,
|
else
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: onLogin,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||||
foregroundColor: _primaryGreen,
|
foregroundColor: _kPrimaryBlue,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8)),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -267,25 +313,26 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
style: GoogleFonts.roboto(
|
style: GoogleFonts.roboto(
|
||||||
fontSize: 15.5,
|
fontSize: 15.5,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: _primaryGreen,
|
color: _kPrimaryBlue,
|
||||||
letterSpacing: 0.2,
|
letterSpacing: 0.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (biometricAvailable) ...[
|
||||||
// Biometric
|
|
||||||
if (_biometricAvailable) ...[
|
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
Center(
|
Center(
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: _authenticateBiometric,
|
onPressed: onBiometric,
|
||||||
icon: const Icon(Icons.fingerprint_rounded, color: Colors.white60, size: 22),
|
icon: const Icon(Icons.fingerprint_rounded,
|
||||||
|
color: Colors.white60, size: 22),
|
||||||
label: Text(
|
label: Text(
|
||||||
'Connexion biométrique',
|
'Connexion biométrique',
|
||||||
style: GoogleFonts.roboto(fontSize: 12.5, color: Colors.white60),
|
style: GoogleFonts.roboto(
|
||||||
|
fontSize: 12.5, color: Colors.white60),
|
||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -300,14 +347,12 @@ class _LoginPageState extends State<LoginPage> with TickerProviderStateMixin {
|
|||||||
// Painters
|
// Painters
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Motif hexagonal en overlay sur le dégradé (opacité 4%)
|
|
||||||
class _HexPatternOverlay extends StatelessWidget {
|
class _HexPatternOverlay extends StatelessWidget {
|
||||||
const _HexPatternOverlay();
|
const _HexPatternOverlay();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) =>
|
||||||
return CustomPaint(painter: _HexPatternPainter());
|
CustomPaint(painter: _HexPatternPainter());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HexPatternPainter extends CustomPainter {
|
class _HexPatternPainter extends CustomPainter {
|
||||||
@@ -324,8 +369,11 @@ class _HexPatternPainter extends CustomPainter {
|
|||||||
|
|
||||||
for (double row = -1; row * vSpace < size.height + vSpace; row++) {
|
for (double row = -1; row * vSpace < size.height + vSpace; row++) {
|
||||||
final offset = (row % 2 == 0) ? 0.0 : hSpace / 2;
|
final offset = (row % 2 == 0) ? 0.0 : hSpace / 2;
|
||||||
for (double col = -1; col * hSpace - offset < size.width + hSpace; col++) {
|
for (double col = -1;
|
||||||
_hexagon(canvas, paint, Offset(col * hSpace + offset, row * vSpace), r);
|
col * hSpace - offset < size.width + hSpace;
|
||||||
|
col++) {
|
||||||
|
_hexagon(canvas, paint,
|
||||||
|
Offset(col * hSpace + offset, row * vSpace), r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,8 +382,9 @@ class _HexPatternPainter extends CustomPainter {
|
|||||||
final path = Path();
|
final path = Path();
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
final a = (i * 60 - 30) * math.pi / 180;
|
final a = (i * 60 - 30) * math.pi / 180;
|
||||||
final p = Offset(center.dx + r * math.cos(a), center.dy + r * math.sin(a));
|
final p = Offset(
|
||||||
if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy);
|
center.dx + r * math.cos(a), center.dy + r * math.sin(a));
|
||||||
|
i == 0 ? path.moveTo(p.dx, p.dy) : path.lineTo(p.dx, p.dy);
|
||||||
}
|
}
|
||||||
path.close();
|
path.close();
|
||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
@@ -344,57 +393,3 @@ class _HexPatternPainter extends CustomPainter {
|
|||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logo hexagonal avec initiales "UF"
|
|
||||||
class _HexLogoMark extends CustomPainter {
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final cx = size.width / 2;
|
|
||||||
final cy = size.height / 2;
|
|
||||||
final r = size.width / 2 - 2;
|
|
||||||
|
|
||||||
// Fond hexagonal blanc semi-transparent
|
|
||||||
final bgPaint = Paint()
|
|
||||||
..color = Colors.white.withOpacity(0.18)
|
|
||||||
..style = PaintingStyle.fill;
|
|
||||||
final borderPaint = Paint()
|
|
||||||
..color = Colors.white.withOpacity(0.6)
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = 1.8;
|
|
||||||
|
|
||||||
final path = Path();
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
final a = (i * 60 - 30) * math.pi / 180;
|
|
||||||
final p = Offset(cx + r * math.cos(a), cy + r * math.sin(a));
|
|
||||||
if (i == 0) path.moveTo(p.dx, p.dy); else path.lineTo(p.dx, p.dy);
|
|
||||||
}
|
|
||||||
path.close();
|
|
||||||
canvas.drawPath(path, bgPaint);
|
|
||||||
canvas.drawPath(path, borderPaint);
|
|
||||||
|
|
||||||
// Lignes stylisées "UF" dessinées (plus propre qu'un TextPainter dans un painter)
|
|
||||||
final linePaint = Paint()
|
|
||||||
..color = Colors.white
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = 2.8
|
|
||||||
..strokeCap = StrokeCap.round
|
|
||||||
..strokeJoin = StrokeJoin.round;
|
|
||||||
|
|
||||||
// Lettre U
|
|
||||||
final uPath = Path()
|
|
||||||
..moveTo(cx - 13, cy - 10)
|
|
||||||
..lineTo(cx - 13, cy + 5)
|
|
||||||
..quadraticBezierTo(cx - 13, cy + 12, cx - 7, cy + 12)
|
|
||||||
..quadraticBezierTo(cx - 1, cy + 12, cx - 1, cy + 5)
|
|
||||||
..lineTo(cx - 1, cy - 10);
|
|
||||||
canvas.drawPath(uPath, linePaint);
|
|
||||||
|
|
||||||
// Lettre F
|
|
||||||
canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 3, cy + 12), linePaint);
|
|
||||||
canvas.drawLine(Offset(cx + 3, cy - 10), Offset(cx + 13, cy - 10), linePaint);
|
|
||||||
canvas.drawLine(Offset(cx + 3, cy + 1), Offset(cx + 11, cy + 1), linePaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
/// Widget tuile de conversation
|
/// Widget tuile de conversation v4
|
||||||
library conversation_tile;
|
library conversation_tile;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||||
import '../../domain/entities/conversation.dart';
|
import '../../domain/entities/conversation.dart';
|
||||||
|
|
||||||
class ConversationTile extends StatelessWidget {
|
class ConversationTile extends StatelessWidget {
|
||||||
final Conversation conversation;
|
final ConversationSummary conversation;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
|
||||||
const ConversationTile({
|
const ConversationTile({
|
||||||
@@ -31,41 +32,55 @@ class ConversationTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData _typeIcon() {
|
||||||
|
switch (conversation.typeConversation) {
|
||||||
|
case 'DIRECTE':
|
||||||
|
return Icons.person_outline;
|
||||||
|
case 'ROLE_CANAL':
|
||||||
|
return Icons.account_circle_outlined;
|
||||||
|
case 'GROUPE':
|
||||||
|
return Icons.group_outlined;
|
||||||
|
default:
|
||||||
|
return Icons.chat_bubble_outline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _apercuMessage() {
|
||||||
|
if (conversation.dernierMessageApercu == null) return 'Aucun message';
|
||||||
|
final type = conversation.dernierMessageType ?? 'TEXTE';
|
||||||
|
if (type == 'VOCAL') return '🎙️ Note vocale';
|
||||||
|
if (type == 'IMAGE') return '📷 Image';
|
||||||
|
return conversation.dernierMessageApercu!;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ColorTokens.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: conversation.hasUnread
|
color: conversation.hasUnread
|
||||||
? AppColors.primaryGreen.withOpacity(0.3)
|
? AppColors.primary.withOpacity(0.3)
|
||||||
: ColorTokens.outline,
|
: ColorTokens.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// Avatar
|
// Avatar type
|
||||||
CircleAvatar(
|
Container(
|
||||||
radius: 24,
|
width: 48,
|
||||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
height: 48,
|
||||||
backgroundImage: conversation.avatarUrl != null
|
decoration: BoxDecoration(
|
||||||
? NetworkImage(conversation.avatarUrl!)
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
: null,
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
|
||||||
child: conversation.avatarUrl == null
|
|
||||||
? Text(
|
|
||||||
conversation.name.isNotEmpty
|
|
||||||
? conversation.name[0].toUpperCase()
|
|
||||||
: '?',
|
|
||||||
style: AppTypography.actionText.copyWith(
|
|
||||||
color: AppColors.primaryGreen,
|
|
||||||
),
|
),
|
||||||
)
|
child: Icon(_typeIcon(), color: AppColors.primary, size: 24),
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: SpacingTokens.md),
|
const SizedBox(width: SpacingTokens.md),
|
||||||
@@ -79,7 +94,7 @@ class ConversationTile extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
conversation.name,
|
conversation.titre,
|
||||||
style: AppTypography.actionText.copyWith(
|
style: AppTypography.actionText.copyWith(
|
||||||
fontWeight: conversation.hasUnread
|
fontWeight: conversation.hasUnread
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
@@ -89,21 +104,20 @@ class ConversationTile extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (conversation.lastMessage != null)
|
if (conversation.dernierMessageAt != null)
|
||||||
Text(
|
Text(
|
||||||
_formatDate(conversation.lastMessage!.createdAt),
|
_formatDate(conversation.dernierMessageAt!),
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (conversation.lastMessage != null) ...[
|
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
conversation.lastMessage!.content,
|
_apercuMessage(),
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
fontWeight: conversation.hasUnread
|
fontWeight: conversation.hasUnread
|
||||||
? FontWeight.w600
|
? FontWeight.w600
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
@@ -112,7 +126,6 @@ class ConversationTile extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -120,16 +133,13 @@ class ConversationTile extends StatelessWidget {
|
|||||||
if (conversation.hasUnread) ...[
|
if (conversation.hasUnread) ...[
|
||||||
const SizedBox(width: SpacingTokens.sm),
|
const SizedBox(width: SpacingTokens.sm),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
horizontal: 8,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusCircular),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${conversation.unreadCount}',
|
'${conversation.nonLus}',
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -137,27 +147,6 @@ class ConversationTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Icônes statut
|
|
||||||
if (conversation.isPinned || conversation.isMuted) ...[
|
|
||||||
const SizedBox(width: SpacingTokens.sm),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
if (conversation.isPinned)
|
|
||||||
Icon(
|
|
||||||
Icons.push_pin,
|
|
||||||
size: 16,
|
|
||||||
color: AppColors.textSecondaryLight,
|
|
||||||
),
|
|
||||||
if (conversation.isMuted)
|
|
||||||
Icon(
|
|
||||||
Icons.volume_off,
|
|
||||||
size: 16,
|
|
||||||
color: AppColors.textSecondaryLight,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
BlocBuilder<DashboardBloc, DashboardState>(
|
BlocBuilder<DashboardBloc, DashboardState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is DashboardLoading) {
|
if (state is DashboardLoading) {
|
||||||
return _buildLoadingList();
|
return _buildLoadingList(context);
|
||||||
} else if (state is DashboardLoaded || state is DashboardRefreshing) {
|
} else if (state is DashboardLoaded || state is DashboardRefreshing) {
|
||||||
final data = state is DashboardLoaded
|
final data = state is DashboardLoaded
|
||||||
? state.dashboardData
|
? state.dashboardData
|
||||||
@@ -42,7 +42,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
} else if (state is DashboardError) {
|
} else if (state is DashboardError) {
|
||||||
return _buildErrorState(state.message);
|
return _buildErrorState(state.message);
|
||||||
}
|
}
|
||||||
return _buildEmptyState();
|
return _buildEmptyState(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -55,7 +55,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.history,
|
Icons.history,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -71,7 +71,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'TOUT VOIR',
|
'TOUT VOIR',
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -82,7 +82,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) {
|
Widget _buildActivitiesList(BuildContext context, List<RecentActivityEntity> activities) {
|
||||||
if (activities.isEmpty) {
|
if (activities.isEmpty) {
|
||||||
return _buildEmptyState();
|
return _buildEmptyState(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
final displayActivities = activities.take(maxItems).toList();
|
final displayActivities = activities.take(maxItems).toList();
|
||||||
@@ -142,7 +142,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
activity.userName,
|
activity.userName,
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -157,10 +157,10 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// Action button si disponible
|
// Action button si disponible
|
||||||
if (activity.hasAction)
|
if (activity.hasAction)
|
||||||
const Icon(
|
Icon(
|
||||||
Icons.chevron_right,
|
Icons.chevron_right,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: AppColors.textSecondaryLight,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -189,25 +189,27 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingList() {
|
Widget _buildLoadingList(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: List.generate(3, (index) => Column(
|
children: List.generate(3, (index) => Column(
|
||||||
children: [
|
children: [
|
||||||
_buildLoadingItem(),
|
_buildLoadingItem(context),
|
||||||
if (index < 2) const SizedBox(height: 12),
|
if (index < 2) const SizedBox(height: 12),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoadingItem() {
|
Widget _buildLoadingItem(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final borderColor = isDark ? AppColors.borderDark : AppColors.border;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder,
|
color: borderColor,
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -220,7 +222,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
height: 16,
|
height: 16,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder,
|
color: borderColor,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -229,7 +231,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
height: 12,
|
height: 12,
|
||||||
width: 200,
|
width: 200,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder.withOpacity(0.5),
|
color: borderColor.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -238,7 +240,7 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
height: 12,
|
height: 12,
|
||||||
width: 120,
|
width: 120,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBorder.withOpacity(0.5),
|
color: borderColor.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -261,11 +263,12 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() {
|
Widget _buildEmptyState(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.history, color: AppColors.textSecondaryLight, size: 32),
|
Icon(Icons.history, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall),
|
const Text('AUCUNE ACTIVITÉ', style: AppTypography.subtitleSmall),
|
||||||
Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
Text('Les activités apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||||
@@ -291,20 +294,20 @@ class ConnectedRecentActivities extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getActivityColor(String type) {
|
Color _getActivityColor(String type, {bool isDark = false}) {
|
||||||
switch (type.toLowerCase()) {
|
switch (type.toLowerCase()) {
|
||||||
case 'member':
|
case 'member':
|
||||||
return AppColors.success;
|
return AppColors.success;
|
||||||
case 'event':
|
case 'event':
|
||||||
return AppColors.info;
|
return AppColors.info;
|
||||||
case 'contribution':
|
case 'contribution':
|
||||||
return AppColors.brandGreen;
|
return AppColors.primaryDark;
|
||||||
case 'organization':
|
case 'organization':
|
||||||
return AppColors.primaryGreen;
|
return AppColors.primary;
|
||||||
case 'system':
|
case 'system':
|
||||||
return AppColors.warning;
|
return AppColors.warning;
|
||||||
default:
|
default:
|
||||||
return AppColors.textSecondaryLight;
|
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
} else if (state is DashboardError) {
|
} else if (state is DashboardError) {
|
||||||
return _buildErrorState(state.message);
|
return _buildErrorState(state.message);
|
||||||
}
|
}
|
||||||
return _buildEmptyState();
|
return _buildEmptyState(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -50,7 +50,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.event_outlined,
|
Icons.event_outlined,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -66,7 +66,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'TOUT VOIR',
|
'TOUT VOIR',
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -77,7 +77,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) {
|
Widget _buildEventsList(BuildContext context, List<UpcomingEventEntity> events) {
|
||||||
if (events.isEmpty) {
|
if (events.isEmpty) {
|
||||||
return _buildEmptyState();
|
return _buildEmptyState(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
final displayEvents = events.take(maxItems).toList();
|
final displayEvents = events.take(maxItems).toList();
|
||||||
@@ -99,7 +99,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) {
|
Widget _buildEventCard(BuildContext context, UpcomingEventEntity event) {
|
||||||
final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primaryGreen);
|
final statusColor = event.isToday ? AppColors.success : (event.isTomorrow ? AppColors.warning : AppColors.primary);
|
||||||
|
|
||||||
return CoreCard(
|
return CoreCard(
|
||||||
backgroundColor: Theme.of(context).cardColor,
|
backgroundColor: Theme.of(context).cardColor,
|
||||||
@@ -140,7 +140,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.location_on_outlined, size: 10, color: AppColors.textSecondaryLight),
|
Icon(Icons.location_on_outlined, size: 10, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -188,7 +188,7 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: event.fillPercentage,
|
value: event.fillPercentage,
|
||||||
minHeight: 4,
|
minHeight: 4,
|
||||||
backgroundColor: AppColors.lightBorder,
|
backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.borderDark : AppColors.border,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success),
|
event.isFull ? AppColors.error : (event.isAlmostFull ? AppColors.warning : AppColors.success),
|
||||||
),
|
),
|
||||||
@@ -230,11 +230,12 @@ class ConnectedUpcomingEvents extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() {
|
Widget _buildEmptyState(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.event_outlined, color: AppColors.textSecondaryLight, size: 32),
|
Icon(Icons.event_outlined, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary, size: 32),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall),
|
const Text('AUCUN ÉVÉNEMENT', style: AppTypography.subtitleSmall),
|
||||||
Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
Text('Les événements apparaîtront ici', style: AppTypography.subtitleSmall.copyWith(fontSize: 10)),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
} else if (state is DashboardError) {
|
} else if (state is DashboardError) {
|
||||||
return _buildErrorNotifications();
|
return _buildErrorNotifications();
|
||||||
}
|
}
|
||||||
return _buildEmptyNotifications();
|
return _buildEmptyNotifications(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -49,7 +49,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen.withOpacity(0.05),
|
color: AppColors.primary.withOpacity(0.05),
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(12),
|
topLeft: Radius.circular(12),
|
||||||
topRight: Radius.circular(12),
|
topRight: Radius.circular(12),
|
||||||
@@ -60,7 +60,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@@ -74,7 +74,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'NOTIFICATIONS',
|
'NOTIFICATIONS',
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 1.1,
|
letterSpacing: 1.1,
|
||||||
),
|
),
|
||||||
@@ -121,23 +121,24 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
final notifications = _generateNotifications(context, data);
|
final notifications = _generateNotifications(context, data);
|
||||||
|
|
||||||
if (notifications.isEmpty) {
|
if (notifications.isEmpty) {
|
||||||
return _buildEmptyNotifications();
|
return _buildEmptyNotifications(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: notifications.take(maxNotifications).map((notification) {
|
children: notifications.take(maxNotifications).map((notification) {
|
||||||
return _buildNotificationItem(notification);
|
return _buildNotificationItem(context, notification);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNotificationItem(DashboardNotification notification) {
|
Widget _buildNotificationItem(BuildContext context, DashboardNotification notification) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: AppColors.lightBorder,
|
color: isDark ? AppColors.borderDark : AppColors.border,
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -199,7 +200,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
notification.message,
|
notification.message,
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -209,7 +210,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
notification.timeAgo,
|
notification.timeAgo,
|
||||||
style: AppTypography.subtitleSmall.copyWith(
|
style: AppTypography.subtitleSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -220,7 +221,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
notification.actionLabel!,
|
notification.actionLabel!,
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
),
|
),
|
||||||
@@ -268,15 +269,16 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyNotifications() {
|
Widget _buildEmptyNotifications(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
Icon(
|
||||||
Icons.notifications_none_outlined,
|
Icons.notifications_none_outlined,
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
size: 24,
|
size: 24,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -359,7 +361,7 @@ class DashboardNotificationsWidget extends StatelessWidget {
|
|||||||
title: 'Nouvelles activités',
|
title: 'Nouvelles activités',
|
||||||
message: '${data.recentActivitiesCount} activités récentes',
|
message: '${data.recentActivitiesCount} activités récentes',
|
||||||
icon: Icons.fiber_new_outlined,
|
icon: Icons.fiber_new_outlined,
|
||||||
color: AppColors.brandGreen,
|
color: AppColors.primaryDark,
|
||||||
timeAgo: '15min',
|
timeAgo: '15min',
|
||||||
isUrgent: false,
|
isUrgent: false,
|
||||||
actionLabel: 'Voir',
|
actionLabel: 'Voir',
|
||||||
|
|||||||
@@ -173,28 +173,28 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
'VUE D\'ENSEMBLE',
|
'VUE D\'ENSEMBLE',
|
||||||
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
|
style: AppTypography.subtitleSmall.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const Icon(Icons.account_balance_wallet, color: AppColors.primaryGreen, size: 24),
|
const Icon(Icons.account_balance_wallet, color: AppColors.primary, size: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.md),
|
const SizedBox(height: SpacingTokens.md),
|
||||||
Text(
|
Text(
|
||||||
'${total.toStringAsFixed(0)} XOF',
|
'${total.toStringAsFixed(0)} XOF',
|
||||||
style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primaryGreen),
|
style: AppTypography.headerSmall.copyWith(fontSize: 24, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.xs),
|
const SizedBox(height: SpacingTokens.xs),
|
||||||
Text(
|
Text(
|
||||||
'Solde disponible total • ${_comptes.length} compte${_comptes.length > 1 ? 's' : ''}',
|
'Solde disponible total • ${_comptes.length} compte${_comptes.length > 1 ? 's' : ''}',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
if (_comptes.any((c) => c.soldeBloque > 0)) ...[
|
if (_comptes.any((c) => c.soldeBloque > 0)) ...[
|
||||||
const SizedBox(height: SpacingTokens.xs),
|
const SizedBox(height: SpacingTokens.xs),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700),
|
Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
'Certains fonds sont sous surveillance LCB-FT',
|
'Certains fonds sont sous surveillance LCB-FT',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: Colors.amber.shade700),
|
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.warning),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -234,12 +234,17 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
if (typeLibelle != null)
|
if (typeLibelle != null)
|
||||||
Text(
|
Text(
|
||||||
typeLibelle,
|
typeLibelle,
|
||||||
style: AppTypography.subtitleSmall.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.subtitleSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
if (dateStr != null)
|
if (dateStr != null)
|
||||||
Text(
|
Text(
|
||||||
dateStr,
|
dateStr,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -252,16 +257,16 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.amber.withOpacity(0.15),
|
color: AppColors.warningContainer,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
border: Border.all(color: Colors.amber.shade700, width: 1),
|
border: Border.all(color: AppColors.warning, width: 1),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.shield_outlined, size: 12, color: Colors.amber.shade700),
|
Icon(Icons.shield_outlined, size: 12, color: AppColors.warning),
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: Colors.amber.shade700)),
|
Text('LCB-FT', style: TextStyle(fontSize: 9, fontWeight: FontWeight.bold, color: AppColors.warning)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -270,7 +275,7 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
if (c.statut != null)
|
if (c.statut != null)
|
||||||
InfoBadge(
|
InfoBadge(
|
||||||
text: c.statut!,
|
text: c.statut!,
|
||||||
backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondaryLight,
|
backgroundColor: c.statut == 'ACTIF' ? AppColors.success : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -284,7 +289,7 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
Text('SOLDE ACTUEL', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
Text('SOLDE ACTUEL', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
||||||
Text(
|
Text(
|
||||||
'${c.soldeActuel.toStringAsFixed(0)} XOF',
|
'${c.soldeActuel.toStringAsFixed(0)} XOF',
|
||||||
style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primaryGreen),
|
style: AppTypography.headerSmall.copyWith(fontSize: 14, color: AppColors.primary),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -305,7 +310,7 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
const SizedBox(height: SpacingTokens.sm),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
Text(
|
Text(
|
||||||
c.description!,
|
c.description!,
|
||||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, color: AppColors.textSecondary),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -321,8 +326,8 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
onPressed: () => _openDepot(c),
|
onPressed: () => _openDepot(c),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||||
foregroundColor: AppColors.primaryGreen,
|
foregroundColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
child: const Text('Dépôt', style: TextStyle(fontSize: 12)),
|
child: const Text('Dépôt', style: TextStyle(fontSize: 12)),
|
||||||
),
|
),
|
||||||
@@ -333,8 +338,8 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
onPressed: soldeDispo > 0 ? () => _openRetrait(c) : null,
|
onPressed: soldeDispo > 0 ? () => _openRetrait(c) : null,
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||||
foregroundColor: AppColors.primaryGreen,
|
foregroundColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
child: const Text('Retrait', style: TextStyle(fontSize: 12)),
|
child: const Text('Retrait', style: TextStyle(fontSize: 12)),
|
||||||
),
|
),
|
||||||
@@ -346,8 +351,8 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null,
|
onPressed: soldeDispo > 0 ? () => _openTransfert(c) : null,
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||||
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
|
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||||
foregroundColor: AppColors.primaryGreen,
|
foregroundColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
child: const Text('Transférer', style: TextStyle(fontSize: 12)),
|
child: const Text('Transférer', style: TextStyle(fontSize: 12)),
|
||||||
),
|
),
|
||||||
@@ -365,7 +370,9 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
label: const Text('Détail', style: TextStyle(fontSize: 12)),
|
label: const Text('Détail', style: TextStyle(fontSize: 12)),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
foregroundColor: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textPrimaryDark
|
||||||
|
: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -377,7 +384,9 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
label: const Text('Historique', style: TextStyle(fontSize: 12)),
|
label: const Text('Historique', style: TextStyle(fontSize: 12)),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.sm),
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
foregroundColor: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textPrimaryDark
|
||||||
|
: AppColors.textPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -424,17 +433,17 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.savings_outlined, size: 64, color: AppColors.textSecondaryLight),
|
Icon(Icons.savings_outlined, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
const SizedBox(height: SpacingTokens.lg),
|
const SizedBox(height: SpacingTokens.lg),
|
||||||
Text(
|
Text(
|
||||||
'Aucun compte épargne',
|
'Aucun compte épargne',
|
||||||
style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.sm),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
Text(
|
Text(
|
||||||
'Votre organisation peut ouvrir un compte épargne pour vous. Contactez-la pour en bénéficier.',
|
'Votre organisation peut ouvrir un compte épargne pour vous. Contactez-la pour en bénéficier.',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
|
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -459,19 +468,18 @@ class _EpargnePageState extends State<EpargnePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final showFab = _canCreateCompte(context);
|
final showFab = _canCreateCompte(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'COMPTES ÉPARGNE',
|
title: 'Comptes Épargne',
|
||||||
backgroundColor: AppColors.surface,
|
moduleGradient: ModuleColors.epargneGradient,
|
||||||
foregroundColor: AppColors.textPrimaryLight,
|
|
||||||
),
|
),
|
||||||
body: _buildBodyContent(),
|
body: _buildBodyContent(),
|
||||||
floatingActionButton: showFab
|
floatingActionButton: showFab
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
onPressed: _openCreerCompte,
|
onPressed: _openCreerCompte,
|
||||||
tooltip: 'Créer un compte épargne pour un membre',
|
tooltip: 'Créer un compte épargne pour un membre',
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -38,9 +38,10 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: ColorTokens.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'BUDGETS',
|
title: 'Budgets',
|
||||||
|
moduleGradient: ModuleColors.financeWorkflowGradient,
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -57,7 +58,9 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocConsumer<BudgetBloc, BudgetState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocConsumer<BudgetBloc, BudgetState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is BudgetCreated) {
|
if (state is BudgetCreated) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -98,13 +101,14 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
SnackbarHelper.showNotImplemented(context, 'Création de budget');
|
SnackbarHelper.showNotImplemented(context, 'Création de budget');
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Nouveau budget'),
|
label: const Text('Nouveau budget'),
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -143,7 +147,7 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
Widget _buildFilterChips(BuildContext context, BudgetsLoaded state) {
|
Widget _buildFilterChips(BuildContext context, BudgetsLoaded state) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
color: AppColors.lightBackground,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: SpacingTokens.sm,
|
spacing: SpacingTokens.sm,
|
||||||
runSpacing: SpacingTokens.sm,
|
runSpacing: SpacingTokens.sm,
|
||||||
@@ -178,6 +182,10 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState(String message) {
|
Widget _buildEmptyState(String message) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -185,21 +193,25 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.account_balance_wallet_outlined,
|
Icons.account_balance_wallet_outlined,
|
||||||
size: 48,
|
size: 48,
|
||||||
color: AppColors.textSecondaryLight.withOpacity(0.5),
|
color: textSecondary.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.lg),
|
const SizedBox(height: SpacingTokens.lg),
|
||||||
Text(
|
Text(
|
||||||
message,
|
message,
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorState(BuildContext context, String message) {
|
Widget _buildErrorState(BuildContext context, String message) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -213,7 +225,7 @@ class _BudgetsListView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
message,
|
message,
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: textSecondary,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -274,14 +286,14 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getStatusColor(BudgetStatus status) {
|
Color _getStatusColor(BudgetStatus status, {bool isDark = false}) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case BudgetStatus.draft:
|
case BudgetStatus.draft:
|
||||||
return AppColors.textSecondaryLight;
|
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
case BudgetStatus.active:
|
case BudgetStatus.active:
|
||||||
return AppColors.brandGreen;
|
return AppColors.primaryDark;
|
||||||
case BudgetStatus.closed:
|
case BudgetStatus.closed:
|
||||||
return AppColors.textSecondaryLight;
|
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
case BudgetStatus.cancelled:
|
case BudgetStatus.cancelled:
|
||||||
return AppColors.error;
|
return AppColors.error;
|
||||||
}
|
}
|
||||||
@@ -302,17 +314,20 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final currencyFormat = NumberFormat.currency(symbol: budget.currency);
|
final currencyFormat = NumberFormat.currency(symbol: budget.currency);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
border: Border.all(color: AppColors.lightBorder),
|
border: Border.all(
|
||||||
|
color: isDark ? AppColors.borderDark : AppColors.border,
|
||||||
|
),
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Color(0x0A000000),
|
color: AppColors.shadow,
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -335,13 +350,13 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getStatusColor(budget.status).withOpacity(0.1),
|
color: _getStatusColor(budget.status, isDark: isDark).withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
_getStatusLabel(budget.status),
|
_getStatusLabel(budget.status),
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: _getStatusColor(budget.status),
|
color: _getStatusColor(budget.status, isDark: isDark),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -368,7 +383,7 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
currencyFormat.format(budget.totalPlanned),
|
currencyFormat.format(budget.totalPlanned),
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -389,7 +404,7 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: budget.isOverBudget
|
color: budget.isOverBudget
|
||||||
? AppColors.error
|
? AppColors.error
|
||||||
: AppColors.brandGreen,
|
: AppColors.primaryDark,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -401,10 +416,10 @@ class _BudgetCard extends StatelessWidget {
|
|||||||
const SizedBox(height: SpacingTokens.sm),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: budget.realizationRate / 100,
|
value: budget.realizationRate / 100,
|
||||||
backgroundColor: AppColors.lightBorder,
|
backgroundColor: isDark ? AppColors.borderDark : AppColors.border,
|
||||||
color: budget.isOverBudget
|
color: budget.isOverBudget
|
||||||
? AppColors.error
|
? AppColors.error
|
||||||
: AppColors.brandGreen,
|
: AppColors.primaryDark,
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.xs),
|
const SizedBox(height: SpacingTokens.xs),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ class _PendingApprovalsView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: ColorTokens.background,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: UFAppBar(
|
appBar: UFAppBar(
|
||||||
title: 'APPROBATIONS EN ATTENTE',
|
title: 'Approbations en attente',
|
||||||
|
moduleGradient: ModuleColors.financeWorkflowGradient,
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -155,6 +156,7 @@ class _PendingApprovalsView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorState(BuildContext context, String message) {
|
Widget _buildErrorState(BuildContext context, String message) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -168,7 +170,7 @@ class _PendingApprovalsView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
message,
|
message,
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -188,6 +190,10 @@ class _PendingApprovalsView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState() {
|
Widget _buildEmptyState() {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -201,19 +207,21 @@ class _PendingApprovalsView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Aucune approbation en attente',
|
'Aucune approbation en attente',
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: SpacingTokens.sm),
|
const SizedBox(height: SpacingTokens.sm),
|
||||||
Text(
|
Text(
|
||||||
'Toutes les transactions sont approuvées',
|
'Toutes les transactions sont approuvées',
|
||||||
style: AppTypography.bodyTextSmall.copyWith(
|
style: AppTypography.bodyTextSmall.copyWith(
|
||||||
color: AppColors.textSecondaryLight,
|
color: textSecondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -248,12 +256,12 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getLevelColor(ApprovalLevel level) {
|
Color _getLevelColor(ApprovalLevel level, {bool isDark = false}) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case ApprovalLevel.none:
|
case ApprovalLevel.none:
|
||||||
return AppColors.textSecondaryLight;
|
return isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
case ApprovalLevel.level1:
|
case ApprovalLevel.level1:
|
||||||
return AppColors.brandGreen;
|
return AppColors.primaryDark;
|
||||||
case ApprovalLevel.level2:
|
case ApprovalLevel.level2:
|
||||||
return AppColors.warning;
|
return AppColors.warning;
|
||||||
case ApprovalLevel.level3:
|
case ApprovalLevel.level3:
|
||||||
@@ -263,17 +271,18 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final currencyFormat = NumberFormat.currency(symbol: approval.currency);
|
final currencyFormat = NumberFormat.currency(symbol: approval.currency);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
|
||||||
border: Border.all(color: AppColors.lightBorder),
|
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||||
boxShadow: const [
|
boxShadow: const [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Color(0x0A000000),
|
color: AppColors.shadow,
|
||||||
blurRadius: 8,
|
blurRadius: 8,
|
||||||
offset: Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -296,13 +305,13 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getLevelColor(approval.requiredLevel).withOpacity(0.1),
|
color: _getLevelColor(approval.requiredLevel, isDark: isDark).withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Niveau ${approval.requiredApprovals}',
|
'Niveau ${approval.requiredApprovals}',
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: _getLevelColor(approval.requiredLevel),
|
color: _getLevelColor(approval.requiredLevel, isDark: isDark),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -313,7 +322,7 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
currencyFormat.format(approval.amount),
|
currencyFormat.format(approval.amount),
|
||||||
style: AppTypography.headerSmall.copyWith(
|
style: AppTypography.headerSmall.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -323,7 +332,7 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.person_outline,
|
Icons.person_outline,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -340,7 +349,7 @@ class _ApprovalCard extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.access_time,
|
Icons.access_time,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: AppColors.textSecondaryLight,
|
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@@ -72,9 +73,9 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBackground,
|
color: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||||
border: Border.all(color: AppColors.lightBorder),
|
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -88,7 +89,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
|||||||
'Montant',
|
'Montant',
|
||||||
currencyFormat.format(widget.approval.amount),
|
currencyFormat.format(widget.approval.amount),
|
||||||
valueStyle: AppTypography.actionText.copyWith(
|
valueStyle: AppTypography.actionText.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -145,7 +146,7 @@ class _ApproveDialogState extends State<ApproveDialog> {
|
|||||||
label: const Text('Approuver'),
|
label: const Text('Approuver'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.success,
|
backgroundColor: AppColors.success,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
final currencyFormat = NumberFormat.currency(symbol: widget.approval.currency);
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@@ -75,9 +76,9 @@ class _RejectDialogState extends State<RejectDialog> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(SpacingTokens.md),
|
padding: const EdgeInsets.all(SpacingTokens.md),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightBackground,
|
color: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||||
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
borderRadius: BorderRadius.circular(SpacingTokens.radiusSm),
|
||||||
border: Border.all(color: AppColors.lightBorder),
|
border: Border.all(color: isDark ? AppColors.borderDark : AppColors.border),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -91,7 +92,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
|||||||
'Montant',
|
'Montant',
|
||||||
currencyFormat.format(widget.approval.amount),
|
currencyFormat.format(widget.approval.amount),
|
||||||
valueStyle: AppTypography.actionText.copyWith(
|
valueStyle: AppTypography.actionText.copyWith(
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -141,7 +142,7 @@ class _RejectDialogState extends State<RejectDialog> {
|
|||||||
label: const Text('Rejeter'),
|
label: const Text('Rejeter'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.error,
|
backgroundColor: AppColors.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onError,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -39,8 +39,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: const UFAppBar(title: 'AIDE & SUPPORT'),
|
appBar: UFAppBar(
|
||||||
body: SingleChildScrollView(
|
title: 'Aide & Support',
|
||||||
|
moduleGradient: ModuleColors.supportGradient,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -75,6 +80,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +92,12 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.primaryGreen.withOpacity(0.1),
|
color: AppColors.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
Icons.help_outline,
|
Icons.help_outline,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -113,13 +119,17 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
|
|
||||||
/// Section de recherche
|
/// Section de recherche
|
||||||
Widget _buildSearchSection() {
|
Widget _buildSearchSection() {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final bgSearch = isDark ? AppColors.surfaceVariantDark : AppColors.surface;
|
||||||
|
final borderSearch = isDark ? AppColors.borderDark : AppColors.border;
|
||||||
|
final iconColor = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
return CoreCard(
|
return CoreCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.search, color: AppColors.primaryGreen, size: 18),
|
const Icon(Icons.search, color: AppColors.primary, size: 18),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'RECHERCHER DANS L\'AIDE',
|
'RECHERCHER DANS L\'AIDE',
|
||||||
@@ -130,9 +140,9 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.lightSurface,
|
color: bgSearch,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: AppColors.lightBorder),
|
border: Border.all(color: borderSearch),
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
@@ -141,14 +151,14 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Une question, un mot-clé...',
|
hintText: 'Une question, un mot-clé...',
|
||||||
hintStyle: AppTypography.subtitleSmall,
|
hintStyle: AppTypography.subtitleSmall,
|
||||||
prefixIcon: const Icon(Icons.search, color: AppColors.textSecondaryLight, size: 18),
|
prefixIcon: Icon(Icons.search, color: iconColor, size: 18),
|
||||||
suffixIcon: _searchQuery.isNotEmpty
|
suffixIcon: _searchQuery.isNotEmpty
|
||||||
? IconButton(
|
? IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_searchController.clear();
|
_searchController.clear();
|
||||||
setState(() => _searchQuery = '');
|
setState(() => _searchQuery = '');
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.clear, color: AppColors.textSecondaryLight, size: 18),
|
icon: Icon(Icons.clear, color: iconColor, size: 18),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@@ -180,7 +190,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
'CHAT',
|
'CHAT',
|
||||||
'Support Direct',
|
'Support Direct',
|
||||||
Icons.chat_bubble_outline,
|
Icons.chat_bubble_outline,
|
||||||
AppColors.primaryGreen,
|
AppColors.primary,
|
||||||
() => _startLiveChat(),
|
() => _startLiveChat(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -286,16 +296,16 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent,
|
color: isSelected ? AppColors.primary.withOpacity(0.1) : Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
|
color: isSelected ? AppColors.primary : AppColors.border,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
label.toUpperCase(),
|
label.toUpperCase(),
|
||||||
style: AppTypography.badgeText.copyWith(
|
style: AppTypography.badgeText.copyWith(
|
||||||
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||||
),
|
),
|
||||||
@@ -338,11 +348,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
),
|
),
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
faq['icon'] as IconData,
|
faq['icon'] as IconData,
|
||||||
color: AppColors.primaryGreen,
|
color: AppColors.primary,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
iconColor: AppColors.primaryGreen,
|
iconColor: AppColors.primary,
|
||||||
collapsedIconColor: AppColors.textSecondaryLight,
|
collapsedIconColor: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
shape: const RoundedRectangleBorder(side: BorderSide.none),
|
shape: const RoundedRectangleBorder(side: BorderSide.none),
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@@ -370,7 +382,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildGuideItem('Introduction', 'Démarrer avec UnionFlow', Icons.play_circle_outline, AppColors.success, () => _openGuide('getting-started')),
|
_buildGuideItem('Introduction', 'Démarrer avec UnionFlow', Icons.play_circle_outline, AppColors.success, () => _openGuide('getting-started')),
|
||||||
_buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primaryGreen, () => _openGuide('members')),
|
_buildGuideItem('Membres', 'Gérer vos adhérents', Icons.people_outline, AppColors.primary, () => _openGuide('members')),
|
||||||
_buildGuideItem('Organisations', 'Structures & Syndicats', Icons.business_outlined, AppColors.info, () => _openGuide('organizations')),
|
_buildGuideItem('Organisations', 'Structures & Syndicats', Icons.business_outlined, AppColors.info, () => _openGuide('organizations')),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -394,7 +406,13 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Icon(Icons.chevron_right, color: AppColors.textSecondaryLight, size: 16),
|
Icon(
|
||||||
|
Icons.chevron_right,
|
||||||
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? AppColors.textSecondaryDark
|
||||||
|
: AppColors.textSecondary,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -402,8 +420,9 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
|
|
||||||
/// Section contact
|
/// Section contact
|
||||||
Widget _buildContactSection() {
|
Widget _buildContactSection() {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
return CoreCard(
|
return CoreCard(
|
||||||
backgroundColor: AppColors.primaryGreen, // Correction: color -> backgroundColor
|
backgroundColor: AppColors.primary, // Correction: color -> backgroundColor
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32),
|
const Icon(Icons.headset_mic_outlined, color: Colors.white, size: 32),
|
||||||
@@ -426,8 +445,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
child: UFPrimaryButton(
|
child: UFPrimaryButton(
|
||||||
label: 'EMAIL', // Correction: text -> label
|
label: 'EMAIL', // Correction: text -> label
|
||||||
onPressed: () => _contactByEmail(),
|
onPressed: () => _contactByEmail(),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: isDark ? AppColors.surfaceDark : AppColors.surface,
|
||||||
textColor: AppColors.primaryGreen,
|
textColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -436,7 +455,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
label: 'CHAT', // Correction: text -> label
|
label: 'CHAT', // Correction: text -> label
|
||||||
onPressed: () => _startLiveChat(),
|
onPressed: () => _startLiveChat(),
|
||||||
backgroundColor: Colors.white.withOpacity(0.2),
|
backgroundColor: Colors.white.withOpacity(0.2),
|
||||||
textColor: Colors.white,
|
textColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -516,8 +535,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
_contactByEmail();
|
_contactByEmail();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Envoyer un email'),
|
child: const Text('Envoyer un email'),
|
||||||
),
|
),
|
||||||
@@ -548,7 +567,7 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.error,
|
backgroundColor: AppColors.error,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onError,
|
||||||
),
|
),
|
||||||
child: const Text('Signaler'),
|
child: const Text('Signaler'),
|
||||||
),
|
),
|
||||||
@@ -578,8 +597,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
_launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile');
|
_launchUrl('mailto:support@unionflow.com?subject=Demande de fonctionnalité - UnionFlow Mobile');
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Envoyer'),
|
child: const Text('Envoyer'),
|
||||||
),
|
),
|
||||||
@@ -613,8 +632,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
_launchUrl('https://docs.unionflow.com/$guideId');
|
_launchUrl('https://docs.unionflow.com/$guideId');
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Voir en ligne'),
|
child: const Text('Voir en ligne'),
|
||||||
),
|
),
|
||||||
@@ -644,8 +663,8 @@ class _HelpSupportPageState extends State<HelpSupportPage> {
|
|||||||
_contactByEmail();
|
_contactByEmail();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
),
|
),
|
||||||
child: const Text('Contacter le support'),
|
child: const Text('Contacter le support'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
|
|||||||
import '../../bloc/org_types_bloc.dart';
|
import '../../bloc/org_types_bloc.dart';
|
||||||
import '../../domain/entities/type_reference_entity.dart';
|
import '../../domain/entities/type_reference_entity.dart';
|
||||||
import '../../../../shared/design_system/tokens/app_colors.dart';
|
import '../../../../shared/design_system/tokens/app_colors.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/module_colors.dart';
|
||||||
|
import '../../../../shared/design_system/components/uf_app_bar.dart';
|
||||||
import '../../../../core/di/injection_container.dart';
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
|
||||||
const List<String> _devises = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
|
const List<String> _devises = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
|
||||||
@@ -106,12 +108,10 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: UFAppBar(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
title: 'Nouvelle Organisation',
|
||||||
foregroundColor: Colors.white,
|
moduleGradient: ModuleColors.organisationsGradient,
|
||||||
title: const Text('Nouvelle Organisation'),
|
|
||||||
elevation: 0,
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _isFormValid() ? _saveOrganisation : null,
|
onPressed: _isFormValid() ? _saveOrganisation : null,
|
||||||
@@ -119,7 +119,9 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: BlocListener<OrganizationsBloc, OrganizationsState>(
|
body: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: BlocListener<OrganizationsBloc, OrganizationsState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is OrganizationCreated) {
|
if (state is OrganizationCreated) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -128,7 +130,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
} else if (state is OrganizationsError) {
|
} else if (state is OrganizationsError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -161,20 +163,21 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSection(String title, IconData icon, List<Widget> children) {
|
Widget _buildSection(String title, IconData icon, List<Widget> children) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
|
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(icon, size: 16, color: AppColors.primaryGreen),
|
Icon(icon, size: 16, color: AppColors.primary),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)),
|
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
...children,
|
...children,
|
||||||
@@ -237,9 +240,18 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
onTap: () => _pickDateFondation(context),
|
onTap: () => _pickDateFondation(context),
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
|
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
|
||||||
child: Text(
|
child: Builder(
|
||||||
|
builder: (ctx) {
|
||||||
|
final isDark = Theme.of(ctx).brightness == Brightness.dark;
|
||||||
|
return Text(
|
||||||
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
|
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
|
||||||
style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight),
|
style: TextStyle(
|
||||||
|
color: _dateFondation != null
|
||||||
|
? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary)
|
||||||
|
: (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -355,7 +367,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
|
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
|
||||||
value: _organisationPublique,
|
value: _organisationPublique,
|
||||||
onChanged: (v) => setState(() => _organisationPublique = v),
|
onChanged: (v) => setState(() => _organisationPublique = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -364,7 +376,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
|
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
|
||||||
value: _accepteNouveauxMembres,
|
value: _accepteNouveauxMembres,
|
||||||
onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
|
onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -392,7 +404,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
|
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
|
||||||
value: _cotisationObligatoire,
|
value: _cotisationObligatoire,
|
||||||
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
if (_cotisationObligatoire) ...[
|
if (_cotisationObligatoire) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -451,8 +463,8 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
label: const Text('Créer l\'organisation'),
|
label: const Text('Créer l\'organisation'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
@@ -466,7 +478,7 @@ class _CreateOrganizationPageState extends State<CreateOrganizationPage> {
|
|||||||
icon: const Icon(Icons.cancel),
|
icon: const Icon(Icons.cancel),
|
||||||
label: const Text('Annuler'),
|
label: const Text('Annuler'),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: AppColors.textSecondaryLight,
|
foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import '../../bloc/organizations_state.dart';
|
|||||||
import '../../bloc/org_types_bloc.dart';
|
import '../../bloc/org_types_bloc.dart';
|
||||||
import '../../domain/entities/type_reference_entity.dart';
|
import '../../domain/entities/type_reference_entity.dart';
|
||||||
import '../../../../shared/design_system/tokens/app_colors.dart';
|
import '../../../../shared/design_system/tokens/app_colors.dart';
|
||||||
|
import '../../../../shared/design_system/tokens/module_colors.dart';
|
||||||
|
import '../../../../shared/design_system/components/uf_app_bar.dart';
|
||||||
import '../../../../core/di/injection_container.dart';
|
import '../../../../core/di/injection_container.dart';
|
||||||
|
|
||||||
const List<String> _devisesEdit = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
|
const List<String> _devisesEdit = ['XOF', 'XAF', 'EUR', 'USD', 'GBP', 'CAD', 'CHF', 'MAD', 'GHS', 'NGN', 'CDF', 'KES'];
|
||||||
@@ -185,12 +187,10 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.lightBackground,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: UFAppBar(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
title: 'Modifier Organisation',
|
||||||
foregroundColor: Colors.white,
|
moduleGradient: ModuleColors.organisationsGradient,
|
||||||
title: const Text('Modifier Organisation'),
|
|
||||||
elevation: 0,
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: _hasChanges() ? _saveChanges : null,
|
onPressed: _hasChanges() ? _saveChanges : null,
|
||||||
@@ -212,7 +212,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
} else if (state is OrganizationsError) {
|
} else if (state is OrganizationsError) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
|
SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -254,14 +254,14 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
Widget _buildSection(String title, IconData icon, List<Widget> children) {
|
Widget _buildSection(String title, IconData icon, List<Widget> children) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
|
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8)),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(icon, size: 16, color: AppColors.primaryGreen),
|
Icon(icon, size: 16, color: AppColors.primary),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)),
|
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primary)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
...children,
|
...children,
|
||||||
@@ -326,9 +326,18 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
onTap: () => _pickDateFondation(context),
|
onTap: () => _pickDateFondation(context),
|
||||||
child: InputDecorator(
|
child: InputDecorator(
|
||||||
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
|
decoration: const InputDecoration(labelText: 'Date de fondation', border: OutlineInputBorder(), prefixIcon: Icon(Icons.cake)),
|
||||||
child: Text(
|
child: Builder(
|
||||||
|
builder: (ctx) {
|
||||||
|
final isDark = Theme.of(ctx).brightness == Brightness.dark;
|
||||||
|
return Text(
|
||||||
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
|
_dateFondation != null ? _formatDate(_dateFondation) : 'Sélectionner une date',
|
||||||
style: TextStyle(color: _dateFondation != null ? AppColors.textPrimaryLight : AppColors.textSecondaryLight),
|
style: TextStyle(
|
||||||
|
color: _dateFondation != null
|
||||||
|
? (isDark ? AppColors.textPrimaryDark : AppColors.textPrimary)
|
||||||
|
: (isDark ? AppColors.textSecondaryDark : AppColors.textSecondary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -452,7 +461,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
|
subtitle: const Text('Visible par tous les utilisateurs', style: TextStyle(fontSize: 12)),
|
||||||
value: _organisationPublique,
|
value: _organisationPublique,
|
||||||
onChanged: (v) => setState(() => _organisationPublique = v),
|
onChanged: (v) => setState(() => _organisationPublique = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -461,7 +470,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
|
subtitle: const Text('Les demandes d\'adhésion sont ouvertes', style: TextStyle(fontSize: 12)),
|
||||||
value: _accepteNouveauxMembres,
|
value: _accepteNouveauxMembres,
|
||||||
onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
|
onChanged: (v) => setState(() => _accepteNouveauxMembres = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -490,7 +499,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
|
title: const Text('Cotisation obligatoire', style: TextStyle(fontSize: 14)),
|
||||||
value: _cotisationObligatoire,
|
value: _cotisationObligatoire,
|
||||||
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
onChanged: (v) => setState(() => _cotisationObligatoire = v),
|
||||||
activeColor: AppColors.primaryGreen,
|
activeColor: AppColors.primary,
|
||||||
),
|
),
|
||||||
if (_cotisationObligatoire) ...[
|
if (_cotisationObligatoire) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -559,12 +568,15 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildReadOnlyRow(IconData icon, String label, String value) {
|
Widget _buildReadOnlyRow(IconData icon, String label, String value) {
|
||||||
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
|
||||||
|
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
|
||||||
return Row(children: [
|
return Row(children: [
|
||||||
Icon(icon, size: 18, color: AppColors.textSecondaryLight),
|
Icon(icon, size: 18, color: textSecondary),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)),
|
Text(label, style: TextStyle(fontSize: 11, color: textSecondary)),
|
||||||
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)),
|
Text(value, style: TextStyle(fontSize: 13, color: textPrimary, fontWeight: FontWeight.w600)),
|
||||||
])),
|
])),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -578,8 +590,8 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
icon: const Icon(Icons.save),
|
icon: const Icon(Icons.save),
|
||||||
label: const Text('Enregistrer les modifications'),
|
label: const Text('Enregistrer les modifications'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryGreen,
|
backgroundColor: AppColors.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: AppColors.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
@@ -592,7 +604,7 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
onPressed: _showDiscardDialog,
|
onPressed: _showDiscardDialog,
|
||||||
icon: const Icon(Icons.cancel),
|
icon: const Icon(Icons.cancel),
|
||||||
label: const Text('Annuler'),
|
label: const Text('Annuler'),
|
||||||
style: OutlinedButton.styleFrom(foregroundColor: AppColors.textSecondaryLight, padding: const EdgeInsets.symmetric(vertical: 10)),
|
style: OutlinedButton.styleFrom(foregroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.textSecondaryDark : AppColors.textSecondary, padding: const EdgeInsets.symmetric(vertical: 10)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@@ -681,8 +693,8 @@ class _EditOrganizationPageState extends State<EditOrganizationPage> {
|
|||||||
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Continuer l\'édition')),
|
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Continuer l\'édition')),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); },
|
onPressed: () { Navigator.of(ctx).pop(); Navigator.of(context).pop(); },
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
|
||||||
child: const Text('Abandonner', style: TextStyle(color: Colors.white)),
|
child: const Text('Abandonner', style: TextStyle(color: AppColors.onPrimary)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user