dashboards_responsive 1.0.3
dashboards_responsive: ^1.0.3 copied to clipboard
A Flutter package for creating responsive, grid-based dashboards with draggable and resizable chart tiles.
import 'package:flutter/material.dart';
import 'package:dashboards_responsive/dashboards_responsive.dart';
void main() {
runApp(const DashboardDemoApp());
}
class DashboardDemoApp extends StatelessWidget {
const DashboardDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dashboard Grid Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
home: const DashboardDemoPage(),
);
}
}
class DashboardDemoPage extends StatefulWidget {
const DashboardDemoPage({super.key});
@override
State<DashboardDemoPage> createState() => _DashboardDemoPageState();
}
class _DashboardDemoPageState extends State<DashboardDemoPage> {
late final HierarchicalDashboardControllerImpl controller;
bool _editMode = false;
// Override parameters for testing responsive behavior
double? _widthOverride;
String? _breakpointOverride;
int? _columnsOverride;
final config = DashboardConfig(
columns: 12,
columnsByBreakpoint: {
Breakpoint.xs: 4,
Breakpoint.sm: 8,
Breakpoint.md: 12,
Breakpoint.lg: 12,
Breakpoint.xl: 12,
},
rowHeight: 40,
gutter: 8,
padding: EdgeInsets.all(8),
breakpoints: DashboardBreakpoints(
breakpoints: {
Breakpoint.xs: 0,
Breakpoint.sm: 600,
Breakpoint.md: 900,
Breakpoint.lg: 1200,
Breakpoint.xl: 1536,
},
),
compactionMode: CompactionMode.vertical,
);
@override
void initState() {
super.initState();
// Create a simple layout using the new hierarchical approach
final layout = DashboardLayout(
breakpointLayouts: {
Breakpoint.xs: BreakpointLayout(
widgets: {
'sales': WidgetPosition(x: 0, y: 0, w: 4, h: 6),
'traffic': WidgetPosition(x: 0, y: 6, w: 4, h: 6),
'kpi': WidgetPosition(x: 0, y: 12, w: 4, h: 3),
'map': WidgetPosition(x: 0, y: 15, w: 4, h: 6),
'table': WidgetPosition(x: 0, y: 21, w: 4, h: 6),
},
),
Breakpoint.sm: BreakpointLayout(
widgets: {
'sales': WidgetPosition(x: 0, y: 0, w: 4, h: 6),
'traffic': WidgetPosition(x: 4, y: 0, w: 4, h: 6),
'kpi': WidgetPosition(x: 0, y: 6, w: 8, h: 3),
'map': WidgetPosition(x: 0, y: 9, w: 8, h: 6),
'table': WidgetPosition(x: 0, y: 15, w: 8, h: 6),
},
),
Breakpoint.md: BreakpointLayout(
widgets: {
'sales': WidgetPosition(x: 0, y: 0, w: 4, h: 6),
'traffic': WidgetPosition(x: 4, y: 0, w: 6, h: 6),
'kpi': WidgetPosition(x: 10, y: 0, w: 6, h: 3),
'map': WidgetPosition(x: 0, y: 6, w: 6, h: 6),
'table': WidgetPosition(x: 6, y: 6, w: 6, h: 6),
},
),
Breakpoint.lg: BreakpointLayout(
widgets: {
'sales': WidgetPosition(x: 0, y: 0, w: 3, h: 6),
'traffic': WidgetPosition(x: 3, y: 0, w: 4, h: 6),
'kpi': WidgetPosition(x: 7, y: 0, w: 5, h: 3),
'map': WidgetPosition(x: 0, y: 6, w: 6, h: 6),
'table': WidgetPosition(x: 6, y: 6, w: 6, h: 6),
},
),
},
widgetIds: {'sales', 'traffic', 'kpi', 'map', 'table'},
);
controller = HierarchicalDashboardControllerImpl(initial: layout);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard Grid Demo'),
actions: [
IconButton(
icon: Icon(_editMode ? Icons.visibility : Icons.edit),
onPressed: () {
setState(() {
_editMode = !_editMode;
});
},
tooltip: _editMode ? 'Switch to View Mode' : 'Switch to Edit Mode',
),
IconButton(
icon: const Icon(Icons.settings),
onPressed: () => _showOverrideDialog(),
tooltip: 'Screen Size Override',
),
IconButton(
icon: const Icon(Icons.save_alt),
onPressed: () {
final json = controller.layout.value.toJson();
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Current Layout JSON'),
content: SingleChildScrollView(child: Text(json.toString())),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
},
),
],
),
body: SingleChildScrollView(
child: DashboardCanvas(
config: config,
controller: controller,
enableDrag: true,
enableResizeHorizontal: true,
enableResizeVertical: true,
editMode: _editMode,
availableWidthOverride: _widthOverride,
breakpointOverride: _breakpointOverride,
effectiveColumnsOverride: _columnsOverride,
widgetBuilder: (context, widgetId, position) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Text(
widgetId,
style: Theme.of(context).textTheme.titleMedium,
),
),
);
},
),
),
);
}
void _showOverrideDialog() {
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) {
return AlertDialog(
title: const Text('Screen Size Override'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Width override
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Width (px)',
hintText: 'e.g., 800',
),
keyboardType: TextInputType.number,
onChanged: (value) {
_widthOverride = value.isEmpty
? null
: double.tryParse(value);
},
controller: TextEditingController(
text: _widthOverride?.toString() ?? '',
),
),
),
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setDialogState(() {
_widthOverride = null;
});
},
),
],
),
const SizedBox(height: 16),
// Breakpoint override
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Breakpoint',
),
value: _breakpointOverride,
items: const [
DropdownMenuItem(value: null, child: Text('Auto')),
DropdownMenuItem(value: 'xs', child: Text('XS')),
DropdownMenuItem(value: 'sm', child: Text('SM')),
DropdownMenuItem(value: 'md', child: Text('MD')),
DropdownMenuItem(value: 'lg', child: Text('LG')),
DropdownMenuItem(value: 'xl', child: Text('XL')),
],
onChanged: (value) {
setDialogState(() {
_breakpointOverride = value;
});
},
),
),
],
),
const SizedBox(height: 16),
// Columns override
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
labelText: 'Columns',
hintText: 'e.g., 8',
),
keyboardType: TextInputType.number,
onChanged: (value) {
_columnsOverride = value.isEmpty
? null
: int.tryParse(value);
},
controller: TextEditingController(
text: _columnsOverride?.toString() ?? '',
),
),
),
IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setDialogState(() {
_columnsOverride = null;
});
},
),
],
),
],
),
actions: [
TextButton(
onPressed: () {
setState(() {
_widthOverride = null;
_breakpointOverride = null;
_columnsOverride = null;
});
Navigator.pop(context);
},
child: const Text('Reset'),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
setState(() {});
Navigator.pop(context);
},
child: const Text('Apply'),
),
],
);
},
),
);
}
}