Initial commit du projet Flutter
This commit is contained in:
377
lib/widgets/responsive_helper.dart
Normal file
377
lib/widgets/responsive_helper.dart
Normal file
@@ -0,0 +1,377 @@
|
||||
// ===== 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<Widget> 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<Widget> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user