fassad_ui 0.2.0
fassad_ui: ^0.2.0 copied to clipboard
Flutter widgets and HTTP client for querying dblm fassad templates — render database results as charts and tables.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:fassad_ui/fassad_ui.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fassad UI Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, brightness: Brightness.light),
darkTheme: ThemeData(colorSchemeSeed: Colors.indigo, brightness: Brightness.dark),
themeMode: ThemeMode.system,
home: const FassadDemoPage(),
);
}
}
class FassadDemoPage extends StatefulWidget {
const FassadDemoPage({super.key});
@override
State<FassadDemoPage> createState() => _FassadDemoPageState();
}
class _FassadDemoPageState extends State<FassadDemoPage> {
final _client = FassadClient(
baseUrl: 'http://localhost:3000',
apiKey: 'your-secret-api-key',
);
final _cache = FassadCache();
Stream<FassadCachedResult>? _stream;
FassadChartType _chartType = FassadChartType.bar;
void _runFassad() {
setState(() {
_stream = _client.runWithCache('user-count', cache: _cache);
});
}
@override
void dispose() {
_client.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('Fassad UI Demo')),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 12,
runSpacing: 12,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
ElevatedButton(
onPressed: _runFassad,
child: const Text('Run user-count'),
),
SegmentedButton<FassadChartType>(
segments: const [
ButtonSegment(value: FassadChartType.bar, label: Text('Bar')),
ButtonSegment(value: FassadChartType.line, label: Text('Line')),
ButtonSegment(value: FassadChartType.pie, label: Text('Pie')),
],
selected: {_chartType},
onSelectionChanged: (s) => setState(() => _chartType = s.first),
),
],
),
const SizedBox(height: 24),
if (_stream != null)
StreamBuilder<FassadCachedResult>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(
snapshot.error.toString(),
style: TextStyle(color: colorScheme.error),
);
}
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final cached = snapshot.data!;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Source: ${cached.data.source}',
style: Theme.of(context).textTheme.labelSmall,
),
const SizedBox(width: 8),
if (cached.isStale)
Chip(
avatar: Icon(
Icons.offline_bolt_outlined,
size: 14,
color: colorScheme.onSecondaryContainer,
),
label: Text(
'Cached · ${_formatAge(cached.cachedAt)}',
style: TextStyle(
fontSize: 11,
color: colorScheme.onSecondaryContainer,
),
),
backgroundColor: colorScheme.secondaryContainer,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
),
],
),
const SizedBox(height: 12),
FassadChart(
result: cached.data,
xColumn: cached.data.columns.first,
yColumn: cached.data.columns.last,
type: _chartType,
),
const SizedBox(height: 24),
FassadTable(result: cached.data),
],
);
},
),
],
),
),
),
);
}
String _formatAge(DateTime cachedAt) {
final diff = DateTime.now().difference(cachedAt);
if (diff.inSeconds < 60) return 'just now';
if (diff.inMinutes < 60) return '${diff.inMinutes}m ago';
if (diff.inHours < 24) return '${diff.inHours}h ago';
return '${diff.inDays}d ago';
}
}