feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../../../shared/design_system/unionflow_design_system.dart';
|
||||
import '../../../../shared/widgets/core_card.dart';
|
||||
import '../../../../shared/widgets/info_badge.dart';
|
||||
import '../../../../shared/widgets/mini_avatar.dart';
|
||||
import '../../../../core/l10n/locale_provider.dart';
|
||||
import '../../../authentication/presentation/bloc/auth_bloc.dart';
|
||||
import '../../../settings/presentation/pages/language_settings_page.dart';
|
||||
@@ -96,176 +100,78 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xFFF8F9FA),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header harmonisé
|
||||
_buildHeader(),
|
||||
|
||||
// Onglets
|
||||
_buildTabBar(),
|
||||
|
||||
// Contenu des onglets
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildPersonalInfoTab(),
|
||||
_buildPreferencesTab(),
|
||||
_buildSecurityTab(),
|
||||
_buildAdvancedTab(),
|
||||
],
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: UFAppBar(
|
||||
title: 'MON PROFIL',
|
||||
backgroundColor: AppColors.surface,
|
||||
foregroundColor: AppColors.textPrimaryLight,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(_isEditing ? Icons.save_outlined : Icons.edit_outlined, size: 20),
|
||||
onPressed: () => _isEditing ? _saveProfile() : _startEditing(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
_buildTabBar(),
|
||||
SizedBox(
|
||||
height: 600, // Ajuster selon contenu ou utiliser NestedScrollView
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildPersonalInfoTab(),
|
||||
_buildPreferencesTab(),
|
||||
_buildSecurityTab(),
|
||||
_buildAdvancedTab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Header harmonisé avec photo de profil
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF6C5CE7).withOpacity(0.3),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
return CoreCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Photo de profil
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipOval(
|
||||
child: _profileImage != null
|
||||
? Image.file(
|
||||
_profileImage!,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Container(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
child: const Icon(
|
||||
Icons.person,
|
||||
size: 40,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: InkWell(
|
||||
onTap: _pickProfileImage,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.camera_alt,
|
||||
size: 16,
|
||||
color: Color(0xFF6C5CE7),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const MiniAvatar(size: 64, fallbackText: '👤'),
|
||||
const SizedBox(width: 16),
|
||||
|
||||
// Informations utilisateur
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${_firstNameController.text} ${_lastNameController.text}',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
'${_firstNameController.text} ${_lastNameController.text}'.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_emailController.text.isNotEmpty
|
||||
? _emailController.text
|
||||
: 'utilisateur@unionflow.com',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
_emailController.text.toLowerCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 11),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text(
|
||||
'Membre actif',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const InfoBadge(text: 'MEMBRE ACTIF', backgroundColor: AppColors.success),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Statistiques rapides
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard('Depuis', '2 ans', Icons.calendar_today),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatCard('Événements', '24', Icons.event),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildStatCard('Organisations', '3', Icons.business),
|
||||
),
|
||||
_buildStatItem('DEPUIS', '2 ANS'),
|
||||
_buildStatItem('EVENTS', '24'),
|
||||
_buildStatItem('ORGS', '3'),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -273,6 +179,15 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatItem(String label, String value) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(value, style: AppTypography.headerSmall.copyWith(fontSize: 14)),
|
||||
Text(label, style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Carte de statistique
|
||||
Widget _buildStatCard(String label, String value, IconData icon) {
|
||||
return Container(
|
||||
@@ -312,50 +227,23 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
/// Barre d'onglets
|
||||
Widget _buildTabBar() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.lightBorder, width: 0.5),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: const Color(0xFF6C5CE7),
|
||||
unselectedLabelColor: Colors.grey[600],
|
||||
indicatorColor: const Color(0xFF6C5CE7),
|
||||
indicatorWeight: 3,
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 11,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 11,
|
||||
),
|
||||
labelColor: AppColors.primaryGreen,
|
||||
unselectedLabelColor: AppColors.textSecondaryLight,
|
||||
indicatorColor: AppColors.primaryGreen,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||
tabs: const [
|
||||
Tab(
|
||||
icon: Icon(Icons.person, size: 18),
|
||||
text: 'Personnel',
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(Icons.settings, size: 18),
|
||||
text: 'Préférences',
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(Icons.security, size: 18),
|
||||
text: 'Sécurité',
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(Icons.tune, size: 18),
|
||||
text: 'Avancé',
|
||||
),
|
||||
Tab(text: 'PERSO'),
|
||||
Tab(text: 'PRÉF'),
|
||||
Tab(text: 'SÉCU'),
|
||||
Tab(text: 'AVANCÉ'),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -491,51 +379,18 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
IconData icon,
|
||||
List<Widget> children,
|
||||
) {
|
||||
return Container(
|
||||
return CoreCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -564,30 +419,26 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
enabled: enabled,
|
||||
keyboardType: keyboardType,
|
||||
maxLines: maxLines,
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 12),
|
||||
decoration: InputDecoration(
|
||||
labelText: label,
|
||||
labelText: label.toUpperCase(),
|
||||
labelStyle: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold),
|
||||
hintText: hintText,
|
||||
prefixIcon: Icon(icon, color: enabled ? const Color(0xFF6C5CE7) : Colors.grey),
|
||||
prefixIcon: Icon(icon, color: enabled ? AppColors.primaryGreen : Colors.grey, size: 16),
|
||||
filled: true,
|
||||
fillColor: enabled ? Colors.grey[50] : Colors.grey[100],
|
||||
fillColor: AppColors.surface,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey[300]!),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: AppColors.lightBorder),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey[300]!),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: AppColors.lightBorder),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFF6C5CE7), width: 2),
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.grey[300]!),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
color: enabled ? Colors.grey[700] : Colors.grey[500],
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
borderSide: const BorderSide(color: AppColors.primaryGreen, width: 1),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
@@ -1033,47 +884,18 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
IconData icon,
|
||||
List<Widget> children,
|
||||
) {
|
||||
return Container(
|
||||
return CoreCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: Colors.grey[600], size: 20),
|
||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[800],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1119,34 +941,24 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.grey[300]!),
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: AppColors.lightBorder, 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),
|
||||
items: options.map((option) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: option,
|
||||
@@ -1174,27 +986,23 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: const Color(0xFF6C5CE7),
|
||||
Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: AppColors.primaryGreen,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -1209,40 +1017,34 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: AppColors.lightBorder, width: 0.5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: const Color(0xFF6C5CE7), size: 20),
|
||||
Icon(icon, color: AppColors.primaryGreen, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 16),
|
||||
const Icon(Icons.arrow_forward_ios, color: AppColors.textSecondaryLight, size: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1259,16 +1061,19 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentDevice ? const Color(0xFF6C5CE7).withOpacity(0.1) : Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isCurrentDevice ? Border.all(color: const Color(0xFF6C5CE7).withOpacity(0.3)) : null,
|
||||
color: isCurrentDevice ? AppColors.primaryGreen.withOpacity(0.05) : AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: isCurrentDevice ? AppColors.primaryGreen.withOpacity(0.3) : AppColors.lightBorder,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isCurrentDevice ? const Color(0xFF6C5CE7) : Colors.grey[600],
|
||||
size: 20,
|
||||
color: isCurrentDevice ? AppColors.primaryGreen : AppColors.textSecondaryLight,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
@@ -1278,39 +1083,18 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (isCurrentDevice) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF6C5CE7),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'Actuel',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const InfoBadge(text: 'ACTUEL', backgroundColor: AppColors.primaryGreen),
|
||||
],
|
||||
],
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1318,8 +1102,10 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
if (!isCurrentDevice)
|
||||
IconButton(
|
||||
onPressed: () => _terminateSession(title),
|
||||
icon: const Icon(Icons.close, color: Colors.red, size: 18),
|
||||
icon: const Icon(Icons.close, color: AppColors.error, size: 16),
|
||||
tooltip: 'Terminer la session',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1336,41 +1122,34 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withOpacity(0.1)),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: color.withOpacity(0.1), width: 0.5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 20),
|
||||
Icon(icon, color: color, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold, color: color),
|
||||
),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, color: AppColors.textSecondaryLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 16),
|
||||
Icon(Icons.arrow_forward_ios, color: color.withOpacity(0.5), size: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1381,37 +1160,30 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
Widget _buildStorageItem(String title, String size, VoidCallback onTap) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: AppColors.lightBorder, width: 0.5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.folder, color: Colors.grey[600], size: 20),
|
||||
const Icon(Icons.folder_outlined, color: AppColors.primaryGreen, size: 16),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
size,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 10, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(Icons.clear, color: Colors.grey[400], size: 16),
|
||||
const Icon(Icons.clear, color: AppColors.error, size: 14),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -1423,28 +1195,21 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[50],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: AppColors.lightBorder, width: 0.5),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF1F2937),
|
||||
),
|
||||
title.toUpperCase(),
|
||||
style: AppTypography.subtitleSmall.copyWith(fontSize: 9, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: AppTypography.bodyTextSmall.copyWith(fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1477,7 +1242,7 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
}
|
||||
});
|
||||
// Charger le profil complet depuis le backend
|
||||
context.read<ProfileBloc>().add(LoadMyProfile(authState.user.email));
|
||||
context.read<ProfileBloc>().add(const LoadMe());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1832,20 +1597,27 @@ class _ProfilePageState extends State<ProfilePage>
|
||||
void _showSuccessSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: const Color(0xFF00B894),
|
||||
content: Text(
|
||||
message.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
backgroundColor: AppColors.success,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Afficher un message d'erreur
|
||||
void _showErrorSnackBar(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: const Color(0xFFE74C3C),
|
||||
content: Text(
|
||||
message.toUpperCase(),
|
||||
style: AppTypography.actionText.copyWith(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
backgroundColor: AppColors.error,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user