feat(ui): RefreshIndicator + AlwaysScrollable + dark mode sur 14 pages

RefreshIndicator ajouté (dispatche les events BLoC appropriés) :
- adhesion_detail, adhesions_page, demande_aide_detail, demandes_aide_page
- event_detail, organization_detail, org_selector, org_types
- user_management_detail, reports (TabBarView), logs (Dashboard tab)
- profile (onglet Perso), backup (3 onglets), notifications

Fixes associés :
- AlwaysScrollableScrollPhysics sur tous les scroll widgets
  (permet pull-to-refresh même si contenu < écran)
- Empty states des listes : wrappés dans SingleChildScrollView pour refresh
- Dark mode adaptatif sur textes/surfaces/borders hardcodés
- backup_page : bouton retour ajouté dans le header gradient
- org_types : chevron/star/border adaptatifs
- reports : couleurs placeholders graphique + chevrons
This commit is contained in:
dahoud
2026-04-15 20:13:50 +00:00
parent f78892e5f6
commit 55f84da49a
14 changed files with 1565 additions and 1538 deletions

View File

@@ -31,13 +31,14 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: const UFAppBar(
title: 'DÉTAIL ADHÉSION',
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimaryLight,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Détail Adhésion',
moduleGradient: ModuleColors.adhesionsGradient,
),
body: BlocConsumer<AdhesionsBloc, AdhesionsState>(
body: SafeArea(
top: false,
child: BlocConsumer<AdhesionsBloc, AdhesionsState>(
listenWhen: (prev, curr) => prev.status != curr.status,
listener: (context, state) {
if (state.status == AdhesionsStatus.error && state.message != null) {
@@ -58,7 +59,7 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: AppColors.textSecondaryLight),
Icon(Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
const SizedBox(height: 16),
Text(
'Adhésion introuvable',
@@ -68,7 +69,12 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
),
);
}
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.adhesions,
onRefresh: () async =>
context.read<AdhesionsBloc>().add(LoadAdhesionById(widget.adhesionId)),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -118,10 +124,12 @@ class _AdhesionDetailPageState extends State<AdhesionDetailPage> {
_ActionsSection(adhesion: a, currencyFormat: _currencyFormat, isGestionnaire: _isGestionnaire()),
],
),
);
), // SingleChildScrollView
); // RefreshIndicator
},
),
);
), // BlocConsumer
), // SafeArea
); // Scaffold
}
bool _isGestionnaire() {
@@ -157,7 +165,7 @@ class _InfoCard extends StatelessWidget {
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
fontSize: 9,
color: AppColors.textSecondaryLight,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 2),
@@ -187,13 +195,13 @@ Widget _buildStatutBadge(String? statut) {
color = AppColors.error;
break;
case 'EN_ATTENTE':
color = AppColors.brandGreenLight;
color = AppColors.primaryLight;
break;
case 'EN_PAIEMENT':
color = AppColors.warning;
break;
default:
color = AppColors.textSecondaryLight;
color = AppColors.textSecondary;
}
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
}
@@ -307,7 +315,7 @@ class _ActionsSection extends StatelessWidget {
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),

View File

@@ -89,11 +89,10 @@ class _AdhesionsPageState extends State<AdhesionsPage>
}
},
child: Scaffold(
backgroundColor: AppColors.background,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'ADHÉSIONS',
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimaryLight,
title: 'Adhésions',
moduleGradient: ModuleColors.adhesionsGradient,
actions: [
IconButton(
icon: const Icon(Icons.add, size: 20),
@@ -105,9 +104,9 @@ class _AdhesionsPageState extends State<AdhesionsPage>
controller: _tabController,
onTap: _loadTab,
isScrollable: true,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
@@ -148,11 +147,17 @@ class _AdhesionsPageState extends State<AdhesionsPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.assignment_outlined, size: 40, color: AppColors.textSecondaryLight),
Icon(
Icons.assignment_outlined,
size: 40,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
),
const SizedBox(height: 8),
Text(
'Aucune demande d\'adhésion',
style: AppTypography.bodyTextSmall.copyWith(fontSize: 16, color: AppColors.textSecondaryLight),
style: AppTypography.bodyTextSmall.copyWith(fontSize: 16, color: Theme.of(context).colorScheme.onSurfaceVariant),
),
const SizedBox(height: 8),
TextButton.icon(
@@ -249,7 +254,7 @@ class _AdhesionCard extends StatelessWidget {
],
),
),
_buildStatutBadge(adhesion.statut),
_buildStatutBadge(context, adhesion.statut),
],
),
const SizedBox(height: 12),
@@ -262,7 +267,7 @@ class _AdhesionCard extends StatelessWidget {
Text('FRAIS D\'ADHÉSION', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
Text(
adhesion.fraisAdhesion != null ? currencyFormat.format(adhesion.fraisAdhesion) : '',
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primaryGreen),
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primary),
),
],
),
@@ -283,7 +288,7 @@ class _AdhesionCard extends StatelessWidget {
const SizedBox(height: 8),
Text(
'MEMBRE : ${adhesion.nomMembreComplet.toUpperCase()}',
style: AppTypography.subtitleSmall.copyWith(fontSize: 8, color: AppColors.textSecondaryLight),
style: AppTypography.subtitleSmall.copyWith(fontSize: 8, color: Theme.of(context).colorScheme.onSurfaceVariant),
),
],
],
@@ -291,7 +296,7 @@ class _AdhesionCard extends StatelessWidget {
);
}
Widget _buildStatutBadge(String? statut) {
Widget _buildStatutBadge(BuildContext context, String? statut) {
Color color;
switch (statut) {
case 'APPROUVEE':
@@ -303,13 +308,15 @@ class _AdhesionCard extends StatelessWidget {
color = AppColors.error;
break;
case 'EN_ATTENTE':
color = AppColors.brandGreenLight;
color = AppColors.primaryLight;
break;
case 'EN_PAIEMENT':
color = AppColors.warning;
break;
default:
color = AppColors.textSecondaryLight;
color = Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary;
}
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
}

View File

@@ -21,11 +21,14 @@ class UserManagementDetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: const UFAppBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Détail utilisateur',
moduleGradient: ModuleColors.systemeGradient,
),
body: BlocBuilder<AdminUsersBloc, AdminUsersState>(
body: SafeArea(
top: false,
child: BlocBuilder<AdminUsersBloc, AdminUsersState>(
builder: (context, state) {
if (state is AdminUsersLoading) {
return const Center(child: CircularProgressIndicator());
@@ -35,11 +38,15 @@ class UserManagementDetailPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(state.message),
const Icon(Icons.error_outline, size: 48, color: AppColors.error),
const SizedBox(height: 12),
Text(state.message, textAlign: TextAlign.center),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.read<AdminUsersBloc>().add(AdminUserDetailRequested(userId)),
child: const Text('Réessayer'),
ElevatedButton.icon(
onPressed: () => context.read<AdminUsersBloc>()
.add(AdminUserDetailWithRolesRequested(userId)),
icon: const Icon(Icons.refresh, size: 16),
label: const Text('Réessayer'),
),
],
),
@@ -53,9 +60,14 @@ class UserManagementDetailPage extends StatelessWidget {
userId: userId,
);
}
// AdminUserRolesUpdated : rechargement en cours — garder un indicateur léger
if (state is AdminUserRolesUpdated) {
return const Center(child: CircularProgressIndicator());
}
return const SizedBox();
},
),
),
);
}
}
@@ -96,7 +108,13 @@ class _UserDetailContentState extends State<_UserDetailContent> {
);
}
},
child: RefreshIndicator(
color: ModuleColors.systeme,
onRefresh: () async => context
.read<AdminUsersBloc>()
.add(AdminUserDetailWithRolesRequested(widget.userId)),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -122,41 +140,95 @@ class _UserDetailContentState extends State<_UserDetailContent> {
),
),
const SizedBox(height: 8),
CoreCard(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.manage_accounts_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16),
const SizedBox(width: 8),
Text(
'RÔLES (SÉLECTION)',
'RÔLES',
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
letterSpacing: 1.1,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const Spacer(),
Text(
'${_selectedRoleNames.length} sélectionné(s)',
style: AppTypography.subtitleSmall.copyWith(
fontSize: 10,
color: AppColors.primary,
),
),
],
),
const SizedBox(height: 8),
if (widget.allRoles.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
const Icon(Icons.warning_amber_outlined,
size: 16, color: AppColors.error),
const SizedBox(width: 8),
Expanded(
child: Text(
'Impossible de charger les rôles disponibles.',
style: AppTypography.bodyTextSmall.copyWith(
color: AppColors.error),
),
),
TextButton.icon(
onPressed: () => context.read<AdminUsersBloc>()
.add(AdminUserDetailWithRolesRequested(widget.userId)),
icon: const Icon(Icons.refresh, size: 14),
label: const Text('Recharger'),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
],
),
)
else
...widget.allRoles.map((role) {
final selected = _selectedRoleNames.contains(role.name);
return CheckboxListTile(
title: Text(role.name, style: AppTypography.bodyTextSmall),
activeColor: AppColors.primaryGreen,
contentPadding: EdgeInsets.zero,
subtitle: role.description != null && role.description!.isNotEmpty
? Text(role.description!,
style: AppTypography.subtitleSmall.copyWith(fontSize: 10))
: null,
activeColor: AppColors.primary,
contentPadding: const EdgeInsets.symmetric(horizontal: 4),
dense: true,
value: selected,
onChanged: (v) {
setState(() {
onChanged: (v) => setState(() {
if (v == true) {
_selectedRoleNames.add(role.name);
} else {
_selectedRoleNames.remove(role.name);
}
});
},
}),
);
}),
const SizedBox(height: 12),
],
),
),
const SizedBox(height: 8),
if (widget.allRoles.isNotEmpty)
UFPrimaryButton(
label: 'Enregistrer les rôles',
onPressed: () {
context.read<AdminUsersBloc>().add(
AdminUserRolesUpdateRequested(widget.userId, _selectedRoleNames.toList()),
);
},
onPressed: () => context.read<AdminUsersBloc>().add(
AdminUserRolesUpdateRequested(
widget.userId, _selectedRoleNames.toList())),
),
const SizedBox(height: 12),
const Divider(height: 1),
@@ -171,7 +243,7 @@ class _UserDetailContentState extends State<_UserDetailContent> {
const SizedBox(height: 8),
Text(
'Permet à cet utilisateur (ex. admin d\'organisation) de voir « Mes organisations » et d\'accéder au dashboard de l\'organisation.',
style: AppTypography.bodyTextSmall.copyWith(color: AppColors.textSecondaryLight),
style: AppTypography.bodyTextSmall.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant),
),
const SizedBox(height: 12),
OutlinedButton.icon(
@@ -181,13 +253,14 @@ class _UserDetailContentState extends State<_UserDetailContent> {
icon: const Icon(Icons.business, size: 18),
label: const Text('Associer à une organisation'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryGreen,
side: const BorderSide(color: AppColors.primaryGreen),
foregroundColor: AppColors.primary,
side: const BorderSide(color: AppColors.primary),
),
),
],
),
),
), // SingleChildScrollView
), // RefreshIndicator
);
}

View File

