xtal_dashboard 0.3.1
xtal_dashboard: ^0.3.1 copied to clipboard
A grid-based dashboard layout library with drag-and-drop reordering, resizing, and physics-based animations.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:xtal_dashboard/xtal_dashboard.dart';
void main() {
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xtal_dashboard Demo',
theme: ThemeData.light(useMaterial3: true),
debugShowCheckedModeBanner: false,
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
bool _collisionEnabled = true;
bool _editingEnabled = true;
bool _shelfEnabled = true;
bool _resetSizeOnShelf = false;
int _frozenRows = 0;
late List<DashboardItemData> _items;
late List<DashboardItemData> _shelfItems;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_items = _createInitialItems();
_shelfItems = _createInitialShelfItems();
}
// ---- Initial shelf items ----
static const _shelfItemConfigs = <(String, int, int, int, String, String)>[
// (id, columnSpan, rowSpan, colorValue, title, description)
(
'S1',
1,
1,
0xFFEF5350,
'CPU',
'Current load average across all cores is 2.4 with peak utilization at 87%.',
), // Red 400
(
'S2',
2,
1,
0xFF7E57C2,
'Recent Activity Log',
'User admin@example.com deployed v3.2.1 to production at 14:32 UTC. Database migration completed successfully with 12 tables altered.',
), // Deep Purple 400
(
'S3',
1,
1,
0xFF29B6F6,
'System Performance Monitor',
'Monitoring 48 services across 3 regions. All health checks passing. Memory usage trending upward — consider scaling.',
), // Light Blue 400
];
List<DashboardItemData> _createInitialShelfItems() {
return _shelfItemConfigs.map((config) {
final (id, colSpan, rowSpan, colorValue, title, description) = config;
final color = Color(colorValue);
return DashboardItemData(
gridItem: GridItem(
id: id,
column: 0,
row: 0,
columnSpan: colSpan,
rowSpan: rowSpan,
),
builder: (context, isBeingDragged) =>
_buildCard(color, id, description),
title: title,
);
}).toList();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// ---- Initial grid layout (4 columns, 8 items, mixed spans) ----
//
// Col: 0 1 2 3
// Row 0: [ A (2x2) ] [ B (2x1) ]
// Row 1: [ ] [ C ] [ D ]
// Row 2: [ E ] [ F (2x1) ] [ G ]
// Row 3: [ H (3x1) ] [ ]
static const _itemConfigs = <(String, int, int, int, int, int, String, String)>[
// (id, column, row, columnSpan, rowSpan, colorValue, title, description)
(
'A',
0,
0,
2,
2,
0xFF42A5F5,
'Overview Dashboard',
'System uptime: 99.97% over the last 30 days. Active users: 1,284 concurrent sessions. Total API calls today: 3.2M with an average response time of 142ms. No critical incidents reported in the past 72 hours.',
), // Blue 400
(
'B',
2,
0,
2,
1,
0xFF26A69A,
'Network',
'Inbound traffic: 847 Mbps, Outbound: 1.2 Gbps. Packet loss rate is 0.003% across all edge nodes. DNS resolution latency averaging 12ms globally.',
), // Teal 400
(
'C',
2,
1,
1,
1,
0xFFAB47BC,
'Mem',
'Heap: 4.2 GB / 8 GB used. GC pauses under 15ms. RSS stable at 5.1 GB.',
), // Purple 300
(
'D',
3,
1,
1,
1,
0xFFEC407A,
'IO',
'Disk IOPS: 12.4k read, 8.7k write. Queue depth: 3. Avg latency: 0.8ms.',
), // Pink 300
(
'E',
0,
2,
1,
1,
0xFFFFCA28,
'Alerts',
'3 warnings, 0 critical. Last alert: high CPU on node-7 at 09:41 UTC.',
), // Amber 400
(
'F',
1,
2,
2,
1,
0xFFFFA726,
'Request Throughput',
'Current: 24.8k req/s (peak: 31.2k at 14:00 UTC). Error rate: 0.12%. Top endpoint: /api/v2/users accounting for 38% of total traffic. P95 latency: 89ms.',
), // Orange 400
(
'G',
3,
2,
1,
1,
0xFF66BB6A,
'Up',
'All 48 services healthy. Last restart: 6d ago. Zero downtime deploys: 142.',
), // Green 400
(
'H',
0,
3,
3,
1,
0xFF5C6BC0,
'Latency Distribution (p50 / p95 / p99)',
'p50: 23ms — p95: 89ms — p99: 214ms. Tail latency improved 18% after connection pool tuning last Thursday. Outliers traced to cold-start Lambda invocations in us-west-2.',
), // Indigo 300
];
List<DashboardItemData> _createInitialItems() {
return _itemConfigs.map((config) {
final (id, col, row, colSpan, rowSpan, colorValue, title, description) =
config;
final color = Color(colorValue);
return DashboardItemData(
gridItem: GridItem(
id: id,
column: col,
row: row,
columnSpan: colSpan,
rowSpan: rowSpan,
),
builder: (context, isBeingDragged) =>
_buildCard(color, id, description),
title: title,
);
}).toList();
}
// ---- Card widget with visual patterns for FittedBox verification ----
static Widget _buildCard(Color color, String label, String description) {
final darker = Color.lerp(color, const Color(0xFF000000), 0.3)!;
return DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [color, darker],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0x30FFFFFF), width: 1.5),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: const TextStyle(
color: Color(0xFFFFFFFF),
fontSize: 22,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
),
),
const SizedBox(height: 4),
const DecoratedBox(
decoration: BoxDecoration(color: Color(0x30FFFFFF)),
child: SizedBox(height: 1, width: double.infinity),
),
const SizedBox(height: 8),
Expanded(
child: Text(
description,
style: const TextStyle(
color: Color(0xCCFFFFFF),
fontSize: 11,
fontWeight: FontWeight.w400,
height: 1.4,
decoration: TextDecoration.none,
),
overflow: TextOverflow.fade,
),
),
],
),
),
);
}
// ---- Custom ghost: blue border + glow ----
static Widget _buildGhost(BuildContext context, Widget child, Size size) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF2196F3), width: 3),
boxShadow: const [
BoxShadow(color: Color(0x402196F3), blurRadius: 16, spreadRadius: 4),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Opacity(opacity: 0.85, child: child),
),
);
}
// ---- Dashboard widget (shared between frozen and non-frozen modes) ----
Widget _buildDashboard() {
return XtalDashboard(
scrollController: _scrollController,
columns: 4,
items: _items,
gap: 12,
padding: const EdgeInsets.all(12),
frozenRows: _frozenRows,
collisionDetectionEnabled: _collisionEnabled,
editingEnabled: _editingEnabled,
animationConfig: const AnimationConfig(stiffness: 350, damping: 22),
gestureConfig: const GestureConfig(
resizeHandleSize: 28,
autoScrollConfig: AutoScrollConfig(),
),
resizeConstraints: const ResizeConstraints(
maxColumnSpan: 4,
maxRowSpan: 4,
),
shelfConfig: _shelfEnabled
? ShelfConfig(
position: ShelfPosition.right,
showTitles: true,
resetSizeOnShelf: _resetSizeOnShelf,
borderRadius: BorderRadius.circular(12),
backgroundColor: const Color(0x0D000000),
dropTargetColor: const Color(0x262196F3),
gapFromGrid: 16,
hoverOpenDelay: Duration.zero,
autoCloseDelay: const Duration(seconds: 3),
)
: null,
initialShelfItems: _shelfItems,
ghostBuilder: _buildGhost,
onItemsChanged: (items) {
setState(() => _items = items);
},
onShelfChanged: (shelfItems) {
setState(() => _shelfItems = shelfItems);
},
);
}
// ---- Build ----
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('xtal_dashboard (shelf: ${_shelfItems.length})'),
actions: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Edit'),
Switch.adaptive(
value: _editingEnabled,
onChanged: (value) {
setState(() => _editingEnabled = value);
},
),
const SizedBox(width: 8),
const Text('Shelf'),
Switch.adaptive(
value: _shelfEnabled,
onChanged: (value) {
setState(() => _shelfEnabled = value);
},
),
const SizedBox(width: 8),
const Text('Collision'),
Switch.adaptive(
value: _collisionEnabled,
onChanged: (value) {
setState(() => _collisionEnabled = value);
},
),
const SizedBox(width: 8),
const Text('Reset'),
Switch.adaptive(
value: _resetSizeOnShelf,
onChanged: (value) {
setState(() => _resetSizeOnShelf = value);
},
),
const SizedBox(width: 8),
const Text('Frozen'),
Switch.adaptive(
value: _frozenRows > 0,
onChanged: (value) {
setState(() => _frozenRows = value ? 2 : 0);
},
),
],
),
),
],
),
body: SafeArea(
child: _frozenRows > 0
? Padding(
padding: const EdgeInsets.all(12),
child: _buildDashboard(),
)
: SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(12),
child: _buildDashboard(),
),
),
);
}
}