// ===== lib/utils/responsive_helper.dart ===== // Système de responsivité centralisé pour tablettes et rotation import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:math' as math; /// Gestionnaire principal de la responsivité et rotation class ResponsiveHelper { // =================== CONSTANTES DE BREAKPOINTS =================== /// Breakpoints pour différentes tailles d'écran static const double phoneMaxWidth = 400; static const double tabletMinWidth = 600; static const double desktopMinWidth = 1024; /// Hauteurs critiques pour détection d'orientation static const double portraitMinHeight = 600; static const double landscapeMaxHeight = 500; // =================== DÉTECTION DE TYPE D'APPAREIL =================== /// Détermine si c'est un petit téléphone static bool isSmallPhone(BuildContext context) { return MediaQuery.of(context).size.width < phoneMaxWidth; } /// Détermine si c'est un téléphone standard static bool isPhone(BuildContext context) { return MediaQuery.of(context).size.width < tabletMinWidth; } /// Détermine si c'est une tablette static bool isTablet(BuildContext context) { final width = MediaQuery.of(context).size.width; return width >= tabletMinWidth && width < desktopMinWidth; } /// Détermine si c'est un grand écran/desktop static bool isDesktop(BuildContext context) { return MediaQuery.of(context).size.width >= desktopMinWidth; } // =================== DÉTECTION D'ORIENTATION =================== /// Détection précise de l'orientation static bool isPortrait(BuildContext context) { final size = MediaQuery.of(context).size; return size.height > size.width; } /// Détection du mode paysage static bool isLandscape(BuildContext context) { final size = MediaQuery.of(context).size; return size.width > size.height; } /// Détection spéciale pour tablette en mode paysage static bool isTabletLandscape(BuildContext context) { return isTablet(context) && isLandscape(context); } /// Détection spéciale pour téléphone en mode paysage static bool isPhoneLandscape(BuildContext context) { return isPhone(context) && isLandscape(context); } // =================== CONFIGURATION D'ORIENTATION =================== /// Force l'orientation portrait et paysage pour tablettes static void enableAllOrientations() { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } /// Force uniquement le mode portrait (pour certains écrans si nécessaire) static void enablePortraitOnly() { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); } /// Force uniquement le mode paysage static void enableLandscapeOnly() { SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } // =================== DIMENSIONS RESPONSIVES =================== /// Obtient la largeur responsive selon l'appareil static double getResponsiveWidth(BuildContext context, { double? phone, double? tablet, double? desktop, }) { if (isDesktop(context)) return desktop ?? tablet ?? phone ?? 0; if (isTablet(context)) return tablet ?? phone ?? 0; return phone ?? 0; } /// Obtient la hauteur responsive selon l'appareil static double getResponsiveHeight(BuildContext context, { double? phone, double? tablet, double? desktop, }) { if (isDesktop(context)) return desktop ?? tablet ?? phone ?? 0; if (isTablet(context)) return tablet ?? phone ?? 0; return phone ?? 0; } /// Padding responsive adaptatif static EdgeInsets getResponsivePadding(BuildContext context, { EdgeInsets? phone, EdgeInsets? tablet, EdgeInsets? desktop, }) { if (isDesktop(context)) return desktop ?? tablet ?? phone ?? EdgeInsets.zero; if (isTablet(context)) return tablet ?? phone ?? EdgeInsets.zero; return phone ?? EdgeInsets.zero; } /// Taille de police responsive static double getResponsiveFontSize(BuildContext context, { double? phone, double? tablet, double? desktop, }) { if (isDesktop(context)) return desktop ?? tablet ?? phone ?? 14; if (isTablet(context)) return tablet ?? phone ?? 14; return phone ?? 14; } // =================== GRILLES RESPONSIVES =================== /// Nombre de colonnes selon la taille d'écran static int getGridColumns(BuildContext context, { int phoneColumns = 2, int tabletPortraitColumns = 3, int tabletLandscapeColumns = 4, int desktopColumns = 5, }) { if (isDesktop(context)) return desktopColumns; if (isTabletLandscape(context)) return tabletLandscapeColumns; if (isTablet(context)) return tabletPortraitColumns; return phoneColumns; } /// Ratio d'aspect adaptatif pour les cartes static double getCardAspectRatio(BuildContext context) { if (isPhoneLandscape(context)) return 2.5; if (isTabletLandscape(context)) return 1.8; if (isTablet(context)) return 1.4; return 1.2; // Portrait par défaut } // =================== WIDGETS RESPONSIVES =================== /// Container responsive avec padding adaptatif static Widget responsiveContainer({ required BuildContext context, required Widget child, EdgeInsets? phonePadding, EdgeInsets? tabletPadding, EdgeInsets? desktopPadding, Color? backgroundColor, BorderRadius? borderRadius, }) { return Container( padding: getResponsivePadding( context, phone: phonePadding ?? const EdgeInsets.all(16), tablet: tabletPadding ?? const EdgeInsets.all(24), desktop: desktopPadding ?? const EdgeInsets.all(32), ), decoration: BoxDecoration( color: backgroundColor, borderRadius: borderRadius ?? BorderRadius.circular(12), ), child: child, ); } /// Texte responsive avec tailles adaptatives static Widget responsiveText( String text, { required BuildContext context, double? phoneSize, double? tabletSize, double? desktopSize, FontWeight? fontWeight, Color? color, TextAlign? textAlign, int? maxLines, TextOverflow? overflow, }) { return Text( text, style: TextStyle( fontSize: getResponsiveFontSize( context, phone: phoneSize ?? 14, tablet: tabletSize ?? 16, desktop: desktopSize ?? 18, ), fontWeight: fontWeight, color: color, ), textAlign: textAlign, maxLines: maxLines, overflow: overflow, ); } // =================== LAYOUTS RESPONSIVES =================== /// Layout responsive en colonnes static Widget responsiveRow({ required BuildContext context, required List children, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, bool forceColumnOnPhone = true, }) { if (forceColumnOnPhone && isPhone(context)) { return Column( mainAxisAlignment: mainAxisAlignment, crossAxisAlignment: crossAxisAlignment, children: children, ); } return Row( mainAxisAlignment: mainAxisAlignment, crossAxisAlignment: crossAxisAlignment, children: children, ); } /// GridView responsive adaptatif static Widget responsiveGridView({ required BuildContext context, required List children, int? phoneColumns, int? tabletPortraitColumns, int? tabletLandscapeColumns, int? desktopColumns, double childAspectRatio = 1.0, double crossAxisSpacing = 8.0, double mainAxisSpacing = 8.0, EdgeInsets? padding, ScrollPhysics? physics, }) { final columns = getGridColumns( context, phoneColumns: phoneColumns ?? 2, tabletPortraitColumns: tabletPortraitColumns ?? 3, tabletLandscapeColumns: tabletLandscapeColumns ?? 4, desktopColumns: desktopColumns ?? 5, ); return GridView.count( shrinkWrap: true, physics: physics ?? const NeverScrollableScrollPhysics(), crossAxisCount: columns, childAspectRatio: childAspectRatio, crossAxisSpacing: crossAxisSpacing, mainAxisSpacing: mainAxisSpacing, padding: padding ?? getResponsivePadding( context, phone: const EdgeInsets.all(16), tablet: const EdgeInsets.all(24), ), children: children, ); } // =================== NAVIGATION RESPONSIVE =================== /// Détermine si on doit utiliser un Drawer ou une BottomNavigationBar static bool shouldUseDrawer(BuildContext context) { return isTabletLandscape(context) || isDesktop(context); } /// Détermine si on doit utiliser une navigation rail (côté gauche) static bool shouldUseNavigationRail(BuildContext context) { return isTabletLandscape(context) || isDesktop(context); } // =================== ORIENTATIONS SPÉCIFIQUES =================== /// Initialise les orientations selon l'appareil static void initializeOrientations(BuildContext context) { if (isTablet(context) || isDesktop(context)) { enableAllOrientations(); } else { // Pour les téléphones, on peut choisir de limiter ou non enableAllOrientations(); // Ou enablePortraitOnly() selon les besoins } } // =================== UTILITAIRES DIVERS =================== /// Obtient la largeur maximale recommandée pour le contenu static double getMaxContentWidth(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; if (isDesktop(context)) { return math.min(screenWidth * 0.8, 1200); // Max 1200px sur desktop } else if (isTablet(context)) { return screenWidth * 0.9; // 90% sur tablette } return screenWidth; // 100% sur téléphone } /// Centre le contenu avec une largeur maximale static Widget centerContent({ required BuildContext context, required Widget child, double? maxWidth, }) { return Center( child: ConstrainedBox( constraints: BoxConstraints( maxWidth: maxWidth ?? getMaxContentWidth(context), ), child: child, ), ); } /// Détermine l'espacement entre les éléments static double getSpacing(BuildContext context, { double? small, double? medium, double? large, }) { if (isDesktop(context)) return large ?? 24; if (isTablet(context)) return medium ?? 16; return small ?? 12; } } // =================== EXTENSION POUR MEDIAQUERY =================== /// Extension pour simplifier l'utilisation de ResponsiveHelper extension ResponsiveExtension on BuildContext { bool get isSmallPhone => ResponsiveHelper.isSmallPhone(this); bool get isPhone => ResponsiveHelper.isPhone(this); bool get isTablet => ResponsiveHelper.isTablet(this); bool get isDesktop => ResponsiveHelper.isDesktop(this); bool get isPortrait => ResponsiveHelper.isPortrait(this); bool get isLandscape => ResponsiveHelper.isLandscape(this); bool get isTabletLandscape => ResponsiveHelper.isTabletLandscape(this); bool get isPhoneLandscape => ResponsiveHelper.isPhoneLandscape(this); double responsiveWidth({double? phone, double? tablet, double? desktop}) => ResponsiveHelper.getResponsiveWidth(this, phone: phone, tablet: tablet, desktop: desktop); double responsiveHeight({double? phone, double? tablet, double? desktop}) => ResponsiveHelper.getResponsiveHeight(this, phone: phone, tablet: tablet, desktop: desktop); double responsiveFontSize({double? phone, double? tablet, double? desktop}) => ResponsiveHelper.getResponsiveFontSize(this, phone: phone, tablet: tablet, desktop: desktop); EdgeInsets responsivePadding({EdgeInsets? phone, EdgeInsets? tablet, EdgeInsets? desktop}) => ResponsiveHelper.getResponsivePadding(this, phone: phone, tablet: tablet, desktop: desktop); }