- Amélioration de la gestion des entités et des utilisateurs - Mise à jour des modèles Amicale et Client avec champs supplémentaires - Ajout du service de logging et amélioration du chargement UI - Refactoring des formulaires utilisateur et amicale - Intégration de file_picker et image_picker pour la gestion des fichiers - Amélioration de la gestion des membres et de leur suppression - Optimisation des performances de l'API - Mise à jour de la documentation technique 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
229 lines
5.9 KiB
Dart
229 lines
5.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'dart:ui';
|
|
|
|
/// Widget d'overlay de chargement moderne avec spinner circulaire
|
|
/// Affiche un spinner animé avec fond flou et message optionnel
|
|
class LoadingSpinOverlay extends StatefulWidget {
|
|
final String? message;
|
|
final Color backgroundColor;
|
|
final Color spinnerColor;
|
|
final Color textColor;
|
|
final double blurAmount;
|
|
final double spinnerSize;
|
|
final bool showCard;
|
|
|
|
const LoadingSpinOverlay({
|
|
super.key,
|
|
this.message,
|
|
this.backgroundColor = Colors.black54,
|
|
this.spinnerColor = Colors.blue,
|
|
this.textColor = Colors.white,
|
|
this.blurAmount = 8.0,
|
|
this.spinnerSize = 50.0,
|
|
this.showCard = true,
|
|
});
|
|
|
|
@override
|
|
State<LoadingSpinOverlay> createState() => _LoadingSpinOverlayState();
|
|
}
|
|
|
|
class _LoadingSpinOverlayState extends State<LoadingSpinOverlay>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _fadeController;
|
|
late AnimationController _rotationController;
|
|
late Animation<double> _fadeAnimation;
|
|
late Animation<double> _rotationAnimation;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_fadeController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 300),
|
|
);
|
|
_rotationController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(seconds: 1),
|
|
);
|
|
|
|
_fadeAnimation = Tween<double>(
|
|
begin: 0.0,
|
|
end: 1.0,
|
|
).animate(CurvedAnimation(
|
|
parent: _fadeController,
|
|
curve: Curves.easeInOut,
|
|
));
|
|
|
|
_rotationAnimation = Tween<double>(
|
|
begin: 0.0,
|
|
end: 2 * 3.14159,
|
|
).animate(CurvedAnimation(
|
|
parent: _rotationController,
|
|
curve: Curves.linear,
|
|
));
|
|
|
|
_fadeController.forward();
|
|
_rotationController.repeat();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_fadeController.dispose();
|
|
_rotationController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FadeTransition(
|
|
opacity: _fadeAnimation,
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(
|
|
sigmaX: widget.blurAmount,
|
|
sigmaY: widget.blurAmount,
|
|
),
|
|
child: Container(
|
|
color: widget.backgroundColor,
|
|
child: Center(
|
|
child: widget.showCard
|
|
? _buildCardContent()
|
|
: _buildSimpleContent(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCardContent() {
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(32),
|
|
constraints: const BoxConstraints(
|
|
maxWidth: 280,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.92), // Semi-transparent
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.15),
|
|
blurRadius: 20,
|
|
spreadRadius: 2,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Spinner simple de Flutter
|
|
SizedBox(
|
|
width: widget.spinnerSize,
|
|
height: widget.spinnerSize,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 3,
|
|
valueColor: AlwaysStoppedAnimation<Color>(widget.spinnerColor),
|
|
),
|
|
),
|
|
if (widget.message != null) ...[
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
widget.message!,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey[800],
|
|
letterSpacing: 0.3,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSimpleContent() {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
SizedBox(
|
|
width: widget.spinnerSize,
|
|
height: widget.spinnerSize,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 3,
|
|
valueColor: AlwaysStoppedAnimation<Color>(widget.spinnerColor),
|
|
),
|
|
),
|
|
if (widget.message != null) ...[
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
widget.message!,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w500,
|
|
color: widget.textColor,
|
|
letterSpacing: 0.5,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Classe utilitaire pour gérer l'overlay de chargement avec spinner
|
|
class LoadingSpinOverlayUtils {
|
|
static OverlayEntry? _currentOverlay;
|
|
|
|
/// Affiche l'overlay de chargement avec spinner
|
|
static OverlayEntry show({
|
|
required BuildContext context,
|
|
String? message,
|
|
double blurAmount = 8.0,
|
|
bool showCard = true,
|
|
Color? spinnerColor,
|
|
}) {
|
|
// Fermer l'overlay existant s'il y en a un
|
|
hide();
|
|
|
|
final theme = Theme.of(context);
|
|
final overlayEntry = OverlayEntry(
|
|
builder: (context) => LoadingSpinOverlay(
|
|
message: message,
|
|
blurAmount: blurAmount,
|
|
showCard: showCard,
|
|
spinnerColor: spinnerColor ?? theme.colorScheme.primary,
|
|
),
|
|
);
|
|
|
|
_currentOverlay = overlayEntry;
|
|
Overlay.of(context).insert(overlayEntry);
|
|
return overlayEntry;
|
|
}
|
|
|
|
/// Met à jour le message de l'overlay existant
|
|
static void updateMessage({
|
|
required OverlayEntry overlayEntry,
|
|
String? message,
|
|
}) {
|
|
overlayEntry.markNeedsBuild();
|
|
}
|
|
|
|
/// Cache l'overlay de chargement
|
|
static void hide() {
|
|
_currentOverlay?.remove();
|
|
_currentOverlay = null;
|
|
}
|
|
|
|
/// Cache un overlay spécifique
|
|
static void hideSpecific(OverlayEntry? overlayEntry) {
|
|
overlayEntry?.remove();
|
|
if (_currentOverlay == overlayEntry) {
|
|
_currentOverlay = null;
|
|
}
|
|
}
|
|
} |