chico_kit 1.4.0
chico_kit: ^1.4.0 copied to clipboard
Developer helpers for Chico UI — fewer lines for the same results. Context shortcuts, validators, formatters, debounce, and async helpers.
import 'package:chico_kit/chico_kit.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(const KitGalleryApp());
}
/// Live catalog of Chico Kit helpers.
class KitGalleryApp extends StatefulWidget {
const KitGalleryApp({super.key});
@override
State<KitGalleryApp> createState() => _KitGalleryAppState();
}
class _KitGalleryAppState extends State<KitGalleryApp> {
ChicoThemeMode _mode = ChicoThemeMode.system;
@override
Widget build(BuildContext context) {
return ChicoApp(
title: 'Chico Kit',
themeMode: _mode,
home: KitGalleryPage(
mode: _mode,
onModeChanged: (mode) => setState(() => _mode = mode),
),
);
}
}
/// Scrollable playground for context, validate, format, async, result.
class KitGalleryPage extends StatelessWidget {
const KitGalleryPage({
super.key,
required this.mode,
required this.onModeChanged,
});
final ChicoThemeMode mode;
final ValueChanged<ChicoThemeMode> onModeChanged;
@override
Widget build(BuildContext context) {
return ChicoPage(
title: 'Chico Kit',
child: ChicoColumn(
gap: ChicoSpace.space24,
children: [
const ChicoText(
'Helpers on top of Chico UI. One import: package:chico_kit. '
'Tap the actions below — same chrome, fewer lines.',
role: ChicoTextRole.secondary,
),
_AppearanceRow(mode: mode, onModeChanged: onModeChanged),
const _SectionTitle('Context'),
const _ContextPlayground(),
const _SectionTitle('Validate'),
const _ValidatePlayground(),
const _SectionTitle('Format'),
const _FormatPlayground(),
const _SectionTitle('Async & Result'),
const _AsyncPlayground(),
const _SectionTitle('Clipboard & more'),
const _ExtrasPlayground(),
const _SectionTitle('Responsive'),
const _ResponsivePlayground(),
const _SectionTitle('Time / paging / listen'),
const _ToolsPlayground(),
const _SectionTitle('Platform / cache / once'),
const _FinalPlayground(),
const _SectionTitle('AsyncBuilder / PagedList'),
const _DataUiPlayground(),
],
),
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.label);
final String label;
@override
Widget build(BuildContext context) {
return ChicoText(label, variant: ChicoTextVariant.headline);
}
}
class _AppearanceRow extends StatelessWidget {
const _AppearanceRow({required this.mode, required this.onModeChanged});
final ChicoThemeMode mode;
final ValueChanged<ChicoThemeMode> onModeChanged;
@override
Widget build(BuildContext context) {
return ChicoTabs(
labels: const ['System', 'Light', 'Dark'],
index: mode.index,
onChanged: (index) => onModeChanged(ChicoThemeMode.values[index]),
);
}
}
class _ContextPlayground extends StatelessWidget {
const _ContextPlayground();
@override
Widget build(BuildContext context) {
final media = context.chicoMedia;
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'${media.breakpoint.name} · '
'${media.width.round()}×${media.height.round()} · '
'tint ready',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Show toast',
expand: true,
onPressed: () => context.showToast('Saved'),
),
ChicoButton(
label: 'Show busy (1.2s)',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
await ChicoAsync.guard(
() => Future<void>.delayed(const Duration(milliseconds: 1200)),
onStart: () => context.showBusy(message: 'Saving'),
onDone: context.hideBusy,
);
if (context.mounted) {
context.showToast('Done');
}
},
),
ChicoButton(
label: 'Confirm delete',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final ok = await context.confirm(
'Delete item?',
message: 'This cannot be undone.',
confirmTone: ChicoButtonTone.destructive,
);
if (context.mounted) {
context.showToast(ok ? 'Deleted' : 'Cancelled');
}
},
),
ChicoButton(
label: 'Prompt rename',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final name = await context.prompt('Rename', hint: 'Name');
if (context.mounted && name != null) {
context.showToast(ChicoFormat.titleCase(name));
}
},
),
ChicoButton(
label: 'Push page',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
context.pushPage(
Builder(
builder: (pageContext) {
return ChicoScaffold(
bar: const ChicoBar(title: 'Detail'),
body: ChicoPage(
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText('Opened with context.pushPage.'),
ChicoButton(
label: 'Pop',
expand: true,
onPressed: () => pageContext.maybePop(),
),
],
),
),
);
},
),
);
},
),
ChicoButton(
label: 'Show sheet',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
context.showSheet<void>(
title: 'Filters',
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
const ChicoText(
'Opened with context.showSheet.',
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Done',
expand: true,
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
},
),
ChicoButton(
label: 'Show action sheet',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final result = await context.showActionSheet<String>(
title: 'Share',
actions: const [
ChicoDialogAction(label: 'Copy link', result: 'copy'),
ChicoDialogAction(
label: 'Delete',
result: 'delete',
tone: ChicoButtonTone.destructive,
),
],
cancel: const ChicoDialogAction(
label: 'Cancel',
tone: ChicoButtonTone.neutral,
),
);
if (context.mounted && result != null) {
context.showToast(result);
}
},
),
],
);
}
}
class _ValidatePlayground extends StatefulWidget {
const _ValidatePlayground();
@override
State<_ValidatePlayground> createState() => _ValidatePlaygroundState();
}
class _ValidatePlaygroundState extends State<_ValidatePlayground> {
final _formKey = GlobalKey<FormState>();
final _password = TextEditingController();
final _confirm = TextEditingController();
@override
void dispose() {
_password.dispose();
_confirm.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChicoForm(
formKey: _formKey,
child: ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoTextFormField(
label: 'Email',
hint: 'ada@example.com',
keyboardType: TextInputType.emailAddress,
validator: ChicoValidators.compose([
ChicoValidators.required(),
ChicoValidators.email(),
]),
),
ChicoTextFormField(
controller: _password,
label: 'Password',
obscureText: true,
validator: ChicoValidators.compose([
ChicoValidators.required(),
ChicoValidators.minLength(6),
]),
),
ChicoTextFormField(
controller: _confirm,
label: 'Confirm',
obscureText: true,
validator: ChicoValidators.match(() => _password.text),
),
ChicoButton(
label: 'Submit form',
expand: true,
onPressed: () {
context.submitForm(
_formKey,
busyMessage: 'Saving',
successMessage: 'Account ready',
action: () => Future<void>.delayed(400.ms),
);
},
),
],
),
);
}
}
class _FormatPlayground extends StatelessWidget {
const _FormatPlayground();
@override
Widget build(BuildContext context) {
final now = DateTime(2024, 6, 5, 9, 5);
final samples = <(String, String)>[
('titleCase', ChicoFormat.titleCase('ada lovelace')),
('initials', ChicoFormat.initials('Ada Lovelace')),
('dateIso', ChicoFormat.dateIso(now)),
('timeOfDay', ChicoFormat.timeOfDay(now, use24Hour: false)),
('currency', ChicoFormat.currency(12.5)),
('ellipsis', ChicoFormat.ellipsis('abcdefghijklmnop', 10)),
];
return ChicoColumn(
gap: ChicoSpace.space8,
children: [
for (final sample in samples)
ChicoListTile(
title: sample.$1,
trailing: ChicoText(
sample.$2,
role: ChicoTextRole.secondary,
maxLines: 1,
),
showChevron: false,
),
],
);
}
}
class _AsyncPlayground extends StatefulWidget {
const _AsyncPlayground();
@override
State<_AsyncPlayground> createState() => _AsyncPlaygroundState();
}
class _AsyncPlaygroundState extends State<_AsyncPlayground> {
final _debouncer = ChicoDebouncer(
duration: const Duration(milliseconds: 400),
);
var _query = '';
var _debounced = '';
var _resultLabel = '—';
@override
void dispose() {
_debouncer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoTextField(
label: 'Debounced search',
hint: 'Type…',
onChanged: (value) {
setState(() => _query = value);
_debouncer.run(() {
if (mounted) {
setState(() => _debounced = value);
}
});
},
),
ChicoText(
'Live: $_query',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoText(
'Debounced: $_debounced',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Capture success Result',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final result = await ChicoResult.capture(() async => 42);
setState(() {
_resultLabel = result.when(
ok: (value) => 'ok($value)',
err: (error, stackTrace) => 'err($error)',
);
});
},
),
ChicoButton(
label: 'Capture failure Result',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
final result = await ChicoResult.capture<int>(() async {
throw StateError('network');
});
setState(() {
_resultLabel = result.when(
ok: (value) => 'ok($value)',
err: (error, stackTrace) => 'err($error)',
);
});
},
),
ChicoText(
'Last Result: $_resultLabel',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Retry (succeeds on 3rd try)',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () async {
var attempts = 0;
final value = await ChicoAsync.retry(
() async {
attempts++;
if (attempts < 3) {
throw StateError('attempt $attempts');
}
return attempts;
},
maxAttempts: 3,
delay: 50.ms,
);
setState(() => _resultLabel = 'retry ok after $value');
if (context.mounted) {
context.showToast('Retry ok ($value)');
}
},
),
],
);
}
}
class _ExtrasPlayground extends StatelessWidget {
const _ExtrasPlayground();
@override
Widget build(BuildContext context) {
const link = 'https://example.com/share';
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'Blank? ${''.isBlank} · '
'orEmpty: ${null.orEmpty('n/a')} · '
'distinct: ${[1, 1, 2, 2, 3].distinct()}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Copy link + toast',
expand: true,
onPressed: () => context.copyText(link),
),
ChicoButton(
label: 'Haptic light',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => context.hapticLight(),
),
ChicoButton(
label: 'Unfocus keyboard',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => context.unfocus(),
),
const ChicoTextField(label: 'Focus me, then tap Unfocus'),
],
);
}
}
class _ResponsivePlayground extends StatelessWidget {
const _ResponsivePlayground();
@override
Widget build(BuildContext context) {
final columns = context.chicoValue(phone: 1, tablet: 2, desktop: 4);
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'chicoValue columns: $columns',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoResponsive(
phone: (context) => const ChicoBanner(
title: 'Phone',
message: 'Compact layout builder.',
),
tablet: (context) => const ChicoBanner(
title: 'Tablet',
message: 'md and up.',
tone: ChicoButtonTone.success,
),
desktop: (context) => const ChicoBanner(
title: 'Desktop',
message: 'lg and up — resize the window.',
tone: ChicoButtonTone.warning,
),
),
],
);
}
}
class _ToolsPlayground extends StatefulWidget {
const _ToolsPlayground();
@override
State<_ToolsPlayground> createState() => _ToolsPlaygroundState();
}
class _ToolsPlaygroundState extends State<_ToolsPlayground> {
final _counter = ValueNotifier<int>(0);
final _paging = ChicoPaging(pageSize: 10);
var _loaded = 0;
@override
void dispose() {
_counter.dispose();
_paging.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final ago = ChicoTimeAgo.format(
DateTime.now().subtract(const Duration(hours: 3)),
);
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'TimeAgo: $ago · Compact: ${ChicoCompact.number(12800)}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoListen<int>(
listenable: _counter,
builder: (context, value, child) {
return ChicoButton(
label: 'Listen counter: $value',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => _counter.value++,
);
},
),
ListenableBuilder(
listenable: _paging,
builder: (context, child) {
return ChicoColumn(
gap: ChicoSpace.space8,
children: [
ChicoText(
'Paging page ${_paging.page} · loaded $_loaded · '
'hasMore ${_paging.hasMore} · loading ${_paging.loading}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Load next page',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: !_paging.hasMore || _paging.loading
? null
: () async {
_paging.beginLoad();
await Future<void>.delayed(300.ms);
const batch = 10;
_paging.absorb(batch);
setState(() => _loaded += batch);
},
),
ChicoButton(
label: 'Reset paging',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
_paging.reset();
setState(() => _loaded = 0);
},
),
],
);
},
),
],
);
}
}
class _FinalPlayground extends StatefulWidget {
const _FinalPlayground();
@override
State<_FinalPlayground> createState() => _FinalPlaygroundState();
}
class _FinalPlaygroundState extends State<_FinalPlayground> {
final _cache = ChicoCache<String>(ttl: const Duration(seconds: 30));
final _scroll = ScrollController();
var _onceCount = 0;
var _busHits = 0;
@override
void initState() {
super.initState();
ChicoChannels.on<String>('kit.demo', _onBus);
}
void _onBus(String message) {
if (!mounted) {
return;
}
setState(() => _busHits++);
context.showToast(message);
}
@override
void dispose() {
ChicoChannels.off<String>('kit.demo', _onBus);
_scroll.dispose();
_cache.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final uri = Uri.parse(
'https://example.com/app',
).withQuery({'lang': 'en', 'v': '1'});
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
ChicoText(
'Platform: ${ChicoPlatform.isWeb
? 'web'
: ChicoPlatform.isDesktop
? 'desktop'
: 'mobile'}'
' · dark=${context.isDark} · rtl=${context.isRtl}'
' · kb=${context.isKeyboardVisible}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoText(
'URI: $uri · Base64: ${ChicoCodec.toBase64('chico')}',
variant: ChicoTextVariant.footnote,
role: ChicoTextRole.secondary,
),
ChicoButton(
label: 'Log debug line',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoLog.d('hello from kit', tag: 'gallery');
context.showToast('Logged (debug only)');
},
),
ChicoButton(
label: 'Cache put / get',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
_cache.put('greeting', 'Cached hello');
context.showToast(_cache.get('greeting') ?? 'miss');
},
),
ChicoButton(
label: 'Once (count $_onceCount)',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () {
ChicoOnceKey.run('gallery.once', () {
setState(() => _onceCount++);
context.showToast('Once fired');
});
},
),
ChicoButton(
label: 'Bus emit (hits $_busHits)',
expand: true,
tone: ChicoButtonTone.neutral,
onPressed: () => ChicoChannels.emit<String>('kit.demo', 'Bus ping'),
),
SizedBox(
height: 96,
child: ListView(
controller: _scroll,
children: List.generate(
8,
(i) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: ChicoText(
'Scroll row $i',
variant: ChicoTextVariant.footnote,
),
),
),
),
),
ChicoRow(
gap: ChicoSpace.space8,
children: [
Expanded(
child: ChicoButton(
label: 'Top',
tone: ChicoButtonTone.neutral,
onPressed: () => _scroll.scrollToTop(),
),
),
Expanded(
child: ChicoButton(
label: 'Bottom',
tone: ChicoButtonTone.neutral,
onPressed: () => _scroll.scrollToBottom(),
),
),
],
),
],
);
}
}
class _DataUiPlayground extends StatelessWidget {
const _DataUiPlayground();
@override
Widget build(BuildContext context) {
return ChicoColumn(
gap: ChicoSpace.space12,
children: [
SizedBox(
height: 120,
child: ChicoAsyncBuilder<String>(
future: () async {
await Future<void>.delayed(200.ms);
return 'Loaded profile';
},
builder: (context, data) => ChicoText(data),
),
),
SizedBox(
height: 220,
child: ChicoPagedList<String>(
pageSize: 5,
fetch: (page, size) async {
await Future<void>.delayed(200.ms);
if (page > 3) {
return [];
}
return [
for (var i = 0; i < size; i++)
'Item ${(page - 1) * size + i + 1}',
];
},
itemBuilder: (context, item, index) => ChicoListTile(title: item),
),
),
],
);
}
}