chico_kit 1.4.0 copy "chico_kit: ^1.4.0" to clipboard
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.

example/lib/main.dart

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),
          ),
        ),
      ],
    );
  }
}
0
likes
160
points
92
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Developer helpers for Chico UI — fewer lines for the same results. Context shortcuts, validators, formatters, debounce, and async helpers.

Repository (GitHub)
View/report issues

Topics

#flutter #ui #helpers #utilities #chico

License

MIT (license)

Dependencies

chico_ui, flutter

More

Packages that depend on chico_kit