verify_code_field 0.1.1 copy "verify_code_field: ^0.1.1" to clipboard
verify_code_field: ^0.1.1 copied to clipboard

Clean, responsive verification code (OTP) field with any number of boxes, easy delete and paste, and SMS code auto-fill on Android and iOS.

example/lib/main.dart

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

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

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Color(0xFF4F46E5);
    return MaterialApp(
      title: 'verify_code_field',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(colorSchemeSeed: seed),
      darkTheme: ThemeData(colorSchemeSeed: seed, brightness: Brightness.dark),
      home: const VerifyPage(),
    );
  }
}

class VerifyPage extends StatefulWidget {
  const VerifyPage({super.key});

  @override
  State<VerifyPage> createState() => _VerifyPageState();
}

class _VerifyPageState extends State<VerifyPage> {
  final _controller = TextEditingController();
  int _length = 6;
  VerifyCodeShape _shape = VerifyCodeShape.outlined;
  bool _obscure = false;
  bool _checking = false;
  bool _verified = false;
  String? _error;
  String? _appSignature;

  /// The demo accepts 1234, 12345, 123456 or 12345678.
  String get _expected => '12345678'.substring(0, _length);

  @override
  void initState() {
    super.initState();
    // Android only: the hash to put at the end of the SMS for SmsAutofill.retriever.
    SmsCodeListener.appSignature().then((hash) {
      if (mounted && hash != null) setState(() => _appSignature = hash);
    });
  }

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

  Future<void> _verify(String code) async {
    setState(() {
      _checking = true;
      _error = null;
    });
    await Future<void>.delayed(
      const Duration(milliseconds: 700),
    ); // Your API call.
    if (!mounted) return;
    setState(() {
      _checking = false;
      if (code == _expected) {
        _verified = true;
      } else {
        _error = 'That code is not correct. Please try again.';
      }
    });
  }

  void _reset({int? length}) {
    _controller.clear();
    setState(() {
      if (length != null) _length = length;
      _error = null;
      _verified = false;
    });
  }

  void _resend() {
    _reset();
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(
        const SnackBar(content: Text('A new code is on its way.')),
      );
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final cs = theme.colorScheme;

    return Scaffold(
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 440),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Center(
                    child: Container(
                      width: 64,
                      height: 64,
                      decoration: BoxDecoration(
                        color: cs.primaryContainer,
                        shape: BoxShape.circle,
                      ),
                      child: Icon(
                        Icons.sms_outlined,
                        color: cs.onPrimaryContainer,
                        size: 30,
                      ),
                    ),
                  ),
                  const SizedBox(height: 20),
                  Text(
                    'Enter verification code',
                    textAlign: TextAlign.center,
                    style: theme.textTheme.headlineSmall?.copyWith(
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    'We sent a $_length-digit code to +856 20 •••• 1234',
                    textAlign: TextAlign.center,
                    style: theme.textTheme.bodyMedium?.copyWith(
                      color: cs.onSurfaceVariant,
                    ),
                  ),
                  const SizedBox(height: 32),
                  VerifyCodeField(
                    length: _length,
                    controller: _controller,
                    autofocus: true,
                    obscureText: _obscure,
                    readOnly: _checking || _verified,
                    style: VerifyCodeStyle(shape: _shape),
                    errorText: _error,
                    onChanged: (_) {
                      if (_error != null) setState(() => _error = null);
                    },
                    onCompleted: _verify,
                  ),
                  const SizedBox(height: 24),
                  SizedBox(height: 48, child: Center(child: _status(theme))),
                  const SizedBox(height: 8),
                  Text(
                    'Demo: the correct code is $_expected',
                    textAlign: TextAlign.center,
                    style: theme.textTheme.bodySmall?.copyWith(
                      color: cs.onSurfaceVariant,
                    ),
                  ),
                  const SizedBox(height: 32),
                  const Divider(),
                  const SizedBox(height: 16),
                  _Option(
                    label: 'Boxes',
                    child: SegmentedButton<int>(
                      segments: const [
                        ButtonSegment(value: 4, label: Text('4')),
                        ButtonSegment(value: 5, label: Text('5')),
                        ButtonSegment(value: 6, label: Text('6')),
                        ButtonSegment(value: 8, label: Text('8')),
                      ],
                      selected: {_length},
                      showSelectedIcon: false,
                      onSelectionChanged: (s) => _reset(length: s.first),
                    ),
                  ),
                  _Option(
                    label: 'Style',
                    child: SegmentedButton<VerifyCodeShape>(
                      segments: const [
                        ButtonSegment(
                          value: VerifyCodeShape.outlined,
                          label: Text('Outlined'),
                        ),
                        ButtonSegment(
                          value: VerifyCodeShape.filled,
                          label: Text('Filled'),
                        ),
                        ButtonSegment(
                          value: VerifyCodeShape.underline,
                          label: Text('Line'),
                        ),
                      ],
                      selected: {_shape},
                      showSelectedIcon: false,
                      onSelectionChanged: (s) =>
                          setState(() => _shape = s.first),
                    ),
                  ),
                  SwitchListTile(
                    contentPadding: EdgeInsets.zero,
                    title: const Text('Hide code'),
                    value: _obscure,
                    onChanged: (v) => setState(() => _obscure = v),
                  ),
                  if (_appSignature != null)
                    ListTile(
                      contentPadding: EdgeInsets.zero,
                      title: const Text('App hash for SMS Retriever'),
                      subtitle: SelectableText(_appSignature!),
                    ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Widget _status(ThemeData theme) {
    if (_checking) {
      return const SizedBox.square(
        dimension: 24,
        child: CircularProgressIndicator(strokeWidth: 2.5),
      );
    }
    if (_verified) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.check_circle, color: Colors.green.shade600),
          const SizedBox(width: 8),
          Text('Verified', style: theme.textTheme.titleMedium),
          const SizedBox(width: 12),
          TextButton(onPressed: _reset, child: const Text('Start over')),
        ],
      );
    }
    return TextButton(
      onPressed: _resend,
      child: const Text("Didn't get a code? Resend"),
    );
  }
}

class _Option extends StatelessWidget {
  const _Option({required this.label, required this.child});

  final String label;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Row(
        children: [
          SizedBox(
            width: 64,
            child: Text(label, style: Theme.of(context).textTheme.titleSmall),
          ),
          Expanded(
            child: Align(
              alignment: Alignment.centerRight,
              child: FittedBox(child: child),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
--
downloads
screenshot

Documentation

API reference

Publisher

verified publisherljthao.com

Clean, responsive verification code (OTP) field with any number of boxes, easy delete and paste, and SMS code auto-fill on Android and iOS.

Repository (GitHub)
View/report issues

Topics

#otp #pin-code #verification-code #sms #autofill

License

MIT (license)

Dependencies

flutter, flutter_web_plugins

More

Packages that depend on verify_code_field

Packages that implement verify_code_field