@@ -4,6 +4,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:share_plus/share_plus.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/tokens/spacing_tokens.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../core/utils/logger.dart';
@@ -82,8 +83,10 @@ class _BackupPageState extends State<BackupPage>
},
builder: (context, state) {
return Scaffold(
backgroundColor: AppColors.lightBackground,
body: Column(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SafeArea(
top: false,
child: Column(
children: [
_buildHeader(),
_buildTabBar(),
@@ -99,27 +102,28 @@ class _BackupPageState extends State<BackupPage>
),
],
),
),
);
},
),
);
}
/// Header harmonisé
/// Header harmonisé avec bouton retour
Widget _buildHeader() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: SpacingTokens.sm, vertical: SpacingTokens.xs),
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: ColorTokens.primaryGradient,
gradient: LinearGradient(
colors: ModuleColors.backupGradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(SpacingTokens.radiusXl),
boxShadow: [
BoxShadow(
color: ColorTokens.primary.withOpacity(0.3),
color: ModuleColors.backup.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 8),
),
@@ -129,6 +133,19 @@ class _BackupPageState extends State<BackupPage>
children: [
Row(
children: [
// Bouton retour
GestureDetector(
onTap: () => Navigator.of(context).maybePop(),
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.arrow_back_rounded, color: Colors.white, size: 18),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
@@ -137,7 +154,7 @@ class _BackupPageState extends State<BackupPage>
),
child: const Icon(
Icons.backup,
color: Colors.white,
color: AppColors.onGradient,
size: 20,
),
),
@@ -151,14 +168,14 @@ class _BackupPageState extends State<BackupPage>
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
color: AppColors.onGradient,
),
),
Text(
'Gestion des sauvegardes système',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
color: AppColors.onGradient.withOpacity(0.8),
),
),
],
@@ -173,7 +190,7 @@ class _BackupPageState extends State<BackupPage>
onPressed: () => _createBackupNow(),
icon: const Icon(
Icons.save,
color: Colors.white,
color: AppColors.onGradient,
),
tooltip: 'Sauvegarde immédiate',
),
@@ -275,21 +292,21 @@ class _BackupPageState extends State<BackupPage>
),
child: Column(
children: [
Icon(icon, color: Colors.white, size: 20),
Icon(icon, color: AppColors.onGradient, size: 20),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
color: AppColors.onGradient,
),
),
Text(
label,
style: TextStyle(
fontSize: 10,
color: Colors.white.withOpacity(0.8),
color: AppColors.onGradient.withOpacity(0.8),
),
textAlign: TextAlign.center,
),
@@ -303,11 +320,11 @@ class _BackupPageState extends State<BackupPage>
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: AppColors.shadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -315,9 +332,9 @@ class _BackupPageState extends State<BackupPage>
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: Colors.grey[600],
indicatorColor: AppColors.primaryGreen,
labelColor: AppColors.primary,
unselectedLabelColor: Theme.of(context).colorScheme.onSurfaceVariant,
indicatorColor: AppColors.primary,
indicatorWeight: 3,
labelStyle: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12),
tabs: const [
@@ -331,7 +348,15 @@ class _BackupPageState extends State<BackupPage>
/// Onglet sauvegardes
Widget _buildBackupsTab(BackupState state) {
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.backup,
onRefresh: () async {
context.read<BackupBloc>()
..add(LoadBackups())
..add(LoadBackupConfig());
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -342,6 +367,7 @@ class _BackupPageState extends State<BackupPage>
const SizedBox(height: 80),
],
),
),
);
}
@@ -358,11 +384,11 @@ class _BackupPageState extends State<BackupPage>
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: AppColors.shadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -373,14 +399,14 @@ class _BackupPageState extends State<BackupPage>
children: [
Row(
children: [
const Icon(Icons.folder, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.folder, color: AppColors.primary, size: 20),
const SizedBox(width: 8),
Text(
'Sauvegardes disponibles',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
color: Theme.of(context).colorScheme.onSurface,
),
),
],
@@ -398,14 +424,14 @@ class _BackupPageState extends State<BackupPage>
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
color: Theme.of(context).colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
backup['type'] == 'Auto' ? Icons.schedule : Icons.touch_app,
color: backup['type'] == 'Auto' ? AppColors.primaryGreen : AppColors.success,
color: backup['type'] == 'Auto' ? AppColors.primary : AppColors.success,
size: 20,
),
const SizedBox(width: 12),
@@ -415,17 +441,19 @@ class _BackupPageState extends State<BackupPage>
children: [
Text(
backup['name']!,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textPrimaryLight,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimaryDark
: AppColors.textPrimary,
),
),
Text(
'${backup['date']}${backup['size']}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
@@ -438,7 +466,12 @@ class _BackupPageState extends State<BackupPage>
const PopupMenuItem(value: 'download', child: Text('Télécharger')),
const PopupMenuItem(value: 'delete', child: Text('Supprimer')),
],
child: const Icon(Icons.more_vert, color: Colors.grey),
child: Icon(
Icons.more_vert,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textTertiary,
),
),
],
),
@@ -447,7 +480,12 @@ class _BackupPageState extends State<BackupPage>
/// Onglet planification
Widget _buildScheduleTab() {
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.backup,
onRefresh: () async =>
context.read<BackupBloc>().add(LoadBackupConfig()),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -456,6 +494,7 @@ class _BackupPageState extends State<BackupPage>
const SizedBox(height: 80),
],
),
),
);
}
@@ -464,11 +503,11 @@ class _BackupPageState extends State<BackupPage>
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: AppColors.shadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -479,14 +518,14 @@ class _BackupPageState extends State<BackupPage>
children: [
Row(
children: [
const Icon(Icons.schedule, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.schedule, color: AppColors.primary, size: 20),
const SizedBox(width: 8),
Text(
'Configuration automatique',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
color: Theme.of(context).colorScheme.onSurface,
),
),
],
@@ -519,7 +558,12 @@ class _BackupPageState extends State<BackupPage>
/// Onglet restauration
Widget _buildRestoreTab() {
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.backup,
onRefresh: () async =>
context.read<BackupBloc>().add(LoadBackups()),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -528,6 +572,7 @@ class _BackupPageState extends State<BackupPage>
const SizedBox(height: 80),
],
),
),
);
}
@@ -536,11 +581,11 @@ class _BackupPageState extends State<BackupPage>
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: AppColors.shadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -551,14 +596,14 @@ class _BackupPageState extends State<BackupPage>
children: [
Row(
children: [
const Icon(Icons.restore, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.restore, color: AppColors.primary, size: 20),
const SizedBox(width: 8),
Text(
'Options de restauration',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
color: Theme.of(context).colorScheme.onSurface,
),
),
],
@@ -568,7 +613,7 @@ class _BackupPageState extends State<BackupPage>
'Restaurer depuis un fichier',
'Importer une sauvegarde externe',
Icons.file_upload,
AppColors.primaryGreen,
AppColors.primary,
() => _restoreFromFile(),
),
const SizedBox(height: 12),
@@ -601,11 +646,11 @@ class _BackupPageState extends State<BackupPage>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
Text(subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
Text(subtitle, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant)),
],
),
),
Switch(value: value, onChanged: onChanged, activeColor: AppColors.primaryGreen),
Switch(value: value, onChanged: onChanged, activeColor: AppColors.primary),
],
);
}
@@ -619,9 +664,9 @@ class _BackupPageState extends State<BackupPage>
Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: Colors.grey[50],
color: Theme.of(context).colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
border: Border.all(color: Theme.of(context).colorScheme.outline),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
@@ -656,11 +701,11 @@ class _BackupPageState extends State<BackupPage>
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: color)),
Text(subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
Text(subtitle, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant)),
],
),
),
Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 16),
Icon(Icons.arrow_forward_ios, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 16),
],
),
),

View File

