flutter_responsive_plus 1.0.0
flutter_responsive_plus: ^1.0.0 copied to clipboard
The ultimate Flutter responsive & adaptive toolkit. Combines MediaQuery shortcuts, ScreenUtil-style Figma scaling, LayoutBuilder helpers, platform detection, breakpoint management, and extension-based sizing.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_responsive_plus/flutter_responsive_plus.dart';
void main() => runApp(const ExampleApp());
// ─────────────────────────────────────────────────────────────────────────────
// APP ROOT — multi-canvas Figma config + ResKit.builder demo
// ─────────────────────────────────────────────────────────────────────────────
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveKit(
config: ResKitConfig(
// ✅ Per-breakpoint Figma canvases — one per device category.
// Each canvas matches the exact frame size your designer used in Figma.
designs: ResKitDesignConfig(
mobile: ResKitDesignSize.iphone14, // 390 × 844
tablet: ResKitDesignSize.ipadMini, // 768 × 1024
desktop: ResKitDesignSize.macbookAir, // 1440 × 900
web: ResKitDesignSize.webBrowser, // 1280 × 800
// Optional fine-grained overrides:
// tabletSmall: ResKitDesignSize(width: 600, height: 900),
// tabletLarge: ResKitDesignSize(width: 1024, height: 1366),
// desktopLarge: ResKitDesignSize(width: 1920, height: 1080),
),
maxFontScale: 1.3,
breakpoints: ResKitBreakpoints(), // default or customise
),
child: MaterialApp(
title: 'flutter_responsive_plus Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
// ✅ ResKit.builder at the MaterialApp home level — the entire app
// renders the right layout for the current screen size.
home: ResKit.builder(
mobileBuilder: (ctx) => const MobileApp(),
tabletBuilder: (ctx) => const TabletApp(),
desktopBuilder: (ctx) => const DesktopApp(),
webBuilder: (ctx) => const WebApp(),
desktopLargeBuilder: (ctx) => const UltraWideApp(),
onLayout: (info) {
// ignore: avoid_print
print('[ResKit] Active layout: ${info.layoutType} | '
'Canvas: ${info.activeDesign} | '
'Screen: ${info.screenWidth.toStringAsFixed(0)}×'
'${info.screenHeight.toStringAsFixed(0)}');
},
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// MOBILE APP (Figma canvas: 390 × 844 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class MobileApp extends StatefulWidget {
const MobileApp({super.key});
@override State<MobileApp> createState() => _MobileAppState();
}
class _MobileAppState extends State<MobileApp> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => ResKit.debugPrint());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('📱 Mobile', style: TextStyle(fontSize: 18.sp)),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ActiveCanvasBanner(),
SizedBox(height: 16.h),
_SectionHeader('Multi-Canvas Figma Scaling'),
_MultiCanvasDemo(),
SizedBox(height: 16.h),
_SectionHeader('ResKit.builder slots'),
_BuilderSlotsDemo(),
SizedBox(height: 16.h),
_SectionHeader('All Sizing API'),
_SizingApiDemo(),
SizedBox(height: 16.h),
_SectionHeader('Breakpoints'),
_BreakpointDemo(),
SizedBox(height: 16.h),
_SectionHeader('Platform'),
_PlatformDemo(),
],
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// TABLET APP (Figma canvas: 768 × 1024 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class TabletApp extends StatelessWidget {
const TabletApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('📟 Tablet', style: TextStyle(fontSize: 18.sp)),
backgroundColor: Colors.green.shade700,
foregroundColor: Colors.white,
),
body: Row(
children: [
// Sidebar — only shown on tablet+
Container(
width: 220.w,
color: Colors.green.shade50,
child: Column(
children: [
SizedBox(height: 24.h),
_ActiveCanvasBanner(),
SizedBox(height: 16.h),
_NavItem(icon: Icons.home, label: 'Home'),
_NavItem(icon: Icons.dashboard, label: 'Dashboard'),
_NavItem(icon: Icons.settings, label: 'Settings'),
],
),
),
// Main content
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(24.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SectionHeader('Tablet layout — 768×1024 Figma canvas active'),
_MultiCanvasDemo(),
SizedBox(height: 16.h),
_SectionHeader('2-column grid (tablet breakpoint)'),
_TwoColumnGrid(),
],
),
),
),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// DESKTOP APP (Figma canvas: 1440 × 900 — auto-applied)
// ─────────────────────────────────────────────────────────────────────────────
class DesktopApp extends StatelessWidget {
const DesktopApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// Wide sidebar
Container(
width: 240.w,
color: Colors.purple.shade50,
padding: EdgeInsets.symmetric(vertical: 24.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Text('🖥️ Desktop',
style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.w700,
color: Colors.purple.shade800)),
),
SizedBox(height: 24.h),
_ActiveCanvasBanner(),
SizedBox(height: 16.h),
_NavItem(icon: Icons.home, label: 'Home'),
_NavItem(icon: Icons.analytics, label: 'Analytics'),
_NavItem(icon: Icons.people, label: 'Users'),
_NavItem(icon: Icons.settings, label: 'Settings'),
],
),
),
// Main area — centred & width-capped
Expanded(
child: WebContentWrapper(
maxWidth: ResKit.breakpoints.webMaxContent,
child: SingleChildScrollView(
padding: EdgeInsets.all(32.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SectionHeader('Desktop — 1440×900 Figma canvas active'),
_MultiCanvasDemo(),
SizedBox(height: 16.h),
_SectionHeader('4-column grid (desktop breakpoint)'),
_FourColumnGrid(),
],
),
),
),
),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// WEB APP (Figma canvas: 1280 × 800 — platform override, active on web)
// ─────────────────────────────────────────────────────────────────────────────
class WebApp extends StatelessWidget {
const WebApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('🌐 Web', style: TextStyle(fontSize: 18.sp)),
backgroundColor: Colors.orange.shade700,
foregroundColor: Colors.white,
),
body: WebContentWrapper(
maxWidth: 1280,
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(vertical: 32.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ActiveCanvasBanner(),
SizedBox(height: 16.h),
_SectionHeader('Web layout — 1280×800 canvas (platform override)'),
_MultiCanvasDemo(),
SizedBox(height: 16.h),
_SectionHeader('Fluid 3-column grid'),
_ThreeColumnGrid(),
],
),
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// ULTRA-WIDE APP (desktopLarge — 1920 × 1080)
// ─────────────────────────────────────────────────────────────────────────────
class UltraWideApp extends StatelessWidget {
const UltraWideApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('🖥️🖥️ Ultra-wide Desktop', style: TextStyle(fontSize: 18.sp)),
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
),
body: WebContentWrapper(
maxWidth: 1600,
child: SingleChildScrollView(
padding: EdgeInsets.all(40.r),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_ActiveCanvasBanner(),
SizedBox(height: 20.h),
_SectionHeader('Ultra-wide — desktopLarge slot active'),
_MultiCanvasDemo(),
],
),
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// SHARED DEMO WIDGETS
// ─────────────────────────────────────────────────────────────────────────────
/// Banner showing the currently-active Figma canvas.
class _ActiveCanvasBanner extends StatelessWidget {
@override
Widget build(BuildContext context) {
final design = ResKit.activeDesign;
final type = ResKit.deviceType;
final color = switch (type) {
ResKitDeviceType.mobile => Colors.blue,
ResKitDeviceType.tablet => Colors.green,
ResKitDeviceType.desktop => Colors.purple,
};
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
decoration: BoxDecoration(
color: color.withAlpha(20),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: color.withAlpha(80)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Active Figma Canvas',
style: TextStyle(fontSize: 11.sp, color: color.withAlpha(180),
fontWeight: FontWeight.w600, letterSpacing: 0.5)),
SizedBox(height: 4.h),
Text('${design.width.toStringAsFixed(0)} × ${design.height.toStringAsFixed(0)} dp',
style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.w800, color: color)),
Text('scaleW: ${ResKit.scaleW.toStringAsFixed(3)} '
'scaleH: ${ResKit.scaleH.toStringAsFixed(3)} '
'scale: ${ResKit.scale.toStringAsFixed(3)}',
style: TextStyle(fontSize: 11.sp, color: color.withAlpha(160),
fontFamily: 'monospace')),
],
),
);
}
}
class _MultiCanvasDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final d = ResKit.config.designs;
return _Card(children: [
_Row('activeDesign', '${ResKit.activeDesign}'),
_Row('figmaWidth (active)','${ResKit.figmaWidth.toStringAsFixed(0)}'),
_Row('figmaHeight (active)','${ResKit.figmaHeight.toStringAsFixed(0)}'),
if (d != null) ...[
const Divider(),
Text('Configured canvases:',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
SizedBox(height: 4.h),
if (d.mobile != null) _Row(' mobile', '${d.mobile}'),
if (d.tablet != null) _Row(' tablet', '${d.tablet}'),
if (d.desktop != null) _Row(' desktop', '${d.desktop}'),
if (d.web != null) _Row(' web', '${d.web}'),
if (d.tabletSmall != null) _Row(' tabletSmall', '${d.tabletSmall}'),
if (d.tabletLarge != null) _Row(' tabletLarge', '${d.tabletLarge}'),
if (d.desktopLarge != null) _Row(' desktopLarge', '${d.desktopLarge}'),
],
]);
}
}
class _BuilderSlotsDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _Card(children: [
Text('ResKit.builder() — inline slot test:',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
SizedBox(height: 8.h),
ResKit.builder(
mobile: _SlotChip('mobile', Colors.blue),
tablet: _SlotChip('tablet', Colors.green),
desktop: _SlotChip('desktop', Colors.purple),
web: _SlotChip('web', Colors.orange),
desktopLarge: _SlotChip('desktopLarge', Colors.deepPurple),
desktopXL: _SlotChip('desktopXL', Colors.red),
),
SizedBox(height: 8.h),
Text('context.responsiveBuilder() — context shortcut:',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w600)),
SizedBox(height: 8.h),
context.responsiveBuilder(
mobile: _SlotChip('mobile ctx', Colors.blue),
tablet: _SlotChip('tablet ctx', Colors.green),
desktop: _SlotChip('desktop ctx', Colors.purple),
web: _SlotChip('web ctx', Colors.orange),
),
]);
}
}
class _SlotChip extends StatelessWidget {
const _SlotChip(this.label, this.color);
final String label; final Color color;
@override
Widget build(BuildContext context) => Chip(
label: Text('✅ $label', style: TextStyle(fontSize: 13.sp, color: color)),
backgroundColor: color.withAlpha(20),
side: BorderSide(color: color),
);
}
class _SizingApiDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _Card(children: [
_Row('ResKit.width(200)', ResKit.width(200).toStringAsFixed(2)),
_Row('ResKit.w(200)', ResKit.w(200).toStringAsFixed(2)),
_Row('ResKit.height(100)', ResKit.height(100).toStringAsFixed(2)),
_Row('ResKit.h(100)', ResKit.h(100).toStringAsFixed(2)),
_Row('ResKit.radius(12)', ResKit.radius(12).toStringAsFixed(2)),
_Row('ResKit.sp(16)', ResKit.sp(16).toStringAsFixed(2)),
_Row('ResKit.sw(0.5)', ResKit.sw(0.5).toStringAsFixed(2)),
_Row('ResKit.sh(0.1)', ResKit.sh(0.1).toStringAsFixed(2)),
_Row('200.w (extension)', 200.w.toStringAsFixed(2)),
_Row('100.h (extension)', 100.h.toStringAsFixed(2)),
_Row('12.r (extension)', 12.r.toStringAsFixed(2)),
_Row('16.sp (extension)', 16.sp.toStringAsFixed(2)),
_Row('0.5.sw (extension)', 0.5.sw.toStringAsFixed(2)),
_Row('0.1.sh (extension)', 0.1.sh.toStringAsFixed(2)),
_Row('8.dp (extension)', 8.dp.toStringAsFixed(2)),
_Row('screenWidth', ResKit.screenWidth.toStringAsFixed(1)),
_Row('screenHeight', ResKit.screenHeight.toStringAsFixed(1)),
_Row('statusBarHeight', ResKit.statusBarHeight.toStringAsFixed(1)),
_Row('bottomBarHeight', ResKit.bottomBarHeight.toStringAsFixed(1)),
_Row('keyboardHeight', ResKit.keyboardHeight.toStringAsFixed(1)),
_Row('diagonal', ResKit.diagonal.toStringAsFixed(1)),
]);
}
}
class _BreakpointDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _Card(children: [
_Row('isMobileSmall', '${ResKit.isMobileSmall}'),
_Row('isMobile', '${ResKit.isMobile}'),
_Row('isMobileLarge', '${ResKit.isMobileLarge}'),
_Row('isTabletSmall', '${ResKit.isTabletSmall}'),
_Row('isTablet', '${ResKit.isTablet}'),
_Row('isTabletLarge', '${ResKit.isTabletLarge}'),
_Row('isDesktop', '${ResKit.isDesktop}'),
_Row('isDesktopLarge', '${ResKit.isDesktopLarge}'),
_Row('isDesktopXL', '${ResKit.isDesktopXL}'),
_Row('deviceType', '${ResKit.deviceType}'),
const Divider(),
Text(ResKit.adaptive(
mobile: '📱 Mobile layout active',
tablet: '📟 Tablet layout active',
desktop: '🖥️ Desktop layout active'),
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
]);
}
}
class _PlatformDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _Card(children: [
_Row('platform', '${ResKit.platform}'),
_Row('isAndroid', '${ResKit.isAndroid}'),
_Row('isIOS', '${ResKit.isIOS}'),
_Row('isWeb', '${ResKit.isWeb}'),
_Row('isMacOS', '${ResKit.isMacOS}'),
_Row('isWindows', '${ResKit.isWindows}'),
_Row('isLinux', '${ResKit.isLinux}'),
_Row('isFuchsia', '${ResKit.isFuchsia}'),
_Row('isNativeMobile', '${ResKit.isNativeMobile}'),
_Row('isNativeDesktop', '${ResKit.isNativeDesktop}'),
]);
}
}
// ─── Grid demos ──────────────────────────────────────────────────────────────
class _TwoColumnGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 12.w,
mainAxisSpacing: 12.h,
childAspectRatio: 1.6,
children: List.generate(4, (i) => _GridCard(i + 1)),
);
}
}
class _ThreeColumnGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 16.w,
mainAxisSpacing: 16.h,
childAspectRatio: 1.8,
children: List.generate(6, (i) => _GridCard(i + 1)),
);
}
}
class _FourColumnGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 20.w,
mainAxisSpacing: 20.h,
childAspectRatio: 1.5,
children: List.generate(8, (i) => _GridCard(i + 1)),
);
}
}
class _GridCard extends StatelessWidget {
const _GridCard(this.n);
final int n;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.primaries[n % Colors.primaries.length].withAlpha(30),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: Colors.primaries[n % Colors.primaries.length].withAlpha(80),
),
),
alignment: Alignment.center,
child: Text('Card $n', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600)),
);
}
}
class _NavItem extends StatelessWidget {
const _NavItem({required this.icon, required this.label});
final IconData icon; final String label;
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, size: 22.r),
title: Text(label, style: TextStyle(fontSize: 14.sp)),
dense: true,
);
}
}
// ─── Shared UI helpers ───────────────────────────────────────────────────────
class _SectionHeader extends StatelessWidget {
const _SectionHeader(this.text);
final String text;
@override
Widget build(BuildContext context) => Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Text(text,
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w700,
color: Theme.of(context).colorScheme.primary)),
);
}
class _Card extends StatelessWidget {
const _Card({required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) => Container(
width: double.infinity,
padding: EdgeInsets.all(12.r),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerLowest,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: children),
);
}
class _Row extends StatelessWidget {
const _Row(this.k, this.v);
final String k, v;
@override
Widget build(BuildContext context) => Padding(
padding: EdgeInsets.symmetric(vertical: 2.h),
child: Row(children: [
Expanded(child: Text(k,
style: TextStyle(fontSize: 11.sp, fontFamily: 'monospace',
color: Theme.of(context).colorScheme.onSurfaceVariant))),
Text(v, style: TextStyle(fontSize: 11.sp, fontFamily: 'monospace',
fontWeight: FontWeight.w600)),
]),
);
}