feat: Livraison version 3.0.6
- 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>
This commit is contained in:
229
app/lib/presentation/widgets/loading_spin_overlay.dart
Normal file
229
app/lib/presentation/widgets/loading_spin_overlay.dart
Normal file
@@ -0,0 +1,229 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user