@@ -2,7 +2,10 @@
library event_detail_page;
import 'package:flutter/material.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/di/injection.dart';
import '../../bloc/evenements_bloc.dart';
@@ -51,10 +54,9 @@ class _EventDetailPageState extends State<EventDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Détails de l\'événement'),
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
appBar: UFAppBar(
title: "Détails de l'événement",
moduleGradient: ModuleColors.evenementsGradient,
actions: [
IconButton(
icon: const Icon(Icons.edit),
@@ -62,9 +64,15 @@ class _EventDetailPageState extends State<EventDetailPage> {
),
],
),
body: BlocBuilder<EvenementsBloc, EvenementsState>(
body: SafeArea(
top: false,
child: BlocBuilder<EvenementsBloc, EvenementsState>(
builder: (context, state) {
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.evenements,
onRefresh: _loadInscriptionStatus,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -76,9 +84,11 @@ class _EventDetailPageState extends State<EventDetailPage> {
const SizedBox(height: 80), // Espace pour le bouton flottant
],
),
);
), // SingleChildScrollView
); // RefreshIndicator
},
),
),
floatingActionButton: _buildInscriptionButton(context),
);
}
@@ -87,12 +97,9 @@ class _EventDetailPageState extends State<EventDetailPage> {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.primaryGreen,
AppColors.primaryGreen.withOpacity(0.8),
],
colors: ModuleColors.evenementsGradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
@@ -191,7 +198,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Icon(icon, color: AppColors.primaryGreen, size: 20),
Icon(icon, color: ModuleColors.evenements, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
@@ -201,7 +208,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 2),
@@ -261,7 +268,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.location_on, color: AppColors.primaryGreen),
const Icon(Icons.location_on, color: ModuleColors.evenements),
const SizedBox(width: 8),
Expanded(
child: Text(
@@ -296,7 +303,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
'${widget.evenement.participantsActuels} inscrits',
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
@@ -305,7 +312,7 @@ class _EventDetailPageState extends State<EventDetailPage> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
@@ -335,14 +342,14 @@ class _EventDetailPageState extends State<EventDetailPage> {
if (!isComplet) {
return FloatingActionButton.extended(
onPressed: () => _showInscriptionDialog(context, isInscrit),
backgroundColor: AppColors.primaryGreen,
backgroundColor: ModuleColors.evenements,
icon: const Icon(Icons.check),
label: const Text('S\'inscrire'),
);
} else {
return const FloatingActionButton.extended(
onPressed: null,
backgroundColor: Colors.grey,
backgroundColor: AppColors.textTertiary,
icon: Icon(Icons.block),
label: Text('Complet'),
);
@@ -425,17 +432,17 @@ class _EventDetailPageState extends State<EventDetailPage> {
Color _getStatutColor(StatutEvenement statut) {
switch (statut) {
case StatutEvenement.planifie:
return AppColors.info;
return ColorTokens.info;
case StatutEvenement.confirme:
return AppColors.success;
return ColorTokens.success;
case StatutEvenement.enCours:
return AppColors.warning;
return ColorTokens.warningLight;
case StatutEvenement.termine:
return AppColors.textSecondaryLight;
return ColorTokens.textSecondary;
case StatutEvenement.annule:
return AppColors.error;
return ColorTokens.error;
case StatutEvenement.reporte:
return AppColors.brandGreen;
return ColorTokens.warning;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -71,18 +71,39 @@ class _NotificationsPageState extends State<NotificationsPage>
}
if (state is NotificationsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message), backgroundColor: Colors.red),
SnackBar(content: Text(state.message), backgroundColor: AppColors.error),
);
}
},
builder: (context, state) {
return Scaffold(
backgroundColor: AppColors.lightBackground,
body: Column(
children: [
_buildHeader(),
_buildTabBar(),
Expanded(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Notifications',
moduleGradient: ModuleColors.notificationsGradient,
actions: [
IconButton(
onPressed: () => _markAllAsRead(),
icon: const Icon(Icons.done_all, size: 20),
tooltip: 'Tout marquer comme lu',
),
],
bottom: TabBar(
controller: _tabController,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
Tab(child: Text('FLUX')),
Tab(child: Text('RÉGLAGES')),
],
),
),
body: SafeArea(
top: false,
child: TabBarView(
controller: _tabController,
children: [
@@ -91,106 +112,13 @@ class _NotificationsPageState extends State<NotificationsPage>
],
),
),
],
),
);
},
);
}
/// Header harmonisé avec le design system
Widget _buildHeader() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColors.brandGreen, AppColors.primaryGreen],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x1A000000),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.notifications_none,
color: Colors.white,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'NOTIFICATIONS',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.1,
),
),
Text(
'Restez connecté à votre réseau',
style: TextStyle(
fontSize: 11,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
IconButton(
onPressed: () => _markAllAsRead(),
icon: const Icon(Icons.done_all, color: Colors.white, size: 20),
tooltip: 'Tout marquer comme lu',
),
],
),
);
}
/// Barre d'onglets
Widget _buildTabBar() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: AppColors.lightSurface,
borderRadius: BorderRadius.circular(8),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: AppColors.primaryGreen.withOpacity(0.1),
),
labelStyle: AppTypography.actionText.copyWith(fontSize: 12),
unselectedLabelStyle: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
tabs: const [
Tab(text: 'FLUX'),
Tab(text: 'RÉGLAGES'),
],
),
);
}
// _buildHeader() et _buildTabBar() supprimés : gradient + TabBar migrés
// dans UFAppBar + UFAppBar.bottom (pattern Adhésions).
/// Onglet des notifications
Widget _buildNotificationsTab() {
@@ -236,7 +164,7 @@ class _NotificationsPageState extends State<NotificationsPage>
child: Switch(
value: _showOnlyUnread,
onChanged: (value) => setState(() => _showOnlyUnread = value),
activeColor: AppColors.primaryGreen,
activeColor: AppColors.primary,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
@@ -262,22 +190,25 @@ class _NotificationsPageState extends State<NotificationsPage>
/// Chip de filtre
Widget _buildFilterChip(String label, bool isSelected) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final borderInactive= isDark ? AppColors.borderDark : AppColors.border;
final textInactive = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return InkWell(
onTap: () => setState(() => _selectedFilter = label),
borderRadius: BorderRadius.circular(4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryGreen.withOpacity(0.1) : Colors.transparent,
color: isSelected ? AppColors.primary.withOpacity(isDark ? 0.2 : 0.1) : Colors.transparent,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isSelected ? AppColors.primaryGreen : AppColors.lightBorder,
color: isSelected ? AppColors.primary : borderInactive,
),
),
child: Text(
label.toUpperCase(),
style: AppTypography.badgeText.copyWith(
color: isSelected ? AppColors.primaryGreen : AppColors.textSecondaryLight,
color: isSelected ? AppColors.primary : textInactive,
fontSize: 9,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
@@ -290,17 +221,24 @@ class _NotificationsPageState extends State<NotificationsPage>
Widget _buildNotificationsList() {
final notifications = _getFilteredNotifications();
if (notifications.isEmpty) {
return _buildEmptyState();
}
return ListView.builder(
return RefreshIndicator(
color: ModuleColors.notifications,
onRefresh: () async =>
context.read<NotificationsBloc>().add(const LoadNotifications()),
child: notifications.isEmpty
? SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: _buildEmptyState(),
)
: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
itemCount: notifications.length,
itemBuilder: (context, index) {
final notification = notifications[index];
return _buildNotificationCard(notification);
},
),
);
}
@@ -310,10 +248,10 @@ class _NotificationsPageState extends State<NotificationsPage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icon(
Icons.notifications_none_outlined,
size: 40,
color: AppColors.textSecondaryLight,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(height: 12),
Text(
@@ -338,6 +276,10 @@ class _NotificationsPageState extends State<NotificationsPage>
final isRead = notification['isRead'] as bool;
final type = notification['type'] as String;
final color = _getNotificationColor(type);
final isDark = Theme.of(context).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
final bgRead = isDark ? AppColors.surfaceVariantDark : AppColors.surface;
return CoreCard(
margin: const EdgeInsets.only(bottom: 8),
@@ -349,8 +291,8 @@ class _NotificationsPageState extends State<NotificationsPage>
MiniAvatar(
fallbackText: _getNotificationIconSource(type),
size: 32,
backgroundColor: isRead ? AppColors.lightSurface : color.withOpacity(0.1),
iconColor: isRead ? AppColors.textSecondaryLight : color,
backgroundColor: isRead ? bgRead : color.withOpacity(isDark ? 0.2 : 0.1),
iconColor: isRead ? textSecondary : color,
isIcon: true,
),
const SizedBox(width: 12),
@@ -366,7 +308,7 @@ class _NotificationsPageState extends State<NotificationsPage>
notification['title'].toString().toUpperCase(),
style: AppTypography.actionText.copyWith(
fontSize: 11,
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
color: isRead ? textSecondary : textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -382,7 +324,7 @@ class _NotificationsPageState extends State<NotificationsPage>
Text(
notification['message'],
style: AppTypography.bodyTextSmall.copyWith(
color: isRead ? AppColors.textSecondaryLight : AppColors.textPrimaryLight,
color: isRead ? textSecondary : textPrimary,
fontWeight: isRead ? FontWeight.normal : FontWeight.w500,
),
maxLines: 2,
@@ -392,8 +334,8 @@ class _NotificationsPageState extends State<NotificationsPage>
const SizedBox(height: 4),
InfoBadge(
text: 'NOUVEAU',
backgroundColor: AppColors.primaryGreen.withOpacity(0.1),
textColor: AppColors.primaryGreen,
backgroundColor: AppColors.primary.withOpacity(0.1),
textColor: AppColors.primary,
),
],
],
@@ -413,7 +355,7 @@ class _NotificationsPageState extends State<NotificationsPage>
child: Text('Supprimer', style: AppTypography.bodyTextSmall.copyWith(color: AppColors.error)),
),
],
child: const Icon(Icons.more_vert, size: 14, color: AppColors.textSecondaryLight),
child: Icon(Icons.more_vert, size: 14, color: Theme.of(context).colorScheme.onSurfaceVariant),
),
],
),
@@ -545,7 +487,7 @@ class _NotificationsPageState extends State<NotificationsPage>
children: [
Icon(
icon,
color: AppColors.primaryGreen,
color: AppColors.primary,
size: 18,
),
const SizedBox(width: 10),
@@ -604,7 +546,7 @@ class _NotificationsPageState extends State<NotificationsPage>
child: Switch(
value: value,
onChanged: onChanged,
activeColor: AppColors.primaryGreen,
activeColor: AppColors.primary,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
@@ -659,15 +601,15 @@ class _NotificationsPageState extends State<NotificationsPage>
Color _getNotificationColor(String type) {
switch (type) {
case 'Membres':
return AppColors.primaryGreen;
return AppColors.primary;
case 'Événements':
return AppColors.success;
case 'Organisations':
return AppColors.primaryGreen;
return AppColors.primary;
case 'Système':
return AppColors.warning;
default:
return AppColors.textSecondaryLight;
return AppColors.textSecondary;
}
}
@@ -756,7 +698,7 @@ class _NotificationsPageState extends State<NotificationsPage>
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('ANNULER', style: AppTypography.actionText.copyWith(color: AppColors.textSecondaryLight)),
child: Text('ANNULER', style: AppTypography.actionText.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant)),
),
TextButton(
onPressed: () {
@@ -769,7 +711,7 @@ class _NotificationsPageState extends State<NotificationsPage>
});
_showSuccessSnackBar('Flux marqué comme lu');
},
child: Text('CONFIRMER', style: AppTypography.actionText.copyWith(color: AppColors.primaryGreen)),
child: Text('CONFIRMER', style: AppTypography.actionText.copyWith(color: AppColors.primary)),
),
],
),
@@ -797,8 +739,8 @@ class _NotificationsPageState extends State<NotificationsPage>
_tabController.animateTo(1); // Aller à l'onglet Préférences
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.primary,
foregroundColor: AppColors.onPrimary,
),
child: const Text('Voir les préférences'),
),
@@ -831,8 +773,8 @@ class _NotificationsPageState extends State<NotificationsPage>
_showSuccessSnackBar('Notification supprimée');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
backgroundColor: AppColors.error,
foregroundColor: AppColors.onError,
),
child: const Text('Supprimer'),
),
@@ -884,7 +826,7 @@ class _NotificationsPageState extends State<NotificationsPage>
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.warning,
foregroundColor: Colors.white,
foregroundColor: AppColors.onPrimary,
),
child: Text(notification['actionText']),
),

View File

@@ -5,6 +5,9 @@
library org_selector_page;
import 'package:flutter/material.dart';
import '../../../../shared/design_system/tokens/module_colors.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/components/uf_app_bar.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../bloc/org_switcher_bloc.dart';
import '../../data/models/org_switcher_entry.dart';
@@ -30,12 +33,14 @@ class _OrgSelectorPageState extends State<OrgSelectorPage> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Choisir une organisation'),
appBar: UFAppBar(
title: 'Choisir une organisation',
moduleGradient: ModuleColors.organisationsGradient,
automaticallyImplyLeading: !widget.required,
elevation: 0,
),
body: BlocConsumer<OrgSwitcherBloc, OrgSwitcherState>(
body: SafeArea(
top: false,
child: BlocConsumer<OrgSwitcherBloc, OrgSwitcherState>(
listener: (context, state) {
if (state is OrgSwitcherLoaded && widget.required && state.active != null) {
// Une org a été auto-sélectionnée, on peut continuer
@@ -71,6 +76,7 @@ class _OrgSelectorPageState extends State<OrgSelectorPage> {
return const SizedBox.shrink();
},
),
),
);
}
}
@@ -103,7 +109,12 @@ class _OrgList extends StatelessWidget {
),
),
Expanded(
child: RefreshIndicator(
color: ModuleColors.organisations,
onRefresh: () async =>
context.read<OrgSwitcherBloc>().add(const OrgSwitcherLoadRequested()),
child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
itemCount: organisations.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
@@ -116,7 +127,8 @@ class _OrgList extends StatelessWidget {
onTap: () => onSelect(org),
);
},
),
), // ListView.separated
), // RefreshIndicator
),
],
);
@@ -199,8 +211,8 @@ class _OrgCard extends StatelessWidget {
_Chip(
org.statutMembre!,
color: org.statutMembre == 'ACTIF'
? Colors.green.shade700
: Colors.orange.shade700,
? AppColors.success
: AppColors.warning,
),
],
],
@@ -268,7 +280,7 @@ class _ErrorView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.cloud_off, size: 56, color: Colors.grey),
Icon(Icons.cloud_off, size: 56, color: AppColors.textTertiary),
const SizedBox(height: 16),
Text(message, textAlign: TextAlign.center),
const SizedBox(height: 16),
@@ -295,7 +307,7 @@ class _EmptyView extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.business_outlined, size: 56, color: Colors.grey),
Icon(Icons.business_outlined, size: 56, color: AppColors.textTertiary),
SizedBox(height: 16),
Text(
'Vous n\'êtes membre d\'aucune organisation active.',

View File

@@ -30,9 +30,10 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.lightBackground,
appBar: const UFAppBar(
title: 'TYPES D\'ORGANISATIONS',
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: "Types d'Organisations",
moduleGradient: ModuleColors.organisationsGradient,
automaticallyImplyLeading: true,
),
body: BlocConsumer<OrgTypesBloc, OrgTypesState>(
@@ -41,7 +42,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: Colors.green,
backgroundColor: AppColors.success,
duration: const Duration(seconds: 2),
),
);
@@ -49,7 +50,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: Colors.red,
backgroundColor: AppColors.error,
duration: const Duration(seconds: 3),
),
);
@@ -86,7 +87,7 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
if (!isSuperAdmin) return const SizedBox.shrink();
return FloatingActionButton.small(
onPressed: () => _showTypeForm(context, null),
backgroundColor: AppColors.primaryGreen,
backgroundColor: AppColors.primary,
child: const Icon(Icons.add, color: Colors.white),
);
}),
@@ -103,10 +104,13 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
Widget _buildTypeCard(BuildContext context, TypeReferenceEntity type, OrgTypesState state) {
final isOperating = state is OrgTypeOperating;
final color = _parseColor(type.couleur) ?? AppColors.primaryGreen;
final color = _parseColor(type.couleur) ?? AppColors.primary;
final authState = context.read<AuthBloc>().state;
final isSuperAdmin = authState is AuthAuthenticated &&
authState.effectiveRole == UserRole.superAdmin;
final isDark = Theme.of(context).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
final textSecondary= isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
return Opacity(
opacity: isOperating ? 0.6 : 1.0,
@@ -145,28 +149,28 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
),
if (type.estDefaut) ...[
const SizedBox(width: 6),
const Icon(Icons.star_rounded, size: 13, color: Color(0xFFF59E0B)),
const Icon(Icons.star_rounded, size: 13, color: AppColors.warning),
],
if (type.estSysteme) ...[
const SizedBox(width: 6),
Icon(Icons.lock_outline, size: 12, color: Colors.grey[500]),
Icon(Icons.lock_outline, size: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
],
],
),
const SizedBox(height: 4),
Text(
type.libelle,
style: const TextStyle(
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: AppColors.textPrimaryLight,
color: textPrimary,
),
),
if (type.description != null && type.description!.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
type.description!,
style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight),
style: TextStyle(fontSize: 11, color: textSecondary),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -177,14 +181,14 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
if (isSuperAdmin && !type.estSysteme && !isOperating) ...[
IconButton(
icon: const Icon(Icons.edit_outlined, size: 16),
color: AppColors.textSecondaryLight,
color: AppColors.textSecondary,
onPressed: () => _showTypeForm(context, type),
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
IconButton(
icon: const Icon(Icons.delete_outline, size: 16),
color: Colors.red[400],
color: AppColors.error,
onPressed: () => _confirmDelete(context, type),
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
@@ -208,18 +212,24 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.category_outlined, size: 48, color: Colors.grey[400]),
Icon(Icons.category_outlined, size: 48, color: Theme.of(context).colorScheme.onSurfaceVariant),
const SizedBox(height: 12),
const Text(
Text(
'Aucun type défini',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.textPrimaryLight),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimaryDark
: AppColors.textPrimary,
),
),
const SizedBox(height: 6),
Text(
isSuperAdmin
? 'Créez votre premier type d\'organisation'
: 'Aucun type d\'organisation disponible',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
textAlign: TextAlign.center,
),
if (isSuperAdmin) ...[
@@ -229,8 +239,8 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
icon: const Icon(Icons.add, size: 16),
label: const Text('Créer un type'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.primary,
foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
@@ -249,17 +259,26 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 40, color: Colors.red[400]),
Icon(Icons.error_outline, size: 40, color: AppColors.error),
const SizedBox(height: 12),
const Text('Erreur de chargement', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700)),
const SizedBox(height: 6),
Text(message, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight), textAlign: TextAlign.center),
Text(
message,
style: TextStyle(
fontSize: 11,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.read<OrgTypesBloc>().add(const LoadOrgTypes()),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.primary,
foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
@@ -303,8 +322,8 @@ class _OrgTypesViewState extends State<_OrgTypesView> {
bloc.add(DeleteOrgTypeEvent(type.id));
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
backgroundColor: AppColors.error,
foregroundColor: AppColors.onError,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
),
@@ -370,12 +389,16 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
@override
Widget build(BuildContext context) {
final isEdit = widget.existing != null;
final isDark = Theme.of(context).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimary;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondary;
final onSurface = Theme.of(context).colorScheme.onSurface;
return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Form(
@@ -389,13 +412,13 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
child: Container(
width: 36,
height: 4,
decoration: BoxDecoration(color: Colors.grey[300], borderRadius: BorderRadius.circular(2)),
decoration: BoxDecoration(color: Theme.of(context).colorScheme.outline, borderRadius: BorderRadius.circular(2)),
),
),
const SizedBox(height: 14),
Text(
isEdit ? 'Modifier le type' : 'Nouveau type d\'organisation',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: AppColors.textPrimaryLight),
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: textPrimary),
),
const SizedBox(height: 14),
@@ -446,7 +469,7 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
const SizedBox(height: 12),
// Color picker
const Text('Couleur', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.textSecondaryLight)),
Text('Couleur', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: textSecondary)),
const SizedBox(height: 6),
Wrap(
spacing: 8,
@@ -462,7 +485,7 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: selected ? Border.all(color: Colors.black, width: 2) : null,
border: selected ? Border.all(color: onSurface, width: 2) : null,
),
child: selected ? const Icon(Icons.check, size: 14, color: Colors.white) : null,
),
@@ -477,8 +500,8 @@ class _OrgTypeFormSheetState extends State<_OrgTypeFormSheet> {
child: ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
foregroundColor: Colors.white,
backgroundColor: AppColors.primary,
foregroundColor: AppColors.onPrimary,
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),

