/// 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 '../auth/models/user.dart'; import '../auth/models/user_role.dart'; import '../auth/services/permission_engine.dart'; import '../auth/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 roleWidgets; /// Permissions requises pour afficher le widget final List 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 createState() => _AdaptiveWidgetState(); } class _AdaptiveWidgetState extends State with TickerProviderStateMixin { /// Cache des widgets construits pour éviter les reconstructions final Map _widgetCache = {}; /// Contrôleur d'animation pour le morphing late AnimationController _morphController; /// Animation d'opacité late Animation _opacityAnimation; /// Animation d'échelle late Animation _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( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _morphController, curve: widget.animationCurve, )); _scaleAnimation = Tween( 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( 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( 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 _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 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( builder: (context, state) { if (state is AuthLoading) { return loadingWidget ?? const SizedBox.shrink(); } if (state is! AuthAuthenticated) { return unauthorizedWidget ?? const SizedBox.shrink(); } return FutureBuilder( 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 _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); } }