253 lines
6.6 KiB
Dart
253 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
class ResponsiveLayout extends StatelessWidget {
|
|
final Widget mobile;
|
|
final Widget? tablet;
|
|
final Widget? desktop;
|
|
final Widget? largeDesktop;
|
|
|
|
const ResponsiveLayout({
|
|
super.key,
|
|
required this.mobile,
|
|
this.tablet,
|
|
this.desktop,
|
|
this.largeDesktop,
|
|
});
|
|
|
|
static bool isMobile(BuildContext context) {
|
|
return MediaQuery.of(context).size.width < 768;
|
|
}
|
|
|
|
static bool isTablet(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return width >= 768 && width < 1024;
|
|
}
|
|
|
|
static bool isDesktop(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
return width >= 1024 && width < 1440;
|
|
}
|
|
|
|
static bool isLargeDesktop(BuildContext context) {
|
|
return MediaQuery.of(context).size.width >= 1440;
|
|
}
|
|
|
|
static bool isPortrait(BuildContext context) {
|
|
return MediaQuery.of(context).orientation == Orientation.portrait;
|
|
}
|
|
|
|
static bool isLandscape(BuildContext context) {
|
|
return MediaQuery.of(context).orientation == Orientation.landscape;
|
|
}
|
|
|
|
static double screenWidth(BuildContext context) {
|
|
return MediaQuery.of(context).size.width;
|
|
}
|
|
|
|
static double screenHeight(BuildContext context) {
|
|
return MediaQuery.of(context).size.height;
|
|
}
|
|
|
|
static EdgeInsets safePadding(BuildContext context) {
|
|
return MediaQuery.of(context).padding;
|
|
}
|
|
|
|
static double safeAreaHeight(BuildContext context) {
|
|
final screenHeight = MediaQuery.of(context).size.height;
|
|
final paddingTop = MediaQuery.of(context).padding.top;
|
|
final paddingBottom = MediaQuery.of(context).padding.bottom;
|
|
return screenHeight - paddingTop - paddingBottom;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
|
|
if (width >= 1440 && largeDesktop != null) {
|
|
return largeDesktop!;
|
|
} else if (width >= 1024 && desktop != null) {
|
|
return desktop!;
|
|
} else if (width >= 768 && tablet != null) {
|
|
return tablet!;
|
|
} else {
|
|
return mobile;
|
|
}
|
|
}
|
|
}
|
|
|
|
class ResponsiveContainer extends StatelessWidget {
|
|
final Widget child;
|
|
final EdgeInsetsGeometry? padding;
|
|
final double? maxWidth;
|
|
final bool centerContent;
|
|
|
|
const ResponsiveContainer({
|
|
super.key,
|
|
required this.child,
|
|
this.padding,
|
|
this.maxWidth,
|
|
this.centerContent = true,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final screenWidth = ResponsiveLayout.screenWidth(context);
|
|
final effectiveMaxWidth = maxWidth ?? (screenWidth < 768 ? screenWidth : 1200.0);
|
|
|
|
Widget container = Container(
|
|
width: double.infinity,
|
|
constraints: BoxConstraints(
|
|
maxWidth: effectiveMaxWidth,
|
|
),
|
|
padding: padding ?? const EdgeInsets.all(16),
|
|
child: child,
|
|
);
|
|
|
|
if (centerContent && screenWidth > effectiveMaxWidth) {
|
|
container = Center(child: container);
|
|
}
|
|
|
|
return container;
|
|
}
|
|
}
|
|
|
|
class ResponsiveGrid extends StatelessWidget {
|
|
final List<Widget> children;
|
|
final int mobileColumns;
|
|
final int tabletColumns;
|
|
final int desktopColumns;
|
|
final int largeDesktopColumns;
|
|
final double spacing;
|
|
final double runSpacing;
|
|
final EdgeInsets? padding;
|
|
|
|
const ResponsiveGrid({
|
|
super.key,
|
|
required this.children,
|
|
this.mobileColumns = 1,
|
|
this.tabletColumns = 2,
|
|
this.desktopColumns = 3,
|
|
this.largeDesktopColumns = 4,
|
|
this.spacing = 16.0,
|
|
this.runSpacing = 16.0,
|
|
this.padding,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
int columns;
|
|
if (ResponsiveLayout.isLargeDesktop(context)) {
|
|
columns = largeDesktopColumns;
|
|
} else if (ResponsiveLayout.isDesktop(context)) {
|
|
columns = desktopColumns;
|
|
} else if (ResponsiveLayout.isTablet(context)) {
|
|
columns = tabletColumns;
|
|
} else {
|
|
columns = mobileColumns;
|
|
}
|
|
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final childAspectRatio = (constraints.maxWidth - (spacing * (columns - 1))) / columns / 200;
|
|
|
|
return Padding(
|
|
padding: padding ?? EdgeInsets.zero,
|
|
child: GridView.count(
|
|
crossAxisCount: columns,
|
|
crossAxisSpacing: spacing,
|
|
mainAxisSpacing: runSpacing,
|
|
childAspectRatio: childAspectRatio,
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
children: children,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class ResponsiveCard extends StatelessWidget {
|
|
final Widget child;
|
|
final EdgeInsetsGeometry? padding;
|
|
final double? borderRadius;
|
|
final Color? backgroundColor;
|
|
final VoidCallback? onTap;
|
|
|
|
const ResponsiveCard({
|
|
super.key,
|
|
required this.child,
|
|
this.padding,
|
|
this.borderRadius,
|
|
this.backgroundColor,
|
|
this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final effectiveBorderRadius = borderRadius ?? (ResponsiveLayout.isMobile(context) ? 8.0 : 12.0);
|
|
final effectivePadding = padding ?? EdgeInsets.all(ResponsiveLayout.isMobile(context) ? 12.0 : 16.0);
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(effectiveBorderRadius),
|
|
),
|
|
color: backgroundColor,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(effectiveBorderRadius),
|
|
child: Padding(
|
|
padding: effectivePadding,
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ResponsiveLayoutBuilder extends StatelessWidget {
|
|
final Widget Function(BuildContext context, ScreenSize screenSize) builder;
|
|
|
|
const ResponsiveLayoutBuilder({
|
|
super.key,
|
|
required this.builder,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
ScreenSize screenSize;
|
|
if (constraints.maxWidth >= 1440) {
|
|
screenSize = ScreenSize.largeDesktop;
|
|
} else if (constraints.maxWidth >= 1024) {
|
|
screenSize = ScreenSize.desktop;
|
|
} else if (constraints.maxWidth >= 768) {
|
|
screenSize = ScreenSize.tablet;
|
|
} else {
|
|
screenSize = ScreenSize.mobile;
|
|
}
|
|
|
|
return builder(context, screenSize);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
enum ScreenSize {
|
|
mobile,
|
|
tablet,
|
|
desktop,
|
|
largeDesktop,
|
|
}
|
|
|
|
extension ScreenSizeExtension on ScreenSize {
|
|
bool get isMobile => this == ScreenSize.mobile;
|
|
bool get isTablet => this == ScreenSize.tablet;
|
|
bool get isDesktop => this == ScreenSize.desktop;
|
|
bool get isLargeDesktop => this == ScreenSize.largeDesktop;
|
|
bool get isDesktopOrLarge => isDesktop || isLargeDesktop;
|
|
bool get isTabletOrLarger => isTablet || isDesktopOrLarge;
|
|
}
|