View File

@@ -11,10 +11,11 @@ import '../../bloc/organizations_event.dart';
import '../../bloc/organizations_state.dart';
import '../../domain/repositories/organization_repository.dart';
import 'edit_organization_page.dart';
import '../../../../shared/design_system/tokens/app_colors.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../../../features/authentication/data/models/user_role.dart';
class OrganizationDetailPage extends StatefulWidget {
final String organizationId;
@@ -44,12 +45,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.lightBackground,
appBar: AppBar(
backgroundColor: AppColors.brandGreen,
foregroundColor: Colors.white,
title: const Text('Détail Organisation'),
elevation: 0,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Détail Organisation',
moduleGradient: ModuleColors.organisationsGradient,
actions: [
Builder(builder: (ctx) {
final authState = ctx.read<AuthBloc>().state;
@@ -67,15 +66,17 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
return PopupMenuButton<String>(
onSelected: _handleMenuAction,
itemBuilder: (context) => [
const PopupMenuItem(value: 'activate', child: Row(children: [Icon(Icons.check_circle, color: AppColors.success), SizedBox(width: 8), Text('Activer')])),
const PopupMenuItem(value: 'deactivate', child: Row(children: [Icon(Icons.pause_circle, color: AppColors.textSecondaryLight), SizedBox(width: 8), Text('Désactiver')])),
const PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, color: Colors.red), SizedBox(width: 8), Text('Supprimer')])),
const PopupMenuItem(value: 'activate', child: Row(children: [Icon(Icons.check_circle, color: AppColors.success), SizedBox(width: SpacingTokens.md), Text('Activer')])),
const PopupMenuItem(value: 'deactivate', child: Row(children: [Icon(Icons.pause_circle, color: AppColors.textSecondary), SizedBox(width: SpacingTokens.md), Text('Désactiver')])),
const PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, color: AppColors.error), SizedBox(width: SpacingTokens.md), Text('Supprimer')])),
],
);
}),
],
),
body: BlocBuilder<OrganizationsBloc, OrganizationsState>(
body: SafeArea(
top: false,
child: BlocBuilder<OrganizationsBloc, OrganizationsState>(
builder: (context, state) {
if (state is OrganizationLoading) return _buildLoading();
if (state is OrganizationLoaded) return _buildContent(state.organization);
@@ -83,74 +84,82 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
return _buildEmpty();
},
),
),
);
}
Widget _buildContent(OrganizationModel org) {
return SingleChildScrollView(
padding: const EdgeInsets.all(12),
return RefreshIndicator(
color: ModuleColors.organisations,
onRefresh: () async => context
.read<OrganizationsBloc>()
.add(LoadOrganizationById(widget.organizationId)),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(SpacingTokens.lg),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeaderCard(org),
const SizedBox(height: 8),
const SizedBox(height: SpacingTokens.md),
_buildInfoCard(org),
const SizedBox(height: 8),
const SizedBox(height: SpacingTokens.md),
_buildContactCard(org),
if (_hasAddress(org)) ...[const SizedBox(height: 8), _buildAddressCard(org)],
const SizedBox(height: 8),
if (_hasAddress(org)) ...[const SizedBox(height: SpacingTokens.md), _buildAddressCard(org)],
const SizedBox(height: SpacingTokens.md),
_buildStatsCard(org),
if (_hasFinances(org)) ...[const SizedBox(height: 8), _buildFinancesCard(org)],
if (_hasMission(org)) ...[const SizedBox(height: 8), _buildMissionCard(org)],
if (_hasSupplementary(org)) ...[const SizedBox(height: 8), _buildSupplementaryCard(org)],
if (org.notes?.isNotEmpty == true) ...[const SizedBox(height: 8), _buildNotesCard(org)],
const SizedBox(height: 8),
if (_hasFinances(org)) ...[const SizedBox(height: SpacingTokens.md), _buildFinancesCard(org)],
if (_hasMission(org)) ...[const SizedBox(height: SpacingTokens.md), _buildMissionCard(org)],
if (_hasSupplementary(org)) ...[const SizedBox(height: SpacingTokens.md), _buildSupplementaryCard(org)],
if (org.notes?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildNotesCard(org)],
const SizedBox(height: SpacingTokens.md),
_buildActionsCard(org),
],
),
);
), // Column
), // SingleChildScrollView
); // RefreshIndicator
}
// ── Header ─────────────────────────────────────────────────────────────────
Widget _buildHeaderCard(OrganizationModel org) {
return Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.brandGreen, AppColors.primaryGreen]),
borderRadius: BorderRadius.circular(10),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2))],
gradient: const LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ModuleColors.organisationsDark, ModuleColors.organisations]),
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
boxShadow: [BoxShadow(color: AppColors.shadowMedium, blurRadius: 8, offset: const Offset(0, 2))],
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd)),
child: const Icon(Icons.business_outlined, size: 24, color: Colors.white),
),
const SizedBox(width: 16),
const SizedBox(width: SpacingTokens.xl),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(org.nom, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),
if (org.nomCourt?.isNotEmpty == true) ...[
const SizedBox(height: 2),
const SizedBox(height: SpacingTokens.xs),
Text(org.nomCourt!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9))),
],
const SizedBox(height: 6),
const SizedBox(height: SpacingTokens.md),
Row(children: [
_buildWhiteBadge(org.typeOrganisationLibelle ?? org.typeOrganisation),
const SizedBox(width: 6),
const SizedBox(width: SpacingTokens.md),
_buildWhiteBadge(org.statutLibelle ?? org.statut.displayName),
]),
])),
]),
if (org.description?.isNotEmpty == true) ...[
const SizedBox(height: 12),
const SizedBox(height: SpacingTokens.lg),
Text(org.description!, style: TextStyle(fontSize: 13, color: Colors.white.withOpacity(0.9), height: 1.4)),
],
const SizedBox(height: 10),
const SizedBox(height: SpacingTokens.md),
Row(children: [
_buildBoolBadge(Icons.public, 'Public', org.organisationPublique),
const SizedBox(width: 8),
const SizedBox(width: SpacingTokens.md),
_buildBoolBadge(Icons.person_add, 'Ouvert', org.accepteNouveauxMembres),
]),
]),
@@ -159,17 +168,19 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildWhiteBadge(String text) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(10)),
padding: const EdgeInsets.symmetric(horizontal: SpacingTokens.md, vertical: 3),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(SpacingTokens.radiusLg)),
child: Text(text, style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white)),
);
}
Widget _buildBoolBadge(IconData icon, String label, bool value) {
// Sur gradient : vert clair lisible pour TRUE, blanc translucide pour FALSE
final color = value ? AppColors.successUI : Colors.white.withOpacity(0.6);
return Row(mainAxisSize: MainAxisSize.min, children: [
Icon(icon, size: 12, color: value ? Colors.greenAccent : Colors.white60),
const SizedBox(width: 4),
Text('$label: ${value ? 'Oui' : 'Non'}', style: TextStyle(fontSize: 11, color: value ? Colors.greenAccent : Colors.white60)),
Icon(icon, size: 12, color: color),
const SizedBox(width: SpacingTokens.sm),
Text('$label: ${value ? 'Oui' : 'Non'}', style: TextStyle(fontSize: 11, color: color)),
]);
}
@@ -178,14 +189,14 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildInfoCard(OrganizationModel org) {
return _buildCard('Informations générales', Icons.info_outline, [
if (org.dateFondation != null) _buildInfoRow(Icons.cake, 'Date de fondation', _formatDate(org.dateFondation)),
if (org.dateFondation != null) const SizedBox(height: 10),
if (org.dateFondation != null) const SizedBox(height: SpacingTokens.md),
if (org.ancienneteAnnees > 0) _buildInfoRow(Icons.access_time, 'Ancienneté', '${org.ancienneteAnnees} an(s)'),
if (org.ancienneteAnnees > 0) const SizedBox(height: 10),
if (org.ancienneteAnnees > 0) const SizedBox(height: SpacingTokens.md),
if (org.numeroEnregistrement?.isNotEmpty == true) _buildInfoRow(Icons.assignment, 'N° d\'enregistrement', org.numeroEnregistrement!),
if (org.numeroEnregistrement?.isNotEmpty == true) const SizedBox(height: 10),
if (org.numeroEnregistrement?.isNotEmpty == true) const SizedBox(height: SpacingTokens.md),
_buildInfoRow(Icons.calendar_today, 'Créé dans le système', _formatDate(org.dateCreation)),
if (org.dateModification != null) ...[
const SizedBox(height: 10),
const SizedBox(height: SpacingTokens.md),
_buildInfoRow(Icons.edit_calendar, 'Dernière modification', _formatDate(org.dateModification)),
],
]);
@@ -204,11 +215,11 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
return _buildCard('Contact', Icons.contact_phone, [
if (org.email?.isNotEmpty == true) _buildContactRow(Icons.email, 'Email', org.email!, onTap: () => _launchEmail(org.email!)),
if (org.emailSecondaire?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.alternate_email, 'Email secondaire', org.emailSecondaire!, onTap: () => _launchEmail(org.emailSecondaire!))],
if (org.telephone?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.phone, 'Téléphone', org.telephone!, onTap: () => _launchPhone(org.telephone!))],
if (org.telephoneSecondaire?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.phone_forwarded, 'Téléphone secondaire', org.telephoneSecondaire!, onTap: () => _launchPhone(org.telephoneSecondaire!))],
if (org.siteWeb?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildContactRow(Icons.web, 'Site web', org.siteWeb!, onTap: () => _launchWebsite(org.siteWeb!))],
if (org.reseauxSociaux?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.share, 'Réseaux sociaux', org.reseauxSociaux!)],
if (org.emailSecondaire?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.alternate_email, 'Email secondaire', org.emailSecondaire!, onTap: () => _launchEmail(org.emailSecondaire!))],
if (org.telephone?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.phone, 'Téléphone', org.telephone!, onTap: () => _launchPhone(org.telephone!))],
if (org.telephoneSecondaire?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.phone_forwarded, 'Téléphone secondaire', org.telephoneSecondaire!, onTap: () => _launchPhone(org.telephoneSecondaire!))],
if (org.siteWeb?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildContactRow(Icons.web, 'Site web', org.siteWeb!, onTap: () => _launchWebsite(org.siteWeb!))],
if (org.reseauxSociaux?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.share, 'Réseaux sociaux', org.reseauxSociaux!)],
]);
}
@@ -221,10 +232,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildAddressCard(OrganizationModel org) {
return _buildCard('Localisation', Icons.location_on, [
if (org.adresse?.isNotEmpty == true) _buildInfoRow(Icons.location_on, 'Adresse', org.adresse!),
if (org.adresse?.isNotEmpty == true && (org.ville?.isNotEmpty == true)) const SizedBox(height: 10),
if (org.adresse?.isNotEmpty == true && (org.ville?.isNotEmpty == true)) const SizedBox(height: SpacingTokens.md),
if (org.ville?.isNotEmpty == true) _buildInfoRow(Icons.location_city, 'Ville', '${org.ville!}${org.codePostal?.isNotEmpty == true ? '${org.codePostal}' : ''}'),
if (org.region?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.map, 'Région', org.region!)],
if (org.pays?.isNotEmpty == true) ...[const SizedBox(height: 10), _buildInfoRow(Icons.flag, 'Pays', org.pays!)],
if (org.region?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.map, 'Région', org.region!)],
if (org.pays?.isNotEmpty == true) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.flag, 'Pays', org.pays!)],
]);
}
@@ -233,10 +244,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildStatsCard(OrganizationModel org) {
return _buildCard('Statistiques', Icons.bar_chart, [
Row(children: [
Expanded(child: _buildStatItem(Icons.people, 'Membres', (_memberCount ?? org.nombreMembres).toString(), AppColors.primaryGreen)),
const SizedBox(width: 10),
Expanded(child: _buildStatItem(Icons.admin_panel_settings, 'Admins', org.nombreAdministrateurs.toString(), AppColors.brandGreen)),
const SizedBox(width: 10),
Expanded(child: _buildStatItem(Icons.people, 'Membres', (_memberCount ?? org.nombreMembres).toString(), ModuleColors.organisations)),
const SizedBox(width: SpacingTokens.md),
Expanded(child: _buildStatItem(Icons.admin_panel_settings, 'Admins', org.nombreAdministrateurs.toString(), ModuleColors.organisationsDark)),
const SizedBox(width: SpacingTokens.md),
Expanded(child: _buildStatItem(Icons.event, 'Événements', (org.nombreEvenements ?? 0).toString(), AppColors.success)),
]),
]);
@@ -244,11 +255,11 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildStatItem(IconData icon, String label, String value, Color color) {
return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(6), border: Border.all(color: color.withOpacity(0.15))),
padding: const EdgeInsets.all(SpacingTokens.md),
decoration: BoxDecoration(color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(SpacingTokens.radiusMd), border: Border.all(color: color.withOpacity(0.15))),
child: Column(children: [
Icon(icon, size: 20, color: color),
const SizedBox(height: 4),
const SizedBox(height: SpacingTokens.sm),
Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: color)),
Text(label, style: TextStyle(fontSize: 11, color: color)),
]),
@@ -263,10 +274,10 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildFinancesCard(OrganizationModel org) {
return _buildCard('Finances', Icons.account_balance_wallet, [
_buildInfoRow(Icons.currency_exchange, 'Devise', org.devise),
if (org.budgetAnnuel != null) ...[const SizedBox(height: 10), _buildInfoRow(Icons.account_balance, 'Budget annuel', '${_formatMontant(org.budgetAnnuel)} ${org.devise}')],
const SizedBox(height: 10),
if (org.budgetAnnuel != null) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.account_balance, 'Budget annuel', '${_formatMontant(org.budgetAnnuel)} ${org.devise}')],
const SizedBox(height: SpacingTokens.md),
_buildInfoRow(Icons.payments, 'Cotisation', org.cotisationObligatoire ? 'Obligatoire' : 'Facultative'),
if (org.cotisationObligatoire && org.montantCotisationAnnuelle != null) ...[const SizedBox(height: 10), _buildInfoRow(Icons.money, 'Montant annuel', '${_formatMontant(org.montantCotisationAnnuelle)} ${org.devise}')],
if (org.cotisationObligatoire && org.montantCotisationAnnuelle != null) ...[const SizedBox(height: SpacingTokens.md), _buildInfoRow(Icons.money, 'Montant annuel', '${_formatMontant(org.montantCotisationAnnuelle)} ${org.devise}')],
]);
}
@@ -278,7 +289,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildMissionCard(OrganizationModel org) {
return _buildCard('Mission & Activités', Icons.flag, [
if (org.objectifs?.isNotEmpty == true) _buildTextBlock(Icons.track_changes, 'Objectifs', org.objectifs!),
if (org.objectifs?.isNotEmpty == true && org.activitesPrincipales?.isNotEmpty == true) const SizedBox(height: 12),
if (org.objectifs?.isNotEmpty == true && org.activitesPrincipales?.isNotEmpty == true) const SizedBox(height: SpacingTokens.lg),
if (org.activitesPrincipales?.isNotEmpty == true) _buildTextBlock(Icons.work, 'Activités principales', org.activitesPrincipales!),
]);
}
@@ -291,7 +302,7 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildSupplementaryCard(OrganizationModel org) {
return _buildCard('Informations complémentaires', Icons.info, [
if (org.certifications?.isNotEmpty == true) _buildTextBlock(Icons.verified, 'Certifications / Agréments', org.certifications!),
if (org.certifications?.isNotEmpty == true && org.partenaires?.isNotEmpty == true) const SizedBox(height: 12),
if (org.certifications?.isNotEmpty == true && org.partenaires?.isNotEmpty == true) const SizedBox(height: SpacingTokens.lg),
if (org.partenaires?.isNotEmpty == true) _buildTextBlock(Icons.handshake, 'Partenaires', org.partenaires!),
]);
}
@@ -299,20 +310,34 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
// ── Notes internes ──────────────────────────────────────────────────────────
Widget _buildNotesCard(OrganizationModel org) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: const Color(0xFFFFFBEB),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFCD34D), width: 1),
// Fond warning adaptatif (jaune clair light / surface sombre dark)
color: isDark
? AppColors.surfaceVariantDark
: AppColors.warningContainer,
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
border: Border.all(color: AppColors.warningUI, width: 1),
),
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Icon(Icons.sticky_note_2, size: 18, color: Color(0xFFF59E0B)),
const SizedBox(width: 10),
const Icon(Icons.sticky_note_2, size: 18, color: AppColors.warningUI),
const SizedBox(width: SpacingTokens.md),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Text('Notes internes', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Color(0xFFF59E0B))),
const SizedBox(height: 4),
Text(org.notes!, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, height: 1.4)),
Text('Notes internes',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isDark ? AppColors.warningUI : AppColors.warning,
)),
const SizedBox(height: SpacingTokens.sm),
Text(org.notes!,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.onSurface,
height: 1.4,
)),
])),
]),
);
@@ -337,15 +362,15 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
onPressed: _showEditPage,
icon: const Icon(Icons.edit),
label: const Text('Modifier'),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
style: ElevatedButton.styleFrom(backgroundColor: ModuleColors.organisations, foregroundColor: AppColors.onPrimary),
)),
if (isSuperAdmin) ...[
const SizedBox(width: 12),
const SizedBox(width: SpacingTokens.lg),
Expanded(child: OutlinedButton.icon(
onPressed: () => _showDeleteConfirmation(org),
icon: const Icon(Icons.delete),
label: const Text('Supprimer'),
style: OutlinedButton.styleFrom(foregroundColor: Colors.red, side: const BorderSide(color: Colors.red)),
style: OutlinedButton.styleFrom(foregroundColor: AppColors.error, side: const BorderSide(color: AppColors.error)),
)),
],
]),
@@ -356,15 +381,19 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildCard(String title, IconData icon, List<Widget> children) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(SpacingTokens.radiusLg),
border: Border.all(color: Theme.of(context).colorScheme.outlineVariant, width: 1),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Icon(icon, size: 15, color: AppColors.primaryGreen),
const SizedBox(width: 6),
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.primaryGreen)),
Icon(icon, size: 15, color: ModuleColors.organisations),
const SizedBox(width: SpacingTokens.md),
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: ModuleColors.organisations)),
]),
const SizedBox(height: 10),
const SizedBox(height: SpacingTokens.md),
...children,
]),
);
@@ -372,12 +401,12 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildInfoRow(IconData icon, String label, String value) {
return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
Icon(icon, size: 18, color: AppColors.primaryGreen),
const SizedBox(width: 10),
Icon(icon, size: 18, color: ModuleColors.organisations),
const SizedBox(width: SpacingTokens.md),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight, fontWeight: FontWeight.w500)),
const SizedBox(height: 2),
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, fontWeight: FontWeight.w600)),
Text(label, style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500)),
const SizedBox(height: SpacingTokens.xs),
Text(value, style: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w600)),
])),
]);
}
@@ -385,29 +414,29 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
Widget _buildTextBlock(IconData icon, String label, String value) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Icon(icon, size: 15, color: AppColors.primaryGreen),
const SizedBox(width: 6),
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondaryLight, fontWeight: FontWeight.w600)),
Icon(icon, size: 15, color: ModuleColors.organisations),
const SizedBox(width: SpacingTokens.md),
Text(label, style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w600)),
]),
const SizedBox(height: 4),
Text(value, style: const TextStyle(fontSize: 13, color: AppColors.textPrimaryLight, height: 1.5)),
const SizedBox(height: SpacingTokens.sm),
Text(value, style: TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.onSurface, height: 1.5)),
]);
}
Widget _buildContactRow(IconData icon, String label, String value, {VoidCallback? onTap}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(6),
borderRadius: BorderRadius.circular(SpacingTokens.radiusMd),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(children: [
Icon(icon, size: 18, color: AppColors.primaryGreen),
const SizedBox(width: 10),
Icon(icon, size: 18, color: ModuleColors.organisations),
const SizedBox(width: SpacingTokens.md),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(label, style: const TextStyle(fontSize: 11, color: AppColors.textSecondaryLight)),
Text(value, style: TextStyle(fontSize: 13, color: onTap != null ? AppColors.primaryGreen : AppColors.textPrimaryLight, fontWeight: FontWeight.w600, decoration: onTap != null ? TextDecoration.underline : null)),
Text(label, style: TextStyle(fontSize: 11, color: Theme.of(context).colorScheme.onSurfaceVariant)),
Text(value, style: TextStyle(fontSize: 13, color: onTap != null ? ModuleColors.organisations : Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.w600, decoration: onTap != null ? TextDecoration.underline : null)),
])),
if (onTap != null) const Icon(Icons.open_in_new, size: 14, color: AppColors.primaryGreen),
if (onTap != null) const Icon(Icons.open_in_new, size: 14, color: ModuleColors.organisations),
]),
),
);
@@ -416,28 +445,28 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
// ── États ────────────────────────────────────────────────────────────────────
Widget _buildLoading() => const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen)),
SizedBox(height: 16),
Text('Chargement...', style: TextStyle(color: AppColors.textSecondaryLight)),
CircularProgressIndicator(color: ModuleColors.organisations),
SizedBox(height: SpacingTokens.xl),
Text('Chargement...'),
]));
Widget _buildError(OrganizationsError state) => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.error_outline, size: 64, color: Colors.red.shade400),
const SizedBox(height: 16),
Text(state.message, textAlign: TextAlign.center, style: const TextStyle(color: AppColors.textSecondaryLight)),
Icon(Icons.error_outline, size: 64, color: Theme.of(context).colorScheme.error),
const SizedBox(height: SpacingTokens.xl),
Text(state.message, textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant)),
const SizedBox(height: 24),
ElevatedButton.icon(
FilledButton.icon(
onPressed: () => context.read<OrganizationsBloc>().add(LoadOrganizationById(widget.organizationId)),
icon: const Icon(Icons.refresh),
icon: const Icon(Icons.refresh, size: 18),
label: const Text('Réessayer'),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
style: FilledButton.styleFrom(backgroundColor: ModuleColors.organisations),
),
]));
Widget _buildEmpty() => const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.business_outlined, size: 64, color: AppColors.textSecondaryLight),
SizedBox(height: 16),
Text('Organisation non trouvée', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimaryLight)),
Widget _buildEmpty() => Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Icon(Icons.business_outlined, size: 64, color: Theme.of(context).colorScheme.onSurfaceVariant),
const SizedBox(height: SpacingTokens.xl),
Text('Organisation non trouvée', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface)),
]));
// ── Actions ──────────────────────────────────────────────────────────────────
@@ -475,8 +504,8 @@ class _OrganizationDetailPageState extends State<OrganizationDetailPage> {
TextButton(onPressed: () => Navigator.of(ctx).pop(), child: const Text('Annuler')),
ElevatedButton(
onPressed: () { Navigator.of(ctx).pop(); bloc.add(DeleteOrganization(widget.organizationId)); nav.pop(); },
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Supprimer', style: TextStyle(color: Colors.white)),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.error),
child: const Text('Supprimer', style: TextStyle(color: AppColors.onPrimary)),
),
],
),

