275 lines
7.4 KiB
Dart
275 lines
7.4 KiB
Dart
/// Responsive layout utilities for optimal performance across devices
|
|
///
|
|
/// Features:
|
|
/// - Breakpoint-based layouts
|
|
/// - Adaptive grid columns
|
|
/// - Performance-optimized responsive widgets
|
|
/// - Device-specific optimizations
|
|
|
|
import 'package:flutter/material.dart';
|
|
import '../constants/performance_constants.dart';
|
|
|
|
/// Responsive helper for device-specific optimizations
|
|
class ResponsiveHelper {
|
|
/// Check if device is mobile
|
|
static bool isMobile(BuildContext context) {
|
|
return MediaQuery.of(context).size.width < PerformanceConstants.mobileBreakpoint;
|
|
}
|
|
|
|
/// Check if device is tablet
|
|
static bool isTablet(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return width >= PerformanceConstants.mobileBreakpoint &&
|
|
width < PerformanceConstants.desktopBreakpoint;
|
|
}
|
|
|
|
/// Check if device is desktop
|
|
static bool isDesktop(BuildContext context) {
|
|
return MediaQuery.of(context).size.width >= PerformanceConstants.desktopBreakpoint;
|
|
}
|
|
|
|
/// Get appropriate grid column count
|
|
static int getGridColumns(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return PerformanceConstants.getGridColumnCount(width);
|
|
}
|
|
|
|
/// Get appropriate cache extent
|
|
static double getCacheExtent(BuildContext context) {
|
|
final height = MediaQuery.of(context).size.height;
|
|
return PerformanceConstants.getCacheExtent(height);
|
|
}
|
|
|
|
/// Check if high performance mode should be enabled
|
|
static bool shouldUseHighPerformance(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return PerformanceConstants.shouldUseHighPerformanceMode(width);
|
|
}
|
|
|
|
/// Get value based on screen size
|
|
static T getValue<T>(
|
|
BuildContext context, {
|
|
required T mobile,
|
|
T? tablet,
|
|
T? desktop,
|
|
}) {
|
|
if (isDesktop(context) && desktop != null) return desktop;
|
|
if (isTablet(context) && tablet != null) return tablet;
|
|
return mobile;
|
|
}
|
|
|
|
/// Get responsive padding
|
|
static EdgeInsets getResponsivePadding(BuildContext context) {
|
|
if (isDesktop(context)) {
|
|
return const EdgeInsets.all(24);
|
|
} else if (isTablet(context)) {
|
|
return const EdgeInsets.all(16);
|
|
} else {
|
|
return const EdgeInsets.all(12);
|
|
}
|
|
}
|
|
|
|
/// Get responsive spacing
|
|
static double getSpacing(BuildContext context) {
|
|
if (isDesktop(context)) return 16;
|
|
if (isTablet(context)) return 12;
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
/// Responsive layout builder with performance optimization
|
|
class ResponsiveLayout extends StatelessWidget {
|
|
final Widget mobile;
|
|
final Widget? tablet;
|
|
final Widget? desktop;
|
|
|
|
const ResponsiveLayout({
|
|
super.key,
|
|
required this.mobile,
|
|
this.tablet,
|
|
this.desktop,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
if (constraints.maxWidth >= PerformanceConstants.desktopBreakpoint) {
|
|
return desktop ?? tablet ?? mobile;
|
|
} else if (constraints.maxWidth >= PerformanceConstants.mobileBreakpoint) {
|
|
return tablet ?? mobile;
|
|
} else {
|
|
return mobile;
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Responsive value builder
|
|
class ResponsiveValue<T> extends StatelessWidget {
|
|
final T mobile;
|
|
final T? tablet;
|
|
final T? desktop;
|
|
final Widget Function(BuildContext context, T value) builder;
|
|
|
|
const ResponsiveValue({
|
|
super.key,
|
|
required this.mobile,
|
|
this.tablet,
|
|
this.desktop,
|
|
required this.builder,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final value = ResponsiveHelper.getValue(
|
|
context,
|
|
mobile: mobile,
|
|
tablet: tablet,
|
|
desktop: desktop,
|
|
);
|
|
return builder(context, value);
|
|
}
|
|
}
|
|
|
|
/// Adaptive grid configuration
|
|
class AdaptiveGridConfig {
|
|
final int crossAxisCount;
|
|
final double childAspectRatio;
|
|
final double spacing;
|
|
final double cacheExtent;
|
|
|
|
const AdaptiveGridConfig({
|
|
required this.crossAxisCount,
|
|
required this.childAspectRatio,
|
|
required this.spacing,
|
|
required this.cacheExtent,
|
|
});
|
|
|
|
factory AdaptiveGridConfig.fromContext(
|
|
BuildContext context, {
|
|
GridType type = GridType.products,
|
|
}) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
final height = MediaQuery.of(context).size.height;
|
|
|
|
return AdaptiveGridConfig(
|
|
crossAxisCount: PerformanceConstants.getGridColumnCount(width),
|
|
childAspectRatio: type == GridType.products
|
|
? PerformanceConstants.productCardAspectRatio
|
|
: PerformanceConstants.categoryCardAspectRatio,
|
|
spacing: PerformanceConstants.gridSpacing,
|
|
cacheExtent: PerformanceConstants.getCacheExtent(height),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum GridType {
|
|
products,
|
|
categories,
|
|
}
|
|
|
|
/// Responsive grid view that adapts to screen size
|
|
class AdaptiveGridView<T> extends StatelessWidget {
|
|
final List<T> items;
|
|
final Widget Function(BuildContext context, T item, int index) itemBuilder;
|
|
final GridType type;
|
|
final ScrollController? scrollController;
|
|
final EdgeInsets? padding;
|
|
|
|
const AdaptiveGridView({
|
|
super.key,
|
|
required this.items,
|
|
required this.itemBuilder,
|
|
this.type = GridType.products,
|
|
this.scrollController,
|
|
this.padding,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final config = AdaptiveGridConfig.fromContext(context, type: type);
|
|
|
|
return GridView.builder(
|
|
controller: scrollController,
|
|
padding: padding ?? ResponsiveHelper.getResponsivePadding(context),
|
|
physics: const BouncingScrollPhysics(
|
|
parent: AlwaysScrollableScrollPhysics(),
|
|
),
|
|
cacheExtent: config.cacheExtent,
|
|
itemCount: items.length,
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: config.crossAxisCount,
|
|
crossAxisSpacing: config.spacing,
|
|
mainAxisSpacing: config.spacing,
|
|
childAspectRatio: config.childAspectRatio,
|
|
),
|
|
itemBuilder: (context, index) {
|
|
final item = items[index];
|
|
return RepaintBoundary(
|
|
key: ValueKey('adaptive_grid_item_$index'),
|
|
child: itemBuilder(context, item, index),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Responsive container with adaptive sizing
|
|
class ResponsiveContainer extends StatelessWidget {
|
|
final Widget child;
|
|
final double? mobileWidth;
|
|
final double? tabletWidth;
|
|
final double? desktopWidth;
|
|
|
|
const ResponsiveContainer({
|
|
super.key,
|
|
required this.child,
|
|
this.mobileWidth,
|
|
this.tabletWidth,
|
|
this.desktopWidth,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final width = ResponsiveHelper.getValue(
|
|
context,
|
|
mobile: mobileWidth,
|
|
tablet: tabletWidth,
|
|
desktop: desktopWidth,
|
|
);
|
|
|
|
return Container(
|
|
width: width,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Extension for easier responsive values
|
|
extension ResponsiveContextExtension on BuildContext {
|
|
bool get isMobile => ResponsiveHelper.isMobile(this);
|
|
bool get isTablet => ResponsiveHelper.isTablet(this);
|
|
bool get isDesktop => ResponsiveHelper.isDesktop(this);
|
|
|
|
int get gridColumns => ResponsiveHelper.getGridColumns(this);
|
|
double get cacheExtent => ResponsiveHelper.getCacheExtent(this);
|
|
double get spacing => ResponsiveHelper.getSpacing(this);
|
|
|
|
EdgeInsets get responsivePadding => ResponsiveHelper.getResponsivePadding(this);
|
|
|
|
T responsive<T>({
|
|
required T mobile,
|
|
T? tablet,
|
|
T? desktop,
|
|
}) {
|
|
return ResponsiveHelper.getValue(
|
|
this,
|
|
mobile: mobile,
|
|
tablet: tablet,
|
|
desktop: desktop,
|
|
);
|
|
}
|
|
}
|