Files
2025-11-17 16:02:04 +00:00

397 lines
11 KiB
Dart

/// Widget adaptatif révolutionnaire avec morphing intelligent
/// Transformation dynamique selon le rôle utilisateur avec animations fluides
library adaptive_widget;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../features/authentication/data/models/user.dart';
import '../../features/authentication/data/models/user_role.dart';
import '../../features/authentication/data/datasources/permission_engine.dart';
import '../../features/authentication/presentation/bloc/auth_bloc.dart';
/// Widget adaptatif révolutionnaire qui se transforme selon le rôle utilisateur
///
/// Fonctionnalités :
/// - Morphing intelligent avec animations fluides
/// - Widgets spécifiques par rôle
/// - Vérification de permissions intégrée
/// - Fallback gracieux pour les rôles non supportés
/// - Cache des widgets pour les performances
class AdaptiveWidget extends StatefulWidget {
/// Widgets spécifiques par rôle utilisateur
final Map<UserRole, Widget Function()> roleWidgets;
/// Permissions requises pour afficher le widget
final List<String> requiredPermissions;
/// Widget affiché si les permissions sont insuffisantes
final Widget? fallbackWidget;
/// Widget affiché pendant le chargement
final Widget? loadingWidget;
/// Activer les animations de morphing
final bool enableMorphing;
/// Durée de l'animation de morphing
final Duration morphingDuration;
/// Courbe d'animation
final Curve animationCurve;
/// Contexte organisationnel pour les permissions
final String? organizationId;
/// Activer l'audit trail
final bool auditLog;
/// Constructeur du widget adaptatif
const AdaptiveWidget({
super.key,
required this.roleWidgets,
this.requiredPermissions = const [],
this.fallbackWidget,
this.loadingWidget,
this.enableMorphing = true,
this.morphingDuration = const Duration(milliseconds: 800),
this.animationCurve = Curves.easeInOutCubic,
this.organizationId,
this.auditLog = true,
});
@override
State<AdaptiveWidget> createState() => _AdaptiveWidgetState();
}
class _AdaptiveWidgetState extends State<AdaptiveWidget>
with TickerProviderStateMixin {
/// Cache des widgets construits pour éviter les reconstructions
final Map<UserRole, Widget> _widgetCache = {};
/// Contrôleur d'animation pour le morphing
late AnimationController _morphController;
/// Animation d'opacité
late Animation<double> _opacityAnimation;
/// Animation d'échelle
late Animation<double> _scaleAnimation;
/// Rôle utilisateur précédent pour détecter les changements
UserRole? _previousRole;
@override
void initState() {
super.initState();
_initializeAnimations();
}
@override
void dispose() {
_morphController.dispose();
super.dispose();
}
/// Initialise les animations de morphing
void _initializeAnimations() {
_morphController = AnimationController(
duration: widget.morphingDuration,
vsync: this,
);
_opacityAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _morphController,
curve: widget.animationCurve,
));
_scaleAnimation = Tween<double>(
begin: 0.95,
end: 1.0,
).animate(CurvedAnimation(
parent: _morphController,
curve: widget.animationCurve,
));
// Démarrer l'animation initiale
_morphController.forward();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
// État de chargement
if (state is AuthLoading) {
return widget.loadingWidget ?? _buildLoadingWidget();
}
// État non authentifié
if (state is! AuthAuthenticated) {
return _buildForRole(UserRole.visitor);
}
final user = state.user;
final currentRole = user.primaryRole;
// Détecter le changement de rôle pour déclencher l'animation
if (_previousRole != null && _previousRole != currentRole && widget.enableMorphing) {
_triggerMorphing();
}
_previousRole = currentRole;
return FutureBuilder<bool>(
future: _checkPermissions(user),
builder: (context, permissionSnapshot) {
if (permissionSnapshot.connectionState == ConnectionState.waiting) {
return widget.loadingWidget ?? _buildLoadingWidget();
}
final hasPermissions = permissionSnapshot.data ?? false;
if (!hasPermissions) {
return widget.fallbackWidget ?? _buildUnauthorizedWidget();
}
return _buildForRole(currentRole);
},
);
},
);
}
/// Construit le widget pour un rôle spécifique
Widget _buildForRole(UserRole role) {
// Vérifier le cache
if (_widgetCache.containsKey(role)) {
return _wrapWithAnimation(_widgetCache[role]!);
}
// Trouver le widget approprié
Widget? widget = _findWidgetForRole(role);
widget ??= this.widget.fallbackWidget ?? _buildUnsupportedRoleWidget(role);
// Mettre en cache
_widgetCache[role] = widget;
return _wrapWithAnimation(widget);
}
/// Trouve le widget approprié pour un rôle
Widget? _findWidgetForRole(UserRole role) {
// Vérification directe
if (widget.roleWidgets.containsKey(role)) {
return widget.roleWidgets[role]!();
}
// Recherche du meilleur match par niveau de rôle
UserRole? bestMatch;
for (final availableRole in widget.roleWidgets.keys) {
if (availableRole.level <= role.level) {
if (bestMatch == null || availableRole.level > bestMatch.level) {
bestMatch = availableRole;
}
}
}
return bestMatch != null ? widget.roleWidgets[bestMatch]!() : null;
}
/// Enveloppe le widget avec les animations
Widget _wrapWithAnimation(Widget child) {
if (!widget.enableMorphing) return child;
return AnimatedBuilder(
animation: _morphController,
builder: (context, _) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: child,
),
);
},
);
}
/// Déclenche l'animation de morphing
void _triggerMorphing() {
_morphController.reset();
_morphController.forward();
// Vider le cache pour forcer la reconstruction
_widgetCache.clear();
}
/// Vérifie les permissions requises
Future<bool> _checkPermissions(User user) async {
if (widget.requiredPermissions.isEmpty) return true;
final results = await PermissionEngine.hasPermissions(
user,
widget.requiredPermissions,
organizationId: widget.organizationId,
auditLog: widget.auditLog,
);
return results.values.every((hasPermission) => hasPermission);
}
/// Widget de chargement par défaut
Widget _buildLoadingWidget() {
return const Center(
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
),
);
}
/// Widget non autorisé par défaut
Widget _buildUnauthorizedWidget() {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.lock_outline,
size: 48,
color: Theme.of(context).disabledColor,
),
const SizedBox(height: 8),
Text(
'Accès non autorisé',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
),
const SizedBox(height: 4),
Text(
'Vous n\'avez pas les permissions nécessaires',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).disabledColor,
),
textAlign: TextAlign.center,
),
],
),
);
}
/// Widget pour rôle non supporté
Widget _buildUnsupportedRoleWidget(UserRole role) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.warning_outlined,
size: 48,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(height: 8),
Text(
'Rôle non supporté',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
const SizedBox(height: 4),
Text(
'Le rôle ${role.displayName} n\'est pas supporté par ce widget',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
],
),
);
}
}
/// Widget sécurisé avec vérification de permissions intégrée
///
/// Version simplifiée d'AdaptiveWidget pour les cas où seules
/// les permissions importent, pas le rôle spécifique
class SecureWidget extends StatelessWidget {
/// Permissions requises pour afficher le widget
final List<String> requiredPermissions;
/// Widget à afficher si autorisé
final Widget child;
/// Widget à afficher si non autorisé
final Widget? unauthorizedWidget;
/// Widget à afficher pendant le chargement
final Widget? loadingWidget;
/// Contexte organisationnel
final String? organizationId;
/// Activer l'audit trail
final bool auditLog;
/// Constructeur du widget sécurisé
const SecureWidget({
super.key,
required this.requiredPermissions,
required this.child,
this.unauthorizedWidget,
this.loadingWidget,
this.organizationId,
this.auditLog = true,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is AuthLoading) {
return loadingWidget ?? const SizedBox.shrink();
}
if (state is! AuthAuthenticated) {
return unauthorizedWidget ?? const SizedBox.shrink();
}
return FutureBuilder<bool>(
future: _checkPermissions(state.user),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return loadingWidget ?? const SizedBox.shrink();
}
final hasPermissions = snapshot.data ?? false;
if (!hasPermissions) {
return unauthorizedWidget ?? const SizedBox.shrink();
}
return child;
},
);
},
);
}
/// Vérifie les permissions requises
Future<bool> _checkPermissions(User user) async {
if (requiredPermissions.isEmpty) return true;
final results = await PermissionEngine.hasPermissions(
user,
requiredPermissions,
organizationId: organizationId,
auditLog: auditLog,
);
return results.values.every((hasPermission) => hasPermission);
}
}