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

@@ -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';
@@ -26,7 +28,7 @@ import '../widgets/kyc_status_widget.dart';
import '../bloc/profile_bloc.dart';
/// Page Mon Profil - UnionFlow Mobile
///
///
/// Page complète de gestion du profil utilisateur avec informations personnelles,
/// préférences, sécurité, et paramètres avancés.
class ProfilePage extends StatefulWidget {
@@ -40,7 +42,7 @@ class _ProfilePageState extends State<ProfilePage>
with TickerProviderStateMixin {
late TabController _tabController;
final _formKey = GlobalKey<FormState>();
// Contrôleurs pour les champs de texte
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
@@ -50,7 +52,7 @@ class _ProfilePageState extends State<ProfilePage>
final _cityController = TextEditingController();
final _postalCodeController = TextEditingController();
final _bioController = TextEditingController();
// État du profil
bool _isEditing = false;
bool _isLoading = false;
@@ -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,37 +145,51 @@ 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),
children: [
_buildHeader(),
const SizedBox(height: 8),
_buildTabBar(),
SizedBox(
height: 600, // Ajuster selon contenu ou utiliser NestedScrollView
child: TabBarView(
controller: _tabController,
children: [
_buildPersonalInfoTab(),
_buildPreferencesTab(),
_buildSecurityTab(),
_buildAdvancedTab(),
],
body: SafeArea(
top: false,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: _buildHeader(),
),
),
],
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildPersonalInfoTab(),
_buildPreferencesTab(),
_buildSecurityTab(),
_buildAdvancedTab(),
],
),
),
],
),
),
),
);
@@ -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)),
),