Files
unionflow-client-quarkus-pr…/unionflow-mobile-apps/lib/features/notifications/presentation/widgets/notification_search_widget.dart
2025-09-17 17:54:06 +00:00

390 lines
11 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../core/widgets/unified_card.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_text_styles.dart';
import '../../domain/entities/notification.dart';
/// Widget de recherche et filtrage des notifications
class NotificationSearchWidget extends StatefulWidget {
final Function(String) onSearchChanged;
final Function({
Set<TypeNotification>? types,
Set<StatutNotification>? statuts,
}) onFiltersChanged;
final Set<TypeNotification> selectedTypes;
final Set<StatutNotification> selectedStatuts;
const NotificationSearchWidget({
super.key,
required this.onSearchChanged,
required this.onFiltersChanged,
required this.selectedTypes,
required this.selectedStatuts,
});
@override
State<NotificationSearchWidget> createState() => _NotificationSearchWidgetState();
}
class _NotificationSearchWidgetState extends State<NotificationSearchWidget> {
final TextEditingController _searchController = TextEditingController();
bool _showFilters = false;
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return UnifiedCard(
variant: UnifiedCardVariant.outlined,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Barre de recherche
Row(
children: [
Expanded(
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Rechercher dans les notifications...',
hintStyle: AppTextStyles.bodyMedium.copyWith(
color: AppColors.onSurface.withOpacity(0.6),
),
prefixIcon: Icon(
Icons.search,
color: AppColors.onSurface.withOpacity(0.6),
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
widget.onSearchChanged('');
},
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.outline.withOpacity(0.3),
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.outline.withOpacity(0.3),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: AppColors.primary,
width: 2,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onChanged: widget.onSearchChanged,
),
),
const SizedBox(width: 12),
// Bouton de filtres
IconButton(
onPressed: () {
setState(() {
_showFilters = !_showFilters;
});
},
icon: Icon(
Icons.filter_list,
color: _hasActiveFilters()
? AppColors.primary
: AppColors.onSurface.withOpacity(0.6),
),
style: IconButton.styleFrom(
backgroundColor: _hasActiveFilters()
? AppColors.primary.withOpacity(0.1)
: AppColors.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: _hasActiveFilters()
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
),
),
),
],
),
// Panneau de filtres (conditionnel)
if (_showFilters) ...[
const SizedBox(height: 16),
_buildFiltersPanel(),
],
],
),
),
);
}
Widget _buildFiltersPanel() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête des filtres
Row(
children: [
Text(
'Filtres',
style: AppTextStyles.titleSmall.copyWith(
fontWeight: FontWeight.w600,
),
),
const Spacer(),
if (_hasActiveFilters())
TextButton(
onPressed: _clearAllFilters,
child: Text(
'Tout effacer',
style: AppTextStyles.labelMedium.copyWith(
color: AppColors.primary,
),
),
),
],
),
const SizedBox(height: 12),
// Filtres par type
Text(
'Types de notification',
style: AppTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _getPopularTypes()
.map((type) => _buildTypeChip(type))
.toList(),
),
const SizedBox(height: 16),
// Filtres par statut
Text(
'Statuts',
style: AppTextStyles.labelLarge.copyWith(
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _getPopularStatuts()
.map((statut) => _buildStatutChip(statut))
.toList(),
),
const SizedBox(height: 16),
// Boutons d'action
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {
setState(() {
_showFilters = false;
});
},
child: const Text('Fermer'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
_showFilters = false;
});
// Les filtres sont déjà appliqués en temps réel
},
child: const Text('Appliquer'),
),
),
],
),
],
);
}
Widget _buildTypeChip(TypeNotification type) {
final isSelected = widget.selectedTypes.contains(type);
return FilterChip(
label: Text(
type.libelle,
style: AppTextStyles.labelMedium.copyWith(
color: isSelected ? AppColors.onPrimary : AppColors.onSurface,
),
),
selected: isSelected,
onSelected: (selected) {
final newTypes = Set<TypeNotification>.from(widget.selectedTypes);
if (selected) {
newTypes.add(type);
} else {
newTypes.remove(type);
}
widget.onFiltersChanged(types: newTypes);
},
selectedColor: AppColors.primary,
backgroundColor: AppColors.surface,
side: BorderSide(
color: isSelected
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
avatar: isSelected
? null
: Icon(
_getTypeIcon(type),
size: 16,
color: _getTypeColor(type),
),
);
}
Widget _buildStatutChip(StatutNotification statut) {
final isSelected = widget.selectedStatuts.contains(statut);
return FilterChip(
label: Text(
statut.libelle,
style: AppTextStyles.labelMedium.copyWith(
color: isSelected ? AppColors.onPrimary : AppColors.onSurface,
),
),
selected: isSelected,
onSelected: (selected) {
final newStatuts = Set<StatutNotification>.from(widget.selectedStatuts);
if (selected) {
newStatuts.add(statut);
} else {
newStatuts.remove(statut);
}
widget.onFiltersChanged(statuts: newStatuts);
},
selectedColor: AppColors.primary,
backgroundColor: AppColors.surface,
side: BorderSide(
color: isSelected
? AppColors.primary
: AppColors.outline.withOpacity(0.3),
),
avatar: isSelected
? null
: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: _getStatutColor(statut),
shape: BoxShape.circle,
),
),
);
}
List<TypeNotification> _getPopularTypes() {
return [
TypeNotification.nouvelEvenement,
TypeNotification.cotisationDue,
TypeNotification.nouvelleDemandeAide,
TypeNotification.nouveauMembre,
TypeNotification.annonceGenerale,
TypeNotification.messagePrive,
];
}
List<StatutNotification> _getPopularStatuts() {
return [
StatutNotification.nonLue,
StatutNotification.lue,
StatutNotification.marqueeImportante,
StatutNotification.archivee,
];
}
IconData _getTypeIcon(TypeNotification type) {
switch (type.icone) {
case 'event':
return Icons.event;
case 'payment':
return Icons.payment;
case 'help':
return Icons.help;
case 'person_add':
return Icons.person_add;
case 'campaign':
return Icons.campaign;
case 'mail':
return Icons.mail;
default:
return Icons.notifications;
}
}
Color _getTypeColor(TypeNotification type) {
try {
return Color(int.parse(type.couleur.replaceFirst('#', '0xFF')));
} catch (e) {
return AppColors.primary;
}
}
Color _getStatutColor(StatutNotification statut) {
try {
return Color(int.parse(statut.couleur.replaceFirst('#', '0xFF')));
} catch (e) {
return AppColors.primary;
}
}
bool _hasActiveFilters() {
return widget.selectedTypes.isNotEmpty || widget.selectedStatuts.isNotEmpty;
}
void _clearAllFilters() {
widget.onFiltersChanged(
types: <TypeNotification>{},
statuts: <StatutNotification>{},
);
}
}