View File

@@ -10,9 +10,11 @@ import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../../shared/design_system/unionflow_design_system.dart';
import '../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../shared/widgets/core_card.dart';
import '../../../../shared/widgets/info_badge.dart';
import '../../../../shared/widgets/mini_avatar.dart';
import '../../../../core/config/environment.dart';
import '../../../../core/l10n/locale_provider.dart';
import '../../../../core/theme/theme_provider.dart';
import '../../../authentication/presentation/bloc/auth_bloc.dart';
@@ -66,15 +68,22 @@ class _ProfilePageState extends State<ProfilePage>
// Infos app (chargées via package_info_plus)
String _appVersion = '...';
String _platformInfo = '...';
String _osVersion = '...';
String _envInfo = '...';
// Tailles stockage (calculées au chargement)
String _cacheSize = '...';
String _imagesSize = '...';
String _offlineSize = '...';
// Options développeur (persistées via SharedPreferences)
bool _devMode = false;
bool _detailedLogs = false;
final List<String> _themes = ['Système', 'Clair', 'Sombre'];
static const _keyNotifPush = 'notif_push';
static const _keyNotifEmail = 'notif_email';
static const _keyNotifSon = 'notif_son';
static const _keyDevMode = 'dev_mode';
static const _keyDetailedLogs = 'detailed_logs';
@override
void initState() {
@@ -136,26 +145,39 @@ class _ProfilePageState extends State<ProfilePage>
child: Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'MON PROFIL',
backgroundColor: Theme.of(context).cardColor,
foregroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.textPrimaryDark
: AppColors.textPrimaryLight,
title: 'Mon Profil',
moduleGradient: ModuleColors.profilGradient,
actions: [
IconButton(
icon: Icon(_isEditing ? Icons.save_outlined : Icons.edit_outlined, size: 20),
onPressed: () => _isEditing ? _saveProfile() : _startEditing(),
),
],
bottom: TabBar(
controller: _tabController,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
Tab(child: Text('PERSO')),
Tab(child: Text('PRÉFÉRENCES')),
Tab(child: Text('SÉCURITÉ')),
Tab(child: Text('AVANCÉ')),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
body: SafeArea(
top: false,
child: Column(
children: [
_buildHeader(),
const SizedBox(height: 8),
_buildTabBar(),
SizedBox(
height: 600, // Ajuster selon contenu ou utiliser NestedScrollView
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: _buildHeader(),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
@@ -169,6 +191,7 @@ class _ProfilePageState extends State<ProfilePage>
],
),
),
),
);
}
@@ -176,6 +199,7 @@ class _ProfilePageState extends State<ProfilePage>
Widget _buildHeader() {
return BlocBuilder<ProfileBloc, ProfileState>(
builder: (context, state) {
final scheme = Theme.of(context).colorScheme;
final membre = (state is ProfileLoaded)
? state.membre
: (state is ProfileUpdated ? state.membre
@@ -189,39 +213,39 @@ class _ProfilePageState extends State<ProfilePage>
Color badgeColor;
if (isLoading) {
badgeText = 'CHARGEMENT...';
badgeColor = AppColors.textSecondaryLight;
badgeColor = ColorTokens.textSecondary;
} else if (hasError) {
badgeText = 'ERREUR CHARGEMENT';
badgeColor = AppColors.error;
badgeColor = ColorTokens.error;
} else if (membre != null) {
// Priorité au rôle s'il est disponible
if (membre.role != null && membre.role!.isNotEmpty) {
badgeText = membre.role!.replaceAll('_', ' ');
badgeColor = AppColors.primaryGreen;
badgeColor = ModuleColors.profil;
} else {
// Fallback sur le statut
switch (membre.statut) {
case StatutMembre.actif:
badgeText = membre.cotisationAJour ? 'MEMBRE ACTIF' : 'COTISATION EN RETARD';
badgeColor = membre.cotisationAJour ? AppColors.success : AppColors.warning;
badgeColor = membre.cotisationAJour ? ColorTokens.success : ColorTokens.warning;
break;
case StatutMembre.inactif:
badgeText = 'INACTIF';
badgeColor = AppColors.textSecondaryLight;
badgeColor = ColorTokens.textSecondary;
break;
case StatutMembre.suspendu:
badgeText = 'SUSPENDU';
badgeColor = AppColors.error;
badgeColor = ColorTokens.error;
break;
case StatutMembre.enAttente:
badgeText = 'EN ATTENTE';
badgeColor = AppColors.warning;
badgeColor = ColorTokens.warning;
break;
}
}
} else {
badgeText = 'CHARGEMENT...';
badgeColor = AppColors.textSecondaryLight;
badgeColor = ColorTokens.textSecondary;
}
// Ancienneté réelle
@@ -274,7 +298,7 @@ class _ProfilePageState extends State<ProfilePage>
),
Text(
_emailController.text.toLowerCase(),
style: AppTypography.subtitleSmall.copyWith(fontSize: 11),
style: AppTypography.subtitleSmall.copyWith(fontSize: 11, color: scheme.onSurfaceVariant),
),
const SizedBox(height: 8),
if (isLoading)
@@ -291,7 +315,7 @@ class _ProfilePageState extends State<ProfilePage>
child: Text(
'Réessayer',
style: AppTypography.subtitleSmall.copyWith(
color: AppColors.primaryGreen,
color: ModuleColors.profil,
fontSize: 10,
decoration: TextDecoration.underline,
),
@@ -344,10 +368,11 @@ class _ProfilePageState extends State<ProfilePage>
}
Widget _buildStatItem(String label, String value) {
final scheme = Theme.of(context).colorScheme;
return Column(
children: [
Text(value, style: AppTypography.headerSmall.copyWith(fontSize: 14)),
Text(label, style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
Text(label, style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold, color: scheme.onSurfaceVariant)),
],
);
}
@@ -388,35 +413,16 @@ class _ProfilePageState extends State<ProfilePage>
);
}
/// Barre d'onglets
Widget _buildTabBar() {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
Tab(text: 'PERSO'),
Tab(text: 'PRÉF'),
Tab(text: 'SÉCU'),
Tab(text: 'AVANCÉ'),
],
),
);
}
// _buildTabBar() supprimé : migré dans UFAppBar.bottom (pattern Adhésions)
/// Onglet informations personnelles
Widget _buildPersonalInfoTab() {
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.profil,
onRefresh: () async =>
context.read<ProfileBloc>().add(const LoadMe()),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -533,8 +539,9 @@ class _ProfilePageState extends State<ProfilePage>
const SizedBox(height: 80),
],
),
);
), // Column
), // SingleChildScrollView
); // RefreshIndicator
}
/// Section d'informations
@@ -551,7 +558,7 @@ class _ProfilePageState extends State<ProfilePage>
children: [
Row(
children: [
Icon(icon, color: AppColors.primaryGreen, size: 16),
Icon(icon, color: ModuleColors.profil, size: 16),
const SizedBox(width: 8),
Text(
title.toUpperCase(),
@@ -579,7 +586,7 @@ class _ProfilePageState extends State<ProfilePage>
int maxLines = 1,
String? hintText,
}) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return TextFormField(
controller: controller,
enabled: enabled,
@@ -587,35 +594,34 @@ class _ProfilePageState extends State<ProfilePage>
maxLines: maxLines,
style: AppTypography.bodyTextSmall.copyWith(
fontSize: 12,
color: isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight,
),
decoration: InputDecoration(
labelText: label.toUpperCase(),
labelStyle: AppTypography.subtitleSmall.copyWith(
fontSize: 9,
fontWeight: FontWeight.bold,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
color: scheme.onSurfaceVariant,
),
hintText: hintText,
prefixIcon: Icon(icon, color: enabled ? AppColors.primaryGreen : AppColors.textSecondaryLight, size: 16),
prefixIcon: Icon(icon, color: enabled ? ModuleColors.profil : ColorTokens.textSecondary, size: 16),
filled: true,
fillColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
fillColor: scheme.surface,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder),
borderSide: BorderSide(color: scheme.outlineVariant),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder),
borderSide: BorderSide(color: scheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: const BorderSide(color: AppColors.primaryGreen, width: 1),
borderSide: BorderSide(color: ModuleColors.profil, width: 1),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
borderSide: BorderSide(color: scheme.outlineVariant, width: 0.5),
),
),
validator: (value) {
@@ -631,15 +637,15 @@ class _ProfilePageState extends State<ProfilePage>
/// Boutons d'action
Widget _buildActionButtons() {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : Colors.white,
color: scheme.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
color: AppColors.shadow,
blurRadius: 10,
offset: const Offset(0, 2),
),
@@ -652,8 +658,8 @@ class _ProfilePageState extends State<ProfilePage>
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _cancelEditing,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[100],
foregroundColor: Colors.grey[700],
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
foregroundColor: AppColors.textSecondary,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
),
@@ -666,7 +672,7 @@ class _ProfilePageState extends State<ProfilePage>
child: ElevatedButton.icon(
onPressed: _isLoading ? null : _saveProfile,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
backgroundColor: ModuleColors.profil,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -689,7 +695,7 @@ class _ProfilePageState extends State<ProfilePage>
child: ElevatedButton.icon(
onPressed: _startEditing,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
backgroundColor: ModuleColors.profil,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -720,9 +726,7 @@ class _ProfilePageState extends State<ProfilePage>
[
Builder(
builder: (ctx) {
final isDark = Theme.of(ctx).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
final scheme = Theme.of(ctx).colorScheme;
return InkWell(
onTap: () async {
await Navigator.of(context).push(
@@ -738,18 +742,18 @@ class _ProfilePageState extends State<ProfilePage>
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Icon(Icons.language, color: textSecondary, size: 22),
Icon(Icons.language, color: scheme.onSurfaceVariant, size: 22),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Langue', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
Text('Actuellement : $_selectedLanguage', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
Text('Langue', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
Text('Actuellement : $_selectedLanguage', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Icon(Icons.chevron_right, color: textSecondary, size: 20),
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
],
),
),
@@ -823,9 +827,7 @@ class _ProfilePageState extends State<ProfilePage>
[
Builder(
builder: (ctx) {
final isDark = Theme.of(ctx).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
final scheme = Theme.of(ctx).colorScheme;
return InkWell(
onTap: () {
Navigator.of(context).push(
@@ -837,18 +839,18 @@ class _ProfilePageState extends State<ProfilePage>
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Icon(Icons.privacy_tip, color: textSecondary, size: 22),
Icon(Icons.privacy_tip, color: scheme.onSurfaceVariant, size: 22),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Gérer la confidentialité', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
Text('Visibilité, partage de données, suppression de compte', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
Text('Gérer la confidentialité', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
Text('Visibilité, partage de données, suppression de compte', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Icon(Icons.chevron_right, color: textSecondary, size: 20),
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
],
),
),
@@ -950,21 +952,21 @@ class _ProfilePageState extends State<ProfilePage>
'Télécharger mes données',
'Exporter toutes vos données personnelles',
Icons.download,
AppColors.primaryGreen,
ModuleColors.profil,
() => _exportUserData(),
),
_buildActionItem(
'Déconnecter tous les appareils',
'Fermer toutes les sessions actives',
Icons.logout,
AppColors.warning,
ColorTokens.warning,
() => _logoutAllDevices(),
),
_buildActionItem(
'Supprimer mon compte',
'Action irréversible - toutes les données seront perdues',
Icons.delete_forever,
Colors.red,
ColorTokens.error,
() => _showDeleteAccountDialog(),
),
],
@@ -1019,14 +1021,22 @@ class _ProfilePageState extends State<ProfilePage>
_buildSwitchPreference(
'Mode développeur',
'Afficher les options de débogage',
false,
(value) => _showSuccessSnackBar('Mode développeur ${value ? 'activé' : 'désactivé'}'),
_devMode,
(value) async {
setState(() => _devMode = value);
await _savePreference(_keyDevMode, value);
_showSuccessSnackBar('Mode développeur ${value ? 'activé' : 'désactivé'}');
},
),
_buildSwitchPreference(
'Logs détaillés',
'Enregistrer plus d\'informations de débogage',
false,
(value) => _showSuccessSnackBar('Logs détaillés ${value ? 'activés' : 'désactivés'}'),
'Logs réseau et erreurs — actif au prochain démarrage',
_detailedLogs,
(value) async {
setState(() => _detailedLogs = value);
await _savePreference(_keyDetailedLogs, value);
_showSuccessSnackBar('Logs détaillés ${value ? 'activés' : 'désactivés'} (prochain démarrage)');
},
),
],
),
@@ -1041,6 +1051,9 @@ class _ProfilePageState extends State<ProfilePage>
[
_buildInfoItem('Version de l\'app', _appVersion),
_buildInfoItem('Plateforme', _platformInfo),
_buildInfoItem('Système', _osVersion),
_buildInfoItem('Environnement', _envInfo),
if (_devMode) _buildInfoItem('API URL', AppConfig.apiBaseUrl),
],
),
@@ -1054,9 +1067,7 @@ class _ProfilePageState extends State<ProfilePage>
[
Builder(
builder: (ctx) {
final isDark = Theme.of(ctx).brightness == Brightness.dark;
final textPrimary = isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
final scheme = Theme.of(ctx).colorScheme;
return InkWell(
onTap: () {
Navigator.of(context).push(
@@ -1068,18 +1079,18 @@ class _ProfilePageState extends State<ProfilePage>
padding: const EdgeInsets.symmetric(vertical: 12),
child: Row(
children: [
Icon(Icons.edit_note, color: textSecondary, size: 22),
Icon(Icons.edit_note, color: scheme.onSurfaceVariant, size: 22),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Envoyer des commentaires', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600, color: textPrimary)),
Text('Suggestions, bugs ou idées d\'amélioration', style: AppTypography.subtitleSmall.copyWith(color: textSecondary)),
Text('Envoyer des commentaires', style: AppTypography.bodyTextSmall.copyWith(fontWeight: FontWeight.w600)),
Text('Suggestions, bugs ou idées d\'amélioration', style: AppTypography.subtitleSmall.copyWith(color: scheme.onSurfaceVariant)),
],
),
),
Icon(Icons.chevron_right, color: textSecondary, size: 20),
Icon(Icons.chevron_right, color: scheme.onSurfaceVariant, size: 20),
],
),
),
@@ -1113,7 +1124,7 @@ class _ProfilePageState extends State<ProfilePage>
children: [
Row(
children: [
Icon(icon, color: AppColors.primaryGreen, size: 16),
Icon(icon, color: ModuleColors.profil, size: 16),
const SizedBox(width: 8),
Text(
title.toUpperCase(),
@@ -1159,7 +1170,7 @@ class _ProfilePageState extends State<ProfilePage>
List<String> options,
Function(String?) onChanged,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -1171,21 +1182,31 @@ class _ProfilePageState extends State<ProfilePage>
Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
color: scheme.surface,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
border: Border.all(color: scheme.outlineVariant, width: 0.5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
isExpanded: true,
onChanged: onChanged,
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primaryGreen, size: 18),
style: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
dropdownColor: scheme.surface,
icon: Icon(Icons.arrow_drop_down, color: ModuleColors.profil, size: 18),
style: AppTypography.bodyTextSmall.copyWith(
fontSize: 12,
color: scheme.onSurface,
),
items: options.map((option) {
return DropdownMenuItem<String>(
value: option,
child: Text(option),
child: Text(
option,
style: AppTypography.bodyTextSmall.copyWith(
fontSize: 12,
color: scheme.onSurface,
),
),
);
}).toList(),
),
@@ -1202,7 +1223,7 @@ class _ProfilePageState extends State<ProfilePage>
bool value,
Function(bool) onChanged,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return Row(
children: [
Expanded(
@@ -1217,7 +1238,7 @@ class _ProfilePageState extends State<ProfilePage>
subtitle,
style: AppTypography.bodyTextSmall.copyWith(
fontSize: 10,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
color: scheme.onSurfaceVariant,
),
),
],
@@ -1228,7 +1249,7 @@ class _ProfilePageState extends State<ProfilePage>
child: Switch(
value: value,
onChanged: onChanged,
activeColor: AppColors.primaryGreen,
activeColor: ModuleColors.profil,
),
),
],
@@ -1242,21 +1263,20 @@ class _ProfilePageState extends State<ProfilePage>
IconData icon,
VoidCallback onTap,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
final scheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
color: scheme.surface,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
border: Border.all(color: scheme.outlineVariant, width: 0.5),
),
child: Row(
children: [
Icon(icon, color: AppColors.primaryGreen, size: 16),
Icon(icon, color: ModuleColors.profil, size: 16),
const SizedBox(width: 12),
Expanded(
child: Column(
@@ -1268,12 +1288,12 @@ class _ProfilePageState extends State<ProfilePage>
),
Text(
subtitle,
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: textSecondary),
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
),
],
),
),
Icon(Icons.arrow_forward_ios, color: textSecondary, size: 12),
Icon(Icons.arrow_forward_ios, color: scheme.onSurfaceVariant, size: 12),
],
),
),
@@ -1287,19 +1307,18 @@ class _ProfilePageState extends State<ProfilePage>
IconData icon,
bool isCurrentDevice,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final textSecondary = isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight;
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isCurrentDevice
? AppColors.primaryGreen.withOpacity(0.05)
: (isDark ? AppColors.darkSurface : AppColors.lightSurface),
? ModuleColors.profil.withOpacity(0.05)
: scheme.surface,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isCurrentDevice
? AppColors.primaryGreen.withOpacity(0.3)
: (isDark ? AppColors.darkBorder : AppColors.lightBorder),
? ModuleColors.profil.withOpacity(0.3)
: scheme.outlineVariant,
width: 0.5,
),
),
@@ -1307,7 +1326,7 @@ class _ProfilePageState extends State<ProfilePage>
children: [
Icon(
icon,
color: isCurrentDevice ? AppColors.primaryGreen : textSecondary,
color: isCurrentDevice ? ModuleColors.profil : scheme.onSurfaceVariant,
size: 16,
),
const SizedBox(width: 12),
@@ -1323,13 +1342,13 @@ class _ProfilePageState extends State<ProfilePage>
),
if (isCurrentDevice) ...[
const SizedBox(width: 8),
const InfoBadge(text: 'ACTUEL', backgroundColor: AppColors.primaryGreen),
InfoBadge(text: 'ACTUEL', backgroundColor: ModuleColors.profil),
],
],
),
Text(
subtitle,
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: textSecondary),
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
),
],
),
@@ -1347,7 +1366,7 @@ class _ProfilePageState extends State<ProfilePage>
Color color,
VoidCallback onTap,
) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(4),
@@ -1374,7 +1393,7 @@ class _ProfilePageState extends State<ProfilePage>
subtitle,
style: AppTypography.bodyTextSmall.copyWith(
fontSize: 10,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
color: scheme.onSurfaceVariant,
),
),
],
@@ -1389,20 +1408,20 @@ class _ProfilePageState extends State<ProfilePage>
/// Élément de stockage
Widget _buildStorageItem(String title, String size, VoidCallback onTap) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(4),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
color: scheme.surface,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
border: Border.all(color: scheme.outlineVariant, width: 0.5),
),
child: Row(
children: [
const Icon(Icons.folder_outlined, color: AppColors.primaryGreen, size: 16),
Icon(Icons.folder_outlined, color: ModuleColors.profil, size: 16),
const SizedBox(width: 12),
Expanded(
child: Text(
@@ -1412,10 +1431,10 @@ class _ProfilePageState extends State<ProfilePage>
),
Text(
size,
style: AppTypography.subtitleSmall.copyWith(fontSize: 10),
style: AppTypography.subtitleSmall.copyWith(fontSize: 10, color: scheme.onSurfaceVariant),
),
const SizedBox(width: 8),
const Icon(Icons.clear, color: AppColors.error, size: 14),
Icon(Icons.clear, color: ColorTokens.error, size: 14),
],
),
),
@@ -1424,20 +1443,20 @@ class _ProfilePageState extends State<ProfilePage>
/// Élément d'information
Widget _buildInfoItem(String title, String value) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final scheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
color: scheme.surface,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: isDark ? AppColors.darkBorder : AppColors.lightBorder, width: 0.5),
border: Border.all(color: scheme.outlineVariant, width: 0.5),
),
child: Row(
children: [
Expanded(
child: Text(
title.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold),
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold, color: scheme.onSurfaceVariant),
),
),
Text(
@@ -1565,7 +1584,7 @@ class _ProfilePageState extends State<ProfilePage>
);
}
/// Charger les préférences notifications depuis SharedPreferences
/// Charger les préférences depuis SharedPreferences
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
if (mounted) {
@@ -1573,6 +1592,8 @@ class _ProfilePageState extends State<ProfilePage>
_notifPush = prefs.getBool(_keyNotifPush) ?? true;
_notifEmail = prefs.getBool(_keyNotifEmail) ?? false;
_notifSon = prefs.getBool(_keyNotifSon) ?? true;
_devMode = prefs.getBool(_keyDevMode) ?? AppConfig.enableDebugMode;
_detailedLogs = prefs.getBool(_keyDetailedLogs) ?? AppConfig.enableLogging;
});
}
}
@@ -1588,7 +1609,7 @@ class _ProfilePageState extends State<ProfilePage>
final info = await PackageInfo.fromPlatform();
if (mounted) {
setState(() {
_appVersion = '${info.version} (Build ${info.buildNumber})';
_appVersion = '${info.version} (build ${info.buildNumber})';
final os = kIsWeb
? 'Web'
: Platform.isAndroid
@@ -1597,6 +1618,10 @@ class _ProfilePageState extends State<ProfilePage>
? 'iOS'
: Platform.operatingSystem;
_platformInfo = '$os · ${info.appName}';
_osVersion = kIsWeb
? 'Navigateur web'
: Platform.operatingSystemVersion;
_envInfo = AppConfig.environment.name.toUpperCase();
});
}
}
@@ -1684,7 +1709,7 @@ class _ProfilePageState extends State<ProfilePage>
confirmPassCtrl.dispose();
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryGreen,
backgroundColor: ModuleColors.profil,
foregroundColor: Colors.white,
),
child: const Text('Modifier'),
@@ -1782,7 +1807,7 @@ class _ProfilePageState extends State<ProfilePage>
_showFinalDeleteConfirmation();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
backgroundColor: ColorTokens.error,
foregroundColor: Colors.white,
),
child: const Text('Continuer'),
@@ -1836,7 +1861,7 @@ class _ProfilePageState extends State<ProfilePage>
confirmCtrl.dispose();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
backgroundColor: ColorTokens.error,
foregroundColor: Colors.white,
),
child: const Text('SUPPRIMER DÉFINITIVEMENT'),
@@ -1953,7 +1978,7 @@ class _ProfilePageState extends State<ProfilePage>
message.toUpperCase(),
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
),
backgroundColor: AppColors.success,
backgroundColor: ColorTokens.success,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
@@ -1967,7 +1992,7 @@ class _ProfilePageState extends State<ProfilePage>
message.toUpperCase(),
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
),
backgroundColor: AppColors.error,
backgroundColor: ColorTokens.error,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),

