dynamic_layouts 0.6.1
dynamic_layouts: ^0.6.1 copied to clipboard
A lightweight, dependency-free Flutter package for adaptive UI rendering across mobile, tablet, and desktop screens.
import 'package:flutter/material.dart';
import 'package:dynamic_layouts/dynamic_layouts.dart';
void main() {
runApp(const DemoApp());
}
class DemoApp extends StatefulWidget {
const DemoApp({super.key});
@override
State<DemoApp> createState() => _DemoAppState();
}
class _DemoAppState extends State<DemoApp> {
bool _maxContentWidthEnabled = false;
void _toggleMaxWidth(bool value) {
setState(() => _maxContentWidthEnabled = value);
}
@override
Widget build(BuildContext context) {
// Initialize global config for static .w/.h usage, but also use
// AdaptiveScope below for fully reactive widget-tree resizing!
ScreenConfig.init(context, designWidth: 375, designHeight: 812);
// 1. Wrap your app in AdaptiveScope for reactive resizing
return AdaptiveScope(
config: ScreenConfig.watch(context),
// NEW: Demonstrate maxContentWidth clamping
maxContentWidth: _maxContentWidthEnabled ? 800 : null,
child: Builder(
builder: (context) {
final baseLight = ThemeData(
colorSchemeSeed: const Color(0xFF6C5CE7),
useMaterial3: true,
brightness: Brightness.light,
);
final baseDark = ThemeData(
colorSchemeSeed: const Color(0xFF6C5CE7),
useMaterial3: true,
brightness: Brightness.dark,
);
return MaterialApp(
title: 'dynamic_layouts Example',
debugShowCheckedModeBanner: false,
// 2. Add the Debug Overlay
builder: (context, child) => AdaptiveDebugOverlay(child: child!),
// 3. Scale the theme automatically
theme: AdaptiveTheme.scale(context, base: baseLight),
darkTheme: AdaptiveTheme.scale(context, base: baseDark),
themeMode: ThemeMode.system,
home: MainScreen(
isMaxWidthEnabled: _maxContentWidthEnabled,
onToggleMaxWidth: _toggleMaxWidth,
),
);
},
),
);
}
}
// ---------------------------------------------------------------------------
// Main Screen — tab navigation
// ---------------------------------------------------------------------------
class MainScreen extends StatefulWidget {
const MainScreen({
super.key,
required this.isMaxWidthEnabled,
required this.onToggleMaxWidth,
});
final bool isMaxWidthEnabled;
final ValueChanged<bool> onToggleMaxWidth;
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
late final _pages = <Widget>[
const OverviewPage(),
const LayoutDemoPage(),
const CollectionDemoPage(),
const TextDemoPage(),
const ComponentsDemoPage(),
const FormsDemoPage(), // NEW TAB
const AdvancedFormsDemoPage(),
AdvancedDemoPage(
isMaxWidthEnabled: widget.isMaxWidthEnabled,
onToggleMaxWidth: widget.onToggleMaxWidth,
),
];
@override
Widget build(BuildContext context) {
// We NO LONGER need ScreenConfig.init(context) here because we are
// using AdaptiveScope at the root of our app!
// AdaptiveNavigationScaffold auto-switches between BottomNavigationBar
// and NavigationRail depending on screen width.
return AdaptiveNavigationScaffold(
selectedIndex: _currentIndex,
onDestinationSelected: (i) => setState(() => _currentIndex = i),
destinations: const [
AdaptiveNavigationDestination(
icon: Icon(Icons.dashboard),
label: 'Overview',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.view_quilt),
label: 'Layout',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.grid_view),
label: 'Collection',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.text_fields),
label: 'Text',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.widgets),
label: 'Widgets',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.dynamic_form),
label: 'Forms',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.keyboard),
label: 'Inputs',
),
AdaptiveNavigationDestination(
icon: Icon(Icons.auto_fix_high),
label: 'Advanced',
),
],
body: _pages[_currentIndex],
);
}
}
// ---------------------------------------------------------------------------
// Page 1 — Overview: Screen info + extension demo
// ---------------------------------------------------------------------------
class OverviewPage extends StatelessWidget {
const OverviewPage({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final config = ScreenConfig.instance;
final bp = BreakpointHelper.current(config.screenWidth);
return Scaffold(
appBar: AppBar(title: const Text('Overview'), centerTitle: true),
body: SingleChildScrollView(
child: AdaptivePadding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- Screen Info Card ---
_SectionTitle('Screen Information', cs),
const SizedBox(height: 8),
AdaptiveContainer(
padding: const EdgeInsets.all(20),
borderRadius: 16,
color: cs.surfaceContainerHighest,
child: Column(
children: [
_InfoRow(
'Screen Width',
'${config.screenWidth.toStringAsFixed(1)} px',
cs,
),
_InfoRow(
'Screen Height',
'${config.screenHeight.toStringAsFixed(1)} px',
cs,
),
const Divider(),
_InfoRow(
'Scale Width',
config.scaleWidth.toStringAsFixed(3),
cs,
),
_InfoRow(
'Scale Height',
config.scaleHeight.toStringAsFixed(3),
cs,
),
_InfoRow(
'Scale Text',
config.scaleText.toStringAsFixed(3),
cs,
),
const Divider(),
_InfoRow('Breakpoint', bp.name.toUpperCase(), cs),
_InfoRow(
'isMobile',
'${BreakpointHelper.isMobile(config.screenWidth)}',
cs,
),
_InfoRow(
'isTablet',
'${BreakpointHelper.isTablet(config.screenWidth)}',
cs,
),
_InfoRow(
'isDesktop',
'${BreakpointHelper.isDesktop(config.screenWidth)}',
cs,
),
],
),
),
const SizedBox(height: 24),
// --- Extension Demo ---
_SectionTitle('Extension Demo (.w / .h / .sp)', cs),
const SizedBox(height: 8),
_ExtensionDemoBox(cs: cs, widthVal: 300, heightVal: 60),
const SizedBox(height: 12),
_ExtensionDemoBox(cs: cs, widthVal: 200, heightVal: 80),
const SizedBox(height: 12),
_ExtensionDemoBox(cs: cs, widthVal: 150, heightVal: 40),
const SizedBox(height: 24),
// --- Adaptive helpers ---
_SectionTitle('Adaptive Helpers', cs),
const SizedBox(height: 8),
AdaptiveContainer(
padding: const EdgeInsets.all(20),
borderRadius: 16,
color: cs.primaryContainer,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AdaptiveContainer',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: cs.onPrimaryContainer,
),
),
AdaptiveSizedBox(height: 8),
Text(
'This container scales its padding, margin, and '
'border radius automatically based on device size.',
style: TextStyle(
fontSize: 14.sp,
color: cs.onPrimaryContainer,
),
),
],
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: AdaptiveContainer(
padding: const EdgeInsets.all(16),
borderRadius: 12,
color: cs.secondaryContainer,
height: 80,
child: Center(
child: Text(
'AdaptiveSizedBox\n& Padding',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: cs.onSecondaryContainer,
),
),
),
),
),
AdaptiveSizedBox(width: 12),
Expanded(
child: AdaptiveContainer(
padding: const EdgeInsets.all(16),
borderRadius: 12,
color: cs.tertiaryContainer,
height: 80,
child: Center(
child: Text(
'All scaled\nautomatically',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: cs.onTertiaryContainer,
),
),
),
),
),
],
),
],
),
),
),
);
}
}
// ---------------------------------------------------------------------------
// Page 2 — AdaptiveLayout demo
// ---------------------------------------------------------------------------
class LayoutDemoPage extends StatelessWidget {
const LayoutDemoPage({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('AdaptiveLayout'), centerTitle: true),
body: AdaptivePadding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionTitle('Resize to see layout switch', cs),
const SizedBox(height: 12),
Expanded(
child: AdaptiveLayout(
mobile: _LayoutVariant(
icon: Icons.phone_android,
label: '📱 Mobile Layout',
subtitle: 'Single column, compact spacing',
color: cs.primaryContainer,
textColor: cs.onPrimaryContainer,
),
tablet: _LayoutVariant(
icon: Icons.tablet_mac,
label: '📋 Tablet Layout',
subtitle: 'Two-column grid, medium spacing',
color: cs.secondaryContainer,
textColor: cs.onSecondaryContainer,
),
desktop: _LayoutVariant(
icon: Icons.desktop_mac,
label: '🖥️ Desktop Layout',
subtitle: 'Multi-column, wide spacing',
color: cs.tertiaryContainer,
textColor: cs.onTertiaryContainer,
),
),
),
const SizedBox(height: 16),
AdaptiveContainer(
padding: const EdgeInsets.all(16),
borderRadius: 12,
color: cs.surfaceContainerHighest,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Breakpoint Thresholds',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: cs.onSurface,
),
),
const SizedBox(height: 8),
_ThresholdRow('Mobile', '< 600 px', cs.primary, cs),
_ThresholdRow('Tablet', '600 – 1024 px', cs.secondary, cs),
_ThresholdRow('Desktop', '> 1024 px', cs.tertiary, cs),
],
),
),
],
),
),
);
}
}
// ---------------------------------------------------------------------------
// Page 3 — AdaptiveCollectionView demo
// ---------------------------------------------------------------------------
class CollectionDemoPage extends StatelessWidget {
const CollectionDemoPage({super.key});
static const _colors = [
Color(0xFF6C5CE7),
Color(0xFF00B894),
Color(0xFFFF7675),
Color(0xFFFDAA5E),
Color(0xFF74B9FF),
Color(0xFFE17055),
Color(0xFFA29BFE),
Color(0xFF55EFC4),
Color(0xFFFF6B81),
Color(0xFFFFD93D),
Color(0xFF0984E3),
Color(0xFFD63031),
];
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('AdaptiveCollectionView'),
centerTitle: true,
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AdaptivePadding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SectionTitle('List ↔ Grid (auto switch)', cs),
const SizedBox(height: 4),
Text(
'Small screens → ListView • Medium/Large → GridView',
style: TextStyle(fontSize: 13.sp, color: cs.onSurfaceVariant),
),
],
),
),
Expanded(
child: AdaptiveCollectionView(
itemCount: 12,
gridSpacing: 12,
listSpacing: 10,
childAspectRatio: 1.3,
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h),
itemBuilder: (context, index) {
final color = _colors[index % _colors.length];
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [color, color.withAlpha(180)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16.w),
boxShadow: [
BoxShadow(
color: color.withAlpha(60),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.auto_awesome,
color: Colors.white,
size: 28.sp,
),
SizedBox(height: 8.h),
Text(
'Item ${index + 1}',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
);
},
),
),
],
),
);
}
}
// ---------------------------------------------------------------------------
// Page 4 — AdaptiveText demo
// ---------------------------------------------------------------------------
class TextDemoPage extends StatelessWidget {
const TextDemoPage({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('AdaptiveText'), centerTitle: true),
body: SingleChildScrollView(
child: AdaptivePadding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionTitle('Scaled Font Sizes', cs),
const SizedBox(height: 12),
..._buildFontSamples(cs),
const SizedBox(height: 24),
_SectionTitle('Max Scale Factor Clamping', cs),
const SizedBox(height: 12),
AdaptiveContainer(
padding: const EdgeInsets.all(20),
borderRadius: 16,
color: cs.surfaceContainerHighest,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AdaptiveText(
'maxScaleFactor: 1.0 (no scaling up)',
baseFontSize: 16,
maxScaleFactor: 1.0,
),
AdaptiveSizedBox(height: 12),
const AdaptiveText(
'maxScaleFactor: 1.5',
baseFontSize: 16,
maxScaleFactor: 1.5,
),
AdaptiveSizedBox(height: 12),
const AdaptiveText(
'maxScaleFactor: 2.0 (default)',
baseFontSize: 16,
maxScaleFactor: 2.0,
),
AdaptiveSizedBox(height: 12),
const AdaptiveText(
'maxScaleFactor: 3.0',
baseFontSize: 16,
maxScaleFactor: 3.0,
),
],
),
),
const SizedBox(height: 24),
_SectionTitle('System Scaling Control', cs),
const SizedBox(height: 12),
AdaptiveContainer(
padding: const EdgeInsets.all(20),
borderRadius: 16,
color: cs.secondaryContainer,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AdaptiveText(
'respectSystemScaling: true (default)',
baseFontSize: 14,
respectSystemScaling: true,
style: TextStyle(color: cs.onSecondaryContainer),
),
AdaptiveSizedBox(height: 12),
AdaptiveText(
'respectSystemScaling: false',
baseFontSize: 14,
respectSystemScaling: false,
style: TextStyle(color: cs.onSecondaryContainer),
),
AdaptiveSizedBox(height: 12),
Text(
'When false, system text scaling is ignored.\n'
'Useful for UI labels that must stay fixed.',
style: TextStyle(
fontSize: 12.sp,
color: cs.onSecondaryContainer.withAlpha(180),
),
),
],
),
),
],
),
),
),
);
}
List<Widget> _buildFontSamples(ColorScheme cs) {
final sizes = [10.0, 14.0, 18.0, 24.0, 32.0, 42.0];
return sizes.map((size) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: AdaptiveContainer(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
borderRadius: 12,
color: cs.surfaceContainerHighest,
child: Row(
children: [
SizedBox(
width: 80,
child: Text(
'${size.toInt()} sp',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: cs.primary,
),
),
),
Expanded(
child: AdaptiveText(
'The quick brown fox',
baseFontSize: size,
style: TextStyle(color: cs.onSurface),
),
),
],
),
),
);
}).toList();
}
}
// ===========================================================================
// Shared helper widgets
// ===========================================================================
class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.text, this.cs);
final String text;
final ColorScheme cs;
@override
Widget build(BuildContext context) {
return AdaptiveText(
text,
baseFontSize: 22,
style: TextStyle(fontWeight: FontWeight.bold, color: cs.primary),
);
}
}
class _InfoRow extends StatelessWidget {
const _InfoRow(this.label, this.value, this.cs);
final String label;
final String value;
final ColorScheme cs;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 3.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(fontSize: 14.sp, color: cs.onSurfaceVariant),
),
Text(
value,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: cs.onSurface,
),
),
],
),
);
}
}
class _ExtensionDemoBox extends StatelessWidget {
const _ExtensionDemoBox({
required this.cs,
required this.widthVal,
required this.heightVal,
});
final ColorScheme cs;
final double widthVal;
final double heightVal;
@override
Widget build(BuildContext context) {
return Container(
width: widthVal.w,
height: heightVal.h,
decoration: BoxDecoration(
gradient: LinearGradient(colors: [cs.primary, cs.tertiary]),
borderRadius: BorderRadius.circular(12.w),
),
alignment: Alignment.center,
child: Text(
'${widthVal.toInt()}.w × ${heightVal.toInt()}.h',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: cs.onPrimary,
),
),
);
}
}
class _LayoutVariant extends StatelessWidget {
const _LayoutVariant({
required this.icon,
required this.label,
required this.subtitle,
required this.color,
required this.textColor,
});
final IconData icon;
final String label;
final String subtitle;
final Color color;
final Color textColor;
@override
Widget build(BuildContext context) {
return AdaptiveContainer(
padding: const EdgeInsets.all(32),
borderRadius: 24,
color: color,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 48.sp, color: textColor),
AdaptiveSizedBox(height: 16),
AdaptiveText(
label,
baseFontSize: 24,
style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
),
AdaptiveSizedBox(height: 8),
AdaptiveText(
subtitle,
baseFontSize: 14,
style: TextStyle(color: textColor.withAlpha(200)),
),
],
),
),
);
}
}
class _ThresholdRow extends StatelessWidget {
const _ThresholdRow(this.label, this.range, this.dotColor, this.cs);
final String label;
final String range;
final Color dotColor;
final ColorScheme cs;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
children: [
Container(
width: 10.w,
height: 10.w,
decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle),
),
SizedBox(width: 8.w),
Text(
'$label ',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: cs.onSurface,
),
),
Text(
range,
style: TextStyle(fontSize: 13.sp, color: cs.onSurfaceVariant),
),
],
),
);
}
}
// ---------------------------------------------------------------------------
// Page 5 — Components (AdaptiveWrap, Spacing, Modal, Image, Value)
// ---------------------------------------------------------------------------
class ComponentsDemoPage extends StatelessWidget {
const ComponentsDemoPage({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
// 1. AdaptiveValue demo
final columnsCount = AdaptiveValue<int>(
mobile: 1,
tablet: 2,
desktop: 3,
).resolve(context);
return Scaffold(
appBar: AppBar(
title: const Text('Adaptive Components'),
centerTitle: true,
),
body: SingleChildScrollView(
child: AdaptivePadding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionTitle('AdaptiveValue<int>', cs),
Text(
'Columns count: $columnsCount (resolves differently per breakpoint)',
),
const AdaptiveSpacing(16),
_SectionTitle('AdaptiveWrap & AdaptiveSpacing', cs),
AdaptiveContainer(
padding: const EdgeInsets.all(16),
color: cs.surfaceContainerHighest,
borderRadius: 12,
child: AdaptiveWrap(
spacing: context.w(10), // Use context.w for responsive gap
children: [
_ColoredBox(
'Item 1',
cs.primaryContainer,
cs.onPrimaryContainer,
),
_ColoredBox(
'Item 2',
cs.secondaryContainer,
cs.onSecondaryContainer,
),
_ColoredBox(
'Item 3',
cs.tertiaryContainer,
cs.onTertiaryContainer,
),
],
),
),
const AdaptiveSpacing(24), // Notice no height/width needed!
_SectionTitle('showAdaptiveModal', cs),
ElevatedButton(
onPressed: () {
showAdaptiveModal(
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.check_circle, size: 64, color: cs.primary),
const AdaptiveSpacing(16),
Text(
'Responsive Modal!',
style: Theme.of(context).textTheme.headlineSmall,
),
const AdaptiveSpacing(8),
const Text(
'BottomSheet on Mobile, Dialog on Desktop.',
),
],
),
),
);
},
child: const Text('Click me!'),
),
const AdaptiveSpacing(24),
_SectionTitle('AdaptiveImage', cs),
const Text(
'Loads different assets based on breakpoints to save bandwidth.',
),
const AdaptiveSpacing(8),
AdaptiveContainer(
height: 150,
color: cs.onInverseSurface,
borderRadius: 12,
child: const AdaptiveImage(
mobile: NetworkImage(
'https://via.placeholder.com/300x150.png?text=Mobile+Image',
),
tablet: NetworkImage(
'https://via.placeholder.com/600x150.png?text=Tablet+Image',
),
desktop: NetworkImage(
'https://via.placeholder.com/1200x150.png?text=Desktop+Image',
),
fit: BoxFit.cover,
),
),
const AdaptiveSpacing(24),
_SectionTitle('AdaptiveGrid', cs),
const Text(
'Auto-calculates columns: 1 (Mobile), 2 (Tablet), 4 (Desktop)',
),
const AdaptiveSpacing(8),
AdaptiveContainer(
height: 300,
borderRadius: 12,
color: cs.surfaceContainerHighest,
child: AdaptiveGrid(
itemCount: 12,
crossAxisCount: const AdaptiveValue<int>(
mobile: 1,
tablet: 2,
desktop: 4,
),
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: cs.primary.withAlpha(200),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Text(
'Item ${index + 1}',
style: TextStyle(
color: cs.onPrimary,
fontWeight: FontWeight.bold,
),
),
);
},
),
),
],
),
),
),
);
}
}
class _ColoredBox extends StatelessWidget {
final String label;
final Color bg;
final Color fg;
const _ColoredBox(this.label, this.bg, this.fg);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: context.w(24),
vertical: context.h(16),
),
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: TextStyle(color: fg, fontWeight: FontWeight.bold),
),
);
}
}
// ---------------------------------------------------------------------------
// Page 6 — Advanced (AnimatedLayout, MaxWidth, ScaleBasis)
// ---------------------------------------------------------------------------
class AdvancedDemoPage extends StatelessWidget {
const AdvancedDemoPage({
super.key,
required this.isMaxWidthEnabled,
required this.onToggleMaxWidth,
});
final bool isMaxWidthEnabled;
final ValueChanged<bool> onToggleMaxWidth;
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('Advanced Layouts'), centerTitle: true),
body: SingleChildScrollView(
child: AdaptivePadding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionTitle('Global Width Clamping', cs),
const Text(
'Prevent your UI from stretching excessively on ultra-wide screens '
'by setting a maximum content width in AdaptiveScope.',
),
const SizedBox(height: 8),
SwitchListTile(
title: const Text('Enable Max Width (800px)'),
value: isMaxWidthEnabled,
onChanged: onToggleMaxWidth,
tileColor: cs.surfaceContainerHighest,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
const AdaptiveSpacing(24),
_SectionTitle('AnimatedAdaptiveLayout', cs),
const Text(
'Smooth transitions when crossing breakpoints. Try resizing '
'your browser or switching landscape/portrait.',
),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: AnimatedAdaptiveLayout(
duration: const Duration(milliseconds: 500),
switchInCurve: Curves.easeInOutBack,
mobile: _InfoBox(
'Mobile View',
cs.primaryContainer,
cs.onPrimaryContainer,
),
tablet: _InfoBox(
'Tablet View',
cs.secondaryContainer,
cs.onSecondaryContainer,
),
desktop: _InfoBox(
'Desktop View',
cs.tertiaryContainer,
cs.onTertiaryContainer,
),
),
),
const AdaptiveSpacing(24),
_SectionTitle('Auto-Calculating Grid', cs),
const Text(
'Instead of fixed column counts, use maxColumnWidth to let the '
'grid automatically fill the available space.',
),
const SizedBox(height: 8),
AdaptiveContainer(
height: 300,
color: cs.surfaceContainerHighest,
borderRadius: 12,
child: AdaptiveGrid(
itemCount: 20,
maxColumnWidth: 150, // Auto-column calculation!
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: const EdgeInsets.all(8),
itemBuilder: (context, index) => Container(
decoration: BoxDecoration(
color: cs.secondary.withAlpha(150),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Text(
'${index + 1}',
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
),
],
),
),
),
);
}
}
class _InfoBox extends StatelessWidget {
const _InfoBox(this.label, this.bg, this.fg);
final String label;
final Color bg;
final Color fg;
@override
Widget build(BuildContext context) {
return Container(
key: ValueKey(label), // Important for AnimatedSwitcher to detect change
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Text(
label,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: fg,
),
),
),
);
}
}
// ---------------------------------------------------------------------------
// Page 7 — Forms & Master Detail (Responsive Forms & Fluid Scaling)
// ---------------------------------------------------------------------------
class FormsDemoPage extends StatelessWidget {
const FormsDemoPage({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('Forms & Data'), centerTitle: true),
body: AdaptiveMasterDetail<String>(
breakpoint: BreakpointConfig.material3().smallMax,
initialSelection: 'User Profile',
masterBuilder: (context, onSelect) => ListView(
children: [
ListTile(
leading: Icon(Icons.person, color: cs.primary),
title: const Text('User Profile'),
onTap: () => onSelect('User Profile'),
),
ListTile(
leading: Icon(Icons.settings, color: cs.primary),
title: const Text('Security Settings'),
onTap: () => onSelect('Security Settings'),
),
ListTile(
leading: Icon(Icons.notifications, color: cs.primary),
title: const Text('Notifications'),
onTap: () => onSelect('Notifications'),
),
],
),
detailBuilder: (context, selectedItem, {onBack}) {
if (selectedItem == null) {
return const Center(child: Text('Select an item from the list.'));
}
return SingleChildScrollView(
child: AdaptivePadding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (onBack != null)
Align(
alignment: Alignment.centerLeft,
child: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: onBack,
),
),
// Fluid Text Scaling Demonstration
Text(
selectedItem,
// font size fluidly scales between 24 (minWidth 375) and 40 (maxWidth 1024)
style: TextStyle(
fontSize: context.fluid(24, 40),
fontWeight: FontWeight.bold,
color: cs.primary,
),
),
const AdaptiveSpacing(24),
// Responsive Form Row Demonstration
AdaptiveContainer(
padding: const EdgeInsets.all(24),
borderRadius: 16,
color: cs.surfaceContainerHighest,
child: Column(
children: [
const AdaptiveFormRow(
label: Text(
'Full Name',
style: TextStyle(fontWeight: FontWeight.bold),
),
input: TextField(
decoration: InputDecoration(
hintText: 'Enter your name',
),
),
),
const AdaptiveSpacing(16),
const AdaptiveFormRow(
label: Text(
'Email Address',
style: TextStyle(fontWeight: FontWeight.bold),
),
input: TextField(
decoration: InputDecoration(
hintText: 'Enter your email',
),
),
),
const AdaptiveSpacing(16),
const AdaptiveFormRow(
label: Text(
'Phone Number',
style: TextStyle(fontWeight: FontWeight.bold),
),
input: TextField(
decoration: InputDecoration(
hintText: 'Enter your phone',
),
),
),
const AdaptiveSpacing(24),
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
onPressed: () {},
child: const Text('Save Changes'),
),
),
],
),
),
],
),
),
);
},
),
);
}
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Page 8 — Advanced Forms Demo Page
// ---------------------------------------------------------------------------
class AdvancedFormsDemoPage extends StatelessWidget {
const AdvancedFormsDemoPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Adaptive Forms')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Adaptive Form Elements',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 8),
const Text(
'Resize the window to see AdaptiveFormRow stack vertically '
'on mobile and align horizontally on desktop. AdaptiveTextField '
'will also become more compact on desktop.',
),
const SizedBox(height: 32),
// 1. Email Field
AdaptiveFormRow(
label: const Text('Email Address'),
input: const AdaptiveTextField.form(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'user@example.com',
),
keyboardType: TextInputType.emailAddress,
),
),
const SizedBox(height: 24),
// 2. Password Field
AdaptiveFormRow(
label: const Text('Password'),
input: const AdaptiveTextField.form(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter your password',
),
obscureText: true,
),
),
const SizedBox(height: 32),
// 3. Submit Button
AdaptiveFormSubmit(
child: FilledButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form Submitted!')),
);
},
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 12.0,
),
child: Text('Submit Form'),
),
),
),
],
),
),
),
);
}
}