craft_kit 0.1.3 copy "craft_kit: ^0.1.3" to clipboard
craft_kit: ^0.1.3 copied to clipboard

Swipeable card stacks with spring physics, per-direction behaviors, undo and a Dock-style genie, plus an OTP input and remembered-email suggestions.

example/lib/main.dart

import 'package:craft_kit/craft_kit.dart';
import 'package:flutter/material.dart';

import 'swipe_lab.dart';
import 'swipe_lab_settings.dart';

void main() => runApp(const CraftKitExample());

/// Demo app showing every craft_kit feature.
class CraftKitExample extends StatelessWidget {
  /// Creates the demo app.
  const CraftKitExample({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'craft_kit',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      home: const _Home(),
    );
  }
}

/// Switches demos with a bottom bar and an [IndexedStack].
///
/// A `TabBarView` would put every demo inside a horizontal `PageView`, which
/// wins the gesture arena against the card stack's horizontal drag, so left
/// and right swipes would never reach the cards. An [IndexedStack] has no
/// gestures of its own, and it also keeps each demo's state while hidden.
class _Home extends StatefulWidget {
  const _Home();

  @override
  State<_Home> createState() => _HomeState();
}

class _HomeState extends State<_Home> {
  static const int _swipeTab = 2;

  final SwipeLabSettings _settings = SwipeLabSettings();
  final SwipeCardController _controller = SwipeCardController();

  int _index = 0;
  bool _panelOpen = false;

  @override
  void dispose() {
    _controller.dispose();
    _settings.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final onSwipeTab = _index == _swipeTab;
    final pages = <Widget>[
      const _EmailDemo(),
      const _OtpDemo(),
      SwipeDemo(settings: _settings, controller: _controller),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('craft_kit'),
        actions: <Widget>[
          if (onSwipeTab)
            IconButton(
              tooltip: 'Control center',
              isSelected: _panelOpen,
              icon: const Icon(Icons.tune),
              selectedIcon: const Icon(Icons.tune),
              onPressed: () => setState(() => _panelOpen = !_panelOpen),
            ),
        ],
      ),
      body: Stack(
        children: <Widget>[
          IndexedStack(
            index: _index,
            children: <Widget>[
              for (var i = 0; i < pages.length; i++)
                // Hidden pages keep their state but stop ticking animations.
                TickerMode(enabled: i == _index, child: pages[i]),
            ],
          ),
          if (onSwipeTab && _panelOpen)
            Positioned.fill(
              child: SwipeControlCenter(
                settings: _settings,
                controller: _controller,
                onClose: () => setState(() => _panelOpen = false),
              ),
            ),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: const <NavigationDestination>[
          NavigationDestination(
            icon: Icon(Icons.alternate_email),
            label: 'Email',
          ),
          NavigationDestination(icon: Icon(Icons.password), label: 'OTP'),
          NavigationDestination(icon: Icon(Icons.style), label: 'Swipe'),
        ],
      ),
    );
  }
}

class _EmailDemo extends StatefulWidget {
  const _EmailDemo();

  @override
  State<_EmailDemo> createState() => _EmailDemoState();
}

class _EmailDemoState extends State<_EmailDemo> {
  // Swap MemoryCraftStorage for your own CraftStorage to persist for real.
  final RememberedEmailStore _store = StorageRememberedEmailStore(
    MemoryCraftStorage(),
  );
  late final EmailFieldController _email = EmailFieldController(store: _store);
  final GlobalKey<FormState> _form = GlobalKey<FormState>();

  @override
  void initState() {
    super.initState();
    _seed();
  }

  Future<void> _seed() async {
    await _store.rememberEmail('anna@gmail.com');
    await _store.rememberEmail('anna.work@acme.io');
  }

  @override
  void dispose() {
    _email.dispose();
    super.dispose();
  }

  Future<void> _signIn() async {
    if (!_form.currentState!.validate()) return;
    await _email.rememberCurrentEmail();
    if (!mounted) return;
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text('Remembered ${_email.email}')));
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Form(
        key: _form,
        child: Column(
          children: <Widget>[
            const Text('Type "an", or "you@gm" to see suggestions'),
            const SizedBox(height: 16),
            RememberedEmailField(controller: _email),
            FilledButton(onPressed: _signIn, child: const Text('Sign in')),
          ],
        ),
      ),
    );
  }
}

class _OtpDemo extends StatefulWidget {
  const _OtpDemo();

  @override
  State<_OtpDemo> createState() => _OtpDemoState();
}

class _OtpDemoState extends State<_OtpDemo> {
  final OtpCodeController _otp = OtpCodeController();
  String _status = 'Type or paste a 6-digit code';

  @override
  void initState() {
    super.initState();
    _otp.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    _otp.dispose();
    super.dispose();
  }

  Future<void> _verify(String code) async {
    setState(() => _status = 'Verifying $code...');
    _otp.beginProcessing();
    await Future<void>.delayed(const Duration(seconds: 2));
    if (!mounted) return;
    if (code == '123456') {
      _otp.showSuccess();
      setState(() => _status = 'Verified');
    } else {
      _otp.showFailure(keepCode: false);
      setState(() => _status = 'Wrong code. Try 123456');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          OtpCodeField(
            controller: _otp,
            autoFocus: false,
            onCompleted: _verify,
          ),
          const SizedBox(height: 24),
          Text(_status),
          const SizedBox(height: 8),
          Text('phase: ${_otp.phase.name}'),
          Wrap(
            spacing: 8,
            children: <Widget>[
              TextButton(
                onPressed: () => _otp.setCode('Your code is 482913'),
                child: const Text('Simulate SMS autofill'),
              ),
              TextButton(onPressed: _otp.clear, child: const Text('Clear')),
            ],
          ),
        ],
      ),
    );
  }
}
1
likes
160
points
353
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Swipeable card stacks with spring physics, per-direction behaviors, undo and a Dock-style genie, plus an OTP input and remembered-email suggestions.

Repository (GitHub)
View/report issues

Topics

#widget #otp #swipe #form #ui

License

MIT (license)

Dependencies

flutter

More

Packages that depend on craft_kit