View File

@@ -58,7 +58,7 @@ class _ReportsPageState extends State<ReportsPage>
}
if (state is ReportsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message), backgroundColor: Colors.orange),
SnackBar(content: Text(state.message), backgroundColor: AppColors.warning),
);
}
if (state is ReportScheduled) {
@@ -74,18 +74,49 @@ class _ReportsPageState extends State<ReportsPage>
},
builder: (context, state) {
return Scaffold(
backgroundColor: AppColors.lightBackground,
body: Column(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Rapports & Analytics',
moduleGradient: ModuleColors.rapportsGradient,
actions: [
IconButton(
onPressed: () => _showExportDialog(),
icon: const Icon(Icons.file_download_outlined, size: 20),
tooltip: 'Exporter',
),
],
bottom: TabBar(
controller: _tabController,
isScrollable: true,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
Tab(child: Text('GLOBAL')),
Tab(child: Text('MEMBRES')),
Tab(child: Text('ORGS')),
Tab(child: Text('EVENTS')),
],
),
),
body: SafeArea(
top: false,
child: Column(
children: [
_buildHeader(),
_buildTabBar(),
if (state is ReportsLoading)
const LinearProgressIndicator(
minHeight: 2,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primaryGreen),
valueColor: AlwaysStoppedAnimation<Color>(AppColors.primary),
),
Expanded(
child: RefreshIndicator(
color: ModuleColors.rapports,
onRefresh: () async => context
.read<ReportsBloc>()
.add(const LoadDashboardReports()),
child: TabBarView(
controller: _tabController,
children: [
@@ -96,162 +127,22 @@ class _ReportsPageState extends State<ReportsPage>
],
),
),
),
],
),
),
);
},
);
}
Widget _buildHeader() {
return Container(
width: double.infinity,
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 12,
bottom: 16,
left: 12,
right: 12,
),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.primaryGreen,
AppColors.brandGreen,
],
),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(32),
bottomRight: Radius.circular(32),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow Analytics'.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 4),
const Text(
'Rapports & Insights',
style: AppTypography.headerSmall,
),
],
),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
onPressed: () => _showExportDialog(),
icon: const Icon(Icons.file_download_outlined, color: Colors.white),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildHeaderStat(
'Membres',
_statsMembres['total']?.toString() ?? '...',
Icons.people_outline,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildHeaderStat(
'Organisations',
_statsMembres['totalOrganisations']?.toString() ?? '...',
Icons.business_outlined,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildHeaderStat(
'Événements',
_statsEvenements['total']?.toString() ?? '...',
Icons.event_outlined,
),
),
],
),
],
),
);
}
Widget _buildHeaderStat(String label, String value, IconData icon) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white.withOpacity(0.2)),
),
child: Column(
children: [
Icon(icon, color: Colors.white, size: 18),
const SizedBox(height: 8),
Text(
value,
style: AppTypography.headerSmall.copyWith(fontSize: 18),
),
Text(
label.toUpperCase(),
style: AppTypography.subtitleSmall.copyWith(
color: Colors.white.withOpacity(0.7),
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Widget _buildTabBar() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppColors.lightBackground,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
indicatorSize: TabBarIndicatorSize.label,
dividerColor: Colors.transparent,
labelStyle: AppTypography.badgeText.copyWith(fontWeight: FontWeight.bold),
tabs: const [
Tab(text: 'GLOBAL'),
Tab(text: 'MEMBRES'),
Tab(text: 'ORGS'),
Tab(text: 'EVENTS'),
],
),
);
}
// _buildHeader(), _buildHeaderStat() et _buildTabBar() supprimés :
// titre + action Export migrés dans UFAppBar, TabBar dans UFAppBar.bottom.
// Les KPIs sont toujours disponibles dans _buildKPICards() (onglet Global).
Widget _buildOverviewTab() {
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
children: [
_buildKPICards(),
@@ -282,7 +173,7 @@ class _ReportsPageState extends State<ReportsPage>
const SizedBox(height: 8),
Row(
children: [
Expanded(child: _buildKPICard('Cotisations', totalCotisations, Icons.payments_outlined, AppColors.brandGreen)),
Expanded(child: _buildKPICard('Cotisations', totalCotisations, Icons.payments_outlined, AppColors.primaryDark)),
const SizedBox(width: 8),
Expanded(child: _buildKPICard('Événements', totalEvenements, Icons.event_available_outlined, AppColors.warning)),
],
@@ -332,7 +223,7 @@ class _ReportsPageState extends State<ReportsPage>
children: [
Row(
children: [
const Icon(Icons.analytics_outlined, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.analytics_outlined, color: AppColors.primary, size: 20),
const SizedBox(width: 8),
Text(
'Évolution de l\'Activité'.toUpperCase(),
@@ -341,26 +232,29 @@ class _ReportsPageState extends State<ReportsPage>
],
),
const SizedBox(height: 10),
Container(
Builder(builder: (context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
height: 180,
decoration: BoxDecoration(
color: AppColors.lightBorder.withOpacity(0.1),
color: (isDark ? AppColors.borderDark : AppColors.border).withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: const Center(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.auto_graph_outlined, color: AppColors.textSecondaryLight, size: 40),
SizedBox(height: 12),
Text(
'Visualisation graphique en préparation',
style: AppTypography.subtitleSmall,
),
Icon(Icons.auto_graph_outlined,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondary,
size: 40),
const SizedBox(height: 12),
Text('Visualisation graphique en préparation',
style: AppTypography.subtitleSmall),
],
),
),
),
);
}),
],
),
);
@@ -400,19 +294,19 @@ class _ReportsPageState extends State<ReportsPage>
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColors.lightBorder.withOpacity(0.05),
color: AppColors.border.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
border: Border.all(color: AppColors.border.withOpacity(0.1)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.1),
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: AppColors.primaryGreen, size: 20),
child: Icon(icon, color: AppColors.primary, size: 20),
),
const SizedBox(width: 16),
Expanded(
@@ -425,7 +319,11 @@ class _ReportsPageState extends State<ReportsPage>
],
),
),
const Icon(Icons.chevron_right_outlined, color: AppColors.textSecondaryLight, size: 20),
Icon(Icons.chevron_right_outlined,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
size: 20),
],
),
),
@@ -436,6 +334,7 @@ class _ReportsPageState extends State<ReportsPage>
/// Onglet membres
Widget _buildMembersTab() {
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -473,9 +372,9 @@ class _ReportsPageState extends State<ReportsPage>
Row(
children: [
Expanded(child: _buildStatItem('Total', total)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('Nouveaux', nouveaux)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('Actifs %', actifs)),
],
),
@@ -512,6 +411,7 @@ class _ReportsPageState extends State<ReportsPage>
/// Onglet organisations
Widget _buildOrganizationsTab() {
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -537,7 +437,7 @@ class _ReportsPageState extends State<ReportsPage>
children: [
Row(
children: [
const Icon(Icons.business_center_outlined, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.business_center_outlined, color: AppColors.primary, size: 20),
const SizedBox(width: 12),
Text(
'Indicateurs Organisations'.toUpperCase(),
@@ -549,9 +449,9 @@ class _ReportsPageState extends State<ReportsPage>
Row(
children: [
Expanded(child: _buildStatItem('Total', total)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('Actives', actives)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('Membres moy.', moy)),
],
),
@@ -568,7 +468,7 @@ class _ReportsPageState extends State<ReportsPage>
children: [
Row(
children: [
const Icon(Icons.folder_shared_outlined, color: AppColors.primaryGreen, size: 20),
const Icon(Icons.folder_shared_outlined, color: AppColors.primary, size: 20),
const SizedBox(width: 12),
Text(
'Rapports Structures'.toUpperCase(),
@@ -588,6 +488,7 @@ class _ReportsPageState extends State<ReportsPage>
/// Onglet événements
Widget _buildEventsTab() {
return SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
children: [
@@ -625,9 +526,9 @@ class _ReportsPageState extends State<ReportsPage>
Row(
children: [
Expanded(child: _buildStatItem('Total', total)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('À Venir', venir)),
Container(width: 1, height: 30, color: AppColors.lightBorder.withOpacity(0.2)),
Container(width: 1, height: 30, color: AppColors.border.withOpacity(0.2)),
Expanded(child: _buildStatItem('Part. moyenne', participation)),
],
),
@@ -667,7 +568,7 @@ class _ReportsPageState extends State<ReportsPage>
children: [
Text(
value,
style: AppTypography.headerSmall.copyWith(color: AppColors.primaryGreen, fontWeight: FontWeight.bold),
style: AppTypography.headerSmall.copyWith(color: AppColors.primary, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(
@@ -688,19 +589,19 @@ class _ReportsPageState extends State<ReportsPage>
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.lightBorder.withOpacity(0.05),
color: AppColors.border.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.lightBorder.withOpacity(0.1)),
border: Border.all(color: AppColors.border.withOpacity(0.1)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primaryGreen.withOpacity(0.1),
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: AppColors.primaryGreen, size: 20),
child: Icon(icon, color: AppColors.primary, size: 20),
),
const SizedBox(width: 16),
Expanded(
@@ -713,7 +614,11 @@ class _ReportsPageState extends State<ReportsPage>
],
),
),
const Icon(Icons.file_download_outlined, color: AppColors.textSecondaryLight, size: 20),
Icon(Icons.file_download_outlined,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary,
size: 20),
],
),
),
@@ -752,7 +657,7 @@ class _ReportsPageState extends State<ReportsPage>
Navigator.of(context).pop();
context.read<ReportsBloc>().add(GenerateReportRequested('export', format: _selectedFormat));
},
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryGreen, foregroundColor: Colors.white),
style: ElevatedButton.styleFrom(backgroundColor: AppColors.primary, foregroundColor: AppColors.onPrimary),
child: const Text('Exporter'),
),
],

View File

@@ -28,18 +28,19 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: const UFAppBar(
title: 'DÉTAIL DEMANDE',
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimaryLight,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'Détail Demande',
moduleGradient: ModuleColors.solidariteGradient,
),
body: BlocConsumer<SolidarityBloc, SolidarityState>(
body: SafeArea(
top: false,
child: BlocConsumer<SolidarityBloc, SolidarityState>(
listenWhen: (prev, curr) => prev.status != curr.status,
listener: (context, state) {
if (state.status == SolidarityStatus.error && state.message != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.message!), backgroundColor: Colors.red),
SnackBar(content: Text(state.message!), backgroundColor: AppColors.error),
);
}
},
@@ -55,7 +56,7 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: Colors.grey),
const Icon(Icons.error_outline, size: 64, color: AppColors.textTertiary),
const SizedBox(height: 16),
Text(
'Demande introuvable',
@@ -65,7 +66,12 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
),
);
}
return SingleChildScrollView(
return RefreshIndicator(
color: ModuleColors.solidarite,
onRefresh: () async =>
context.read<SolidarityBloc>().add(LoadDemandeAideById(widget.demandeId)),
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -97,10 +103,12 @@ class _DemandeAideDetailPageState extends State<DemandeAideDetailPage> {
_ActionsSection(demande: d, isGestionnaire: _isGestionnaire()),
],
),
);
), // SingleChildScrollView
); // RefreshIndicator
},
),
);
), // BlocConsumer
), // SafeArea
); // Scaffold
}
bool _isGestionnaire() {
@@ -136,7 +144,7 @@ class _InfoCard extends StatelessWidget {
style: AppTypography.subtitleSmall.copyWith(
fontWeight: FontWeight.bold,
fontSize: 9,
color: AppColors.textSecondaryLight,
color: AppColors.textSecondary,
),
),
const SizedBox(height: 2),

View File

@@ -76,17 +76,16 @@ class _DemandesAidePageState extends State<DemandesAidePage>
}
},
child: Scaffold(
backgroundColor: AppColors.background,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: UFAppBar(
title: 'SOLIDARITÉ',
backgroundColor: AppColors.surface,
foregroundColor: AppColors.textPrimaryLight,
title: 'Solidarité',
moduleGradient: ModuleColors.solidariteGradient,
bottom: TabBar(
controller: _tabController,
onTap: _loadTab,
labelColor: AppColors.primaryGreen,
unselectedLabelColor: AppColors.textSecondaryLight,
indicatorColor: AppColors.primaryGreen,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
tabs: const [
@@ -125,7 +124,13 @@ class _DemandesAidePageState extends State<DemandesAidePage>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.volunteer_activism_outlined, size: 32, color: AppColors.lightBorder),
Icon(
Icons.volunteer_activism_outlined,
size: 32,
color: Theme.of(context).brightness == Brightness.dark
? AppColors.borderDark
: AppColors.border,
),
const SizedBox(height: 12),
Text('Aucune demande', style: AppTypography.subtitleSmall),
],
@@ -204,7 +209,7 @@ class _DemandeCard extends StatelessWidget {
],
),
),
_buildStatutBadge(demande.statut),
_buildStatutBadge(context, demande.statut),
],
),
const SizedBox(height: 12),
@@ -217,7 +222,7 @@ class _DemandeCard extends StatelessWidget {
Text('MONTANT DEMANDÉ', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
Text(
currencyFormat.format(demande.montantDemande ?? 0),
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primaryGreen),
style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primary),
),
],
),
@@ -235,7 +240,7 @@ class _DemandeCard extends StatelessWidget {
);
}
Widget _buildStatutBadge(String? statut) {
Widget _buildStatutBadge(BuildContext context, String? statut) {
Color color;
switch (statut) {
case 'APPROUVEE':
@@ -246,10 +251,12 @@ class _DemandeCard extends StatelessWidget {
break;
case 'EN_ATTENTE':
case 'SOUMISE':
color = AppColors.brandGreenLight;
color = AppColors.primaryLight;
break;
default:
color = AppColors.textSecondaryLight;
color = Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
: AppColors.textSecondary;
}
return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color);
}