premium_otp_input 0.5.2 copy "premium_otp_input: ^0.5.2" to clipboard
premium_otp_input: ^0.5.2 copied to clipboard

A highly customizable, beautiful, and interactive OTP and PIN entry widget for Flutter featuring Blob, Sigil, Nexus, Lightning, Liquid, and Motion animations.

example/lib/main.dart

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

void main() {
  runApp(const MyApp());
}

/// The main application widget for the example.
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isGlobalDark = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Premium OTP Input Showcase',
      debugShowCheckedModeBanner: false,
      themeMode: _isGlobalDark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData.light(useMaterial3: true).copyWith(
        scaffoldBackgroundColor: const Color(0xFFF8FAFC),
        colorScheme: const ColorScheme.light(
          primary: Color(0xFF4F6EF7),
          surface: Colors.white,
        ),
      ),
      darkTheme: ThemeData.dark(useMaterial3: true).copyWith(
        scaffoldBackgroundColor: const Color(0xFF0B0F19),
        colorScheme: const ColorScheme.dark(
          primary: Color(0xFF6366F1),
          surface: Color(0xFF1E293B),
        ),
      ),
      home: OtpDemoScreen(
        isGlobalDark: _isGlobalDark,
        onToggleGlobalTheme: () {
          setState(() {
            _isGlobalDark = !_isGlobalDark;
          });
        },
      ),
    );
  }
}

enum DemoStyle { blob, nexus, sigil, liquid, lightning, motion, standard }

class OtpDemoScreen extends StatefulWidget {
  final bool isGlobalDark;
  final VoidCallback onToggleGlobalTheme;

  const OtpDemoScreen({
    super.key,
    required this.isGlobalDark,
    required this.onToggleGlobalTheme,
  });

  @override
  State<OtpDemoScreen> createState() => _OtpDemoScreenState();
}

class _OtpDemoScreenState extends State<OtpDemoScreen> {
  // Controllers and Focus Nodes
  final TextEditingController _sigilController = TextEditingController();
  final FocusNode _sigilFocusNode = FocusNode();

  final TextEditingController _liquidController = TextEditingController();
  final FocusNode _liquidFocusNode = FocusNode();

  final TextEditingController _lightningController = TextEditingController();
  final FocusNode _lightningFocusNode = FocusNode();

  final TextEditingController _motionController = TextEditingController();
  final FocusNode _motionFocusNode = FocusNode();

  final TextEditingController _nexusController = TextEditingController();
  final FocusNode _nexusFocusNode = FocusNode();

  final TextEditingController _blobController = TextEditingController();
  final FocusNode _blobFocusNode = FocusNode();

  final TextEditingController _standardController = TextEditingController();
  final FocusNode _standardFocusNode = FocusNode();

  DemoStyle _currentStyle = DemoStyle.blob;

  // Widget States
  bool _isVerifying = false;
  bool _isSuccess = false;
  bool _isError = false;

  // Per-widget dark overrides (null = sync with global theme)
  bool? _widgetDarkOverride;

  // Standard Customization Options
  OtpEntryAnimationStyle _entryAnimationStyle = OtpEntryAnimationStyle.scale;
  OtpSuccessAnimationStyle _successAnimationStyle =
      OtpSuccessAnimationStyle.bounce;
  bool _obscureText = false;
  String _obscuringCharacter = '●';
  double _boxHeight = 64.0;
  double _borderRadius = 16.0;

  bool get _isEffectiveDark => _widgetDarkOverride ?? widget.isGlobalDark;

  @override
  void dispose() {
    _sigilController.dispose();
    _sigilFocusNode.dispose();
    _liquidController.dispose();
    _liquidFocusNode.dispose();
    _lightningController.dispose();
    _lightningFocusNode.dispose();
    _motionController.dispose();
    _motionFocusNode.dispose();
    _nexusController.dispose();
    _nexusFocusNode.dispose();
    _blobController.dispose();
    _blobFocusNode.dispose();
    _standardController.dispose();
    _standardFocusNode.dispose();
    super.dispose();
  }

  void _clearAllInput() {
    setState(() {
      _isVerifying = false;
      _isSuccess = false;
      _isError = false;
      _sigilController.clear();
      _liquidController.clear();
      _lightningController.clear();
      _motionController.clear();
      _nexusController.clear();
      _blobController.clear();
      _standardController.clear();
    });
    _focusActiveNode();
  }

  void _focusActiveNode() {
    switch (_currentStyle) {
      case DemoStyle.sigil:
        _sigilFocusNode.requestFocus();
      case DemoStyle.liquid:
        _liquidFocusNode.requestFocus();
      case DemoStyle.lightning:
        _lightningFocusNode.requestFocus();
      case DemoStyle.motion:
        _motionFocusNode.requestFocus();
      case DemoStyle.nexus:
        _nexusFocusNode.requestFocus();
      case DemoStyle.blob:
        _blobFocusNode.requestFocus();
      case DemoStyle.standard:
        _standardFocusNode.requestFocus();
    }
  }

  void _simulateCompletion(String code, String targetSuccessCode) async {
    setState(() {
      _isVerifying = true;
      _isError = false;
    });

    await Future.delayed(const Duration(milliseconds: 1400));

    if (!mounted) return;

    if (code == targetSuccessCode) {
      setState(() {
        _isVerifying = false;
        _isSuccess = true;
      });
    } else {
      setState(() {
        _isVerifying = false;
        _isError = true;
      });
      await Future.delayed(const Duration(seconds: 2));
      if (!mounted) return;
      setState(() {
        _isError = false;
      });
      _clearAllInput();
    }
  }

  @override
  Widget build(BuildContext context) {
    final isDark = widget.isGlobalDark;
    final bg = isDark ? const Color(0xFF0B0F19) : const Color(0xFFF1F5F9);
    final cardBg = isDark ? const Color(0xFF131C2E) : Colors.white;
    final textPrimary = isDark ? Colors.white : const Color(0xFF0F172A);
    final textSecondary = isDark
        ? const Color(0xFF94A3B8)
        : const Color(0xFF64748B);

    return Scaffold(
      backgroundColor: bg,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        centerTitle: false,
        title: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                gradient: const LinearGradient(
                  colors: [Color(0xFF6366F1), Color(0xFFA855F7)],
                ),
                borderRadius: BorderRadius.circular(12),
              ),
              child: const Icon(
                Icons.shield_outlined,
                color: Colors.white,
                size: 20,
              ),
            ),
            const SizedBox(width: 12),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Premium OTP Input',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                    color: textPrimary,
                  ),
                ),
                Text(
                  'v1.2.0 β€’ Ultra Creative Flutter Suite',
                  style: TextStyle(
                    fontSize: 11,
                    fontWeight: FontWeight.w500,
                    color: textSecondary,
                  ),
                ),
              ],
            ),
          ],
        ),
        actions: [
          IconButton(
            tooltip: isDark ? 'Switch to Light Mode' : 'Switch to Dark Mode',
            icon: Icon(
              isDark ? Icons.wb_sunny_rounded : Icons.nightlight_round,
              color: isDark ? const Color(0xFFF59E0B) : const Color(0xFF6366F1),
            ),
            onPressed: widget.onToggleGlobalTheme,
          ),
          const SizedBox(width: 12),
        ],
      ),
      body: SingleChildScrollView(
        physics: const BouncingScrollPhysics(),
        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
        child: Center(
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 800),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                // 1. Creative Header & Hero Badge
                _buildHeroBanner(isDark, cardBg, textPrimary, textSecondary),
                const SizedBox(height: 20),

                // 2. Style Selector Tabs (Sigil, Liquid, Lightning, Motion, Nexus, Standard)
                _buildStyleSelector(isDark),
                const SizedBox(height: 24),

                // 4. Main Active OTP Input Container Card
                _buildActiveOtpShowcaseCard(isDark, cardBg),
                const SizedBox(height: 24),

                // 5. Customization Options (Only for Standard mode or global overrides)
                if (_currentStyle == DemoStyle.standard) ...[
                  _buildStandardCustomizer(
                    isDark,
                    cardBg,
                    textPrimary,
                    textSecondary,
                  ),
                  const SizedBox(height: 24),
                ],

                const SizedBox(height: 32),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildHeroBanner(
    bool isDark,
    Color cardBg,
    Color textPrimary,
    Color textSecondary,
  ) {
    final styleInfo = _getStyleInfo(_currentStyle);

    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: cardBg,
        borderRadius: BorderRadius.circular(24),
        border: Border.all(
          color: isDark
              ? Colors.white.withValues(alpha: 0.08)
              : const Color(0xFFE2E8F0),
        ),
        boxShadow: [
          BoxShadow(
            color: styleInfo.accentColor.withValues(alpha: 0.12),
            blurRadius: 24,
            spreadRadius: -4,
            offset: const Offset(0, 8),
          ),
        ],
      ),
      child: Row(
        children: [
          Container(
            padding: const EdgeInsets.all(14),
            decoration: BoxDecoration(
              color: styleInfo.accentColor.withValues(alpha: 0.15),
              borderRadius: BorderRadius.circular(18),
            ),
            child: Icon(styleInfo.icon, color: styleInfo.accentColor, size: 28),
          ),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Wrap(
                  spacing: 8,
                  runSpacing: 6,
                  crossAxisAlignment: WrapCrossAlignment.center,
                  children: [
                    Text(
                      styleInfo.name,
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                        color: textPrimary,
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 3,
                      ),
                      decoration: BoxDecoration(
                        color: styleInfo.accentColor.withValues(alpha: 0.2),
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        styleInfo.tag,
                        style: TextStyle(
                          fontSize: 10,
                          fontWeight: FontWeight.bold,
                          color: styleInfo.accentColor,
                        ),
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 3,
                      ),
                      decoration: BoxDecoration(
                        color: const Color(0xFF22C55E).withValues(alpha: 0.15),
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(
                          color: const Color(0xFF22C55E).withValues(alpha: 0.4),
                        ),
                      ),
                      child: Text(
                        'πŸ”‘ Code: ${styleInfo.correctCode}',
                        style: TextStyle(
                          fontSize: 11,
                          fontWeight: FontWeight.bold,
                          color: const Color(0xFF22C55E),
                        ),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 4),
                Text(
                  styleInfo.description,
                  style: TextStyle(fontSize: 13, color: textSecondary),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildStyleSelector(bool isDark) {
    return SingleChildScrollView(
      scrollDirection: Axis.horizontal,
      physics: const BouncingScrollPhysics(),
      child: Row(
        children: DemoStyle.values.map((style) {
          final isSelected = _currentStyle == style;
          final info = _getStyleInfo(style);

          return Padding(
            padding: const EdgeInsets.only(right: 8.0),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 250),
              child: InkWell(
                onTap: () {
                  setState(() {
                    _currentStyle = style;
                    _widgetDarkOverride = null;
                    _clearAllInput();
                  });
                },
                borderRadius: BorderRadius.circular(16),
                child: Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 16,
                    vertical: 12,
                  ),
                  decoration: BoxDecoration(
                    color: isSelected
                        ? info.accentColor
                        : (isDark ? const Color(0xFF1E293B) : Colors.white),
                    borderRadius: BorderRadius.circular(16),
                    border: Border.all(
                      color: isSelected
                          ? info.accentColor
                          : (isDark
                                ? Colors.white.withValues(alpha: 0.08)
                                : const Color(0xFFE2E8F0)),
                    ),
                    boxShadow: isSelected
                        ? [
                            BoxShadow(
                              color: info.accentColor.withValues(alpha: 0.35),
                              blurRadius: 12,
                              offset: const Offset(0, 4),
                            ),
                          ]
                        : null,
                  ),
                  child: Row(
                    children: [
                      Icon(
                        info.icon,
                        size: 16,
                        color: isSelected
                            ? Colors.white
                            : (isDark
                                  ? const Color(0xFF94A3B8)
                                  : const Color(0xFF64748B)),
                      ),
                      const SizedBox(width: 8),
                      Text(
                        info.shortName,
                        style: TextStyle(
                          fontSize: 13,
                          fontWeight: isSelected
                              ? FontWeight.bold
                              : FontWeight.w600,
                          color: isSelected
                              ? Colors.white
                              : (isDark
                                    ? Colors.white
                                    : const Color(0xFF334155)),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        }).toList(),
      ),
    );
  }

  Widget _buildActiveOtpShowcaseCard(bool isDark, Color cardBg) {
    return AnimatedSwitcher(
      duration: const Duration(milliseconds: 350),
      child: switch (_currentStyle) {
        DemoStyle.sigil => SigilOtpVerificationView(
          key: ValueKey('sigil_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _sigilController,
          focusNode: _sigilFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '7890'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('✨ Resent Sigil OTP Code (Try "7890")'),
                backgroundColor: Color(0xFFA855F7),
              ),
            );
          },
        ),
        DemoStyle.liquid => LiquidOtpVerificationView(
          key: ValueKey('liquid_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _liquidController,
          focusNode: _liquidFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '1234'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('🌊 Resent Liquid OTP Code (Try "1234")'),
                backgroundColor: Color(0xFF3B82F6),
              ),
            );
          },
        ),
        DemoStyle.lightning => LightningOtpVerificationView(
          key: ValueKey('lightning_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _lightningController,
          focusNode: _lightningFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '4565'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('⚑ Resent Lightning OTP Code (Try "4565")'),
                backgroundColor: Color(0xFFF59E0B),
              ),
            );
          },
        ),
        DemoStyle.motion => MotionOtpVerificationView(
          key: ValueKey('motion_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _motionController,
          focusNode: _motionFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '1234'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('πŸŒ€ Resent Motion OTP Code (Try "1234")'),
                backgroundColor: Color(0xFF6366F1),
              ),
            );
          },
        ),
        DemoStyle.nexus => NexusOtpVerificationView(
          key: ValueKey('nexus_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _nexusController,
          focusNode: _nexusFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '4545'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('πŸ•ΈοΈ Resent Nexus OTP Code (Try "4545")'),
                backgroundColor: Color(0xFFFF453A),
              ),
            );
          },
        ),
        DemoStyle.blob => BlobOtpVerificationView(
          key: ValueKey('blob_$_isEffectiveDark'),
          length: 4,
          isDark: _isEffectiveDark,
          controller: _blobController,
          focusNode: _blobFocusNode,
          isSuccess: _isSuccess,
          isError: _isError,
          isVerifying: _isVerifying,
          onCompleted: (val) => _simulateCompletion(val, '5454'),
          onResend: () {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                content: Text('🫧 Resent Blob OTP Code (Try "5454")'),
                backgroundColor: Color(0xFFFF6A2A),
              ),
            );
          },
        ),
        DemoStyle.standard => Container(
          padding: const EdgeInsets.all(28),
          decoration: BoxDecoration(
            color: _isEffectiveDark ? const Color(0xFF1E293B) : Colors.white,
            borderRadius: BorderRadius.circular(24),
            border: Border.all(
              color: _isEffectiveDark
                  ? Colors.white.withValues(alpha: 0.12)
                  : const Color(0xFFE2E8F0),
            ),
          ),
          child: Column(
            children: [
              Text(
                'Standard Premium OTP',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: _isEffectiveDark
                      ? Colors.white
                      : const Color(0xFF0F172A),
                ),
              ),
              const SizedBox(height: 6),
              Text(
                'Fully configurable standard input widget',
                style: TextStyle(
                  fontSize: 13,
                  color: _isEffectiveDark
                      ? const Color(0xFF94A3B8)
                      : const Color(0xFF64748B),
                ),
              ),
              const SizedBox(height: 28),
              PremiumOtpInput(
                length: 6,
                controller: _standardController,
                focusNode: _standardFocusNode,
                isSuccess: _isSuccess,
                isError: _isError,
                isVerifying: _isVerifying,
                onCompleted: (val) => _simulateCompletion(val, '123456'),
                obscureText: _obscureText,
                obscuringCharacter: _obscuringCharacter,
                entryAnimationStyle: _entryAnimationStyle,
                successAnimationStyle: _successAnimationStyle,
                animateActiveBorder: true,
                boxHeight: _boxHeight,
                spacing: 10.0,
                borderRadius: _borderRadius,
                activeBorderColor: const Color(0xFF4F6EF7),
                defaultBorderColor: _isEffectiveDark
                    ? Colors.white.withValues(alpha: 0.2)
                    : const Color(0xFFCBD5E1),
                boxBackgroundColor: _isEffectiveDark
                    ? const Color(0xFF0F172A)
                    : const Color(0xFFF8FAFC),
                textStyle: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: _isEffectiveDark
                      ? Colors.white
                      : const Color(0xFF0F172A),
                ),
              ),
            ],
          ),
        ),
      },
    );
  }

  Widget _buildStandardCustomizer(
    bool isDark,
    Color cardBg,
    Color textPrimary,
    Color textSecondary,
  ) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: cardBg,
        borderRadius: BorderRadius.circular(20),
        border: Border.all(
          color: isDark
              ? Colors.white.withValues(alpha: 0.08)
              : const Color(0xFFE2E8F0),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Icon(
                Icons.tune_rounded,
                size: 20,
                color: Color(0xFF4F6EF7),
              ),
              const SizedBox(width: 10),
              Text(
                'Customize Standard Properties',
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                  color: textPrimary,
                ),
              ),
            ],
          ),
          const Divider(height: 24),

          // Entry animation style
          Text(
            'Digit Entry Animation:',
            style: TextStyle(
              fontSize: 13,
              fontWeight: FontWeight.w600,
              color: textSecondary,
            ),
          ),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            children: OtpEntryAnimationStyle.values.map((s) {
              return ChoiceChip(
                label: Text(s.name),
                selected: _entryAnimationStyle == s,
                onSelected: (sel) {
                  if (sel) setState(() => _entryAnimationStyle = s);
                },
              );
            }).toList(),
          ),
          const SizedBox(height: 16),

          // Success animation style
          Text(
            'Success Animation Style:',
            style: TextStyle(
              fontSize: 13,
              fontWeight: FontWeight.w600,
              color: textSecondary,
            ),
          ),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            children: OtpSuccessAnimationStyle.values.map((s) {
              return ChoiceChip(
                label: Text(s.name),
                selected: _successAnimationStyle == s,
                onSelected: (sel) {
                  if (sel) setState(() => _successAnimationStyle = s);
                },
              );
            }).toList(),
          ),
          const SizedBox(height: 16),

          // Box Height Slider
          Row(
            children: [
              Text(
                'Box Height (${_boxHeight.toInt()}px):',
                style: TextStyle(
                  fontSize: 13,
                  fontWeight: FontWeight.w600,
                  color: textSecondary,
                ),
              ),
              Expanded(
                child: Slider(
                  value: _boxHeight,
                  min: 50,
                  max: 84,
                  onChanged: (val) => setState(() => _boxHeight = val),
                ),
              ),
            ],
          ),

          // Border Radius Slider
          Row(
            children: [
              Text(
                'Border Radius (${_borderRadius.toInt()}px):',
                style: TextStyle(
                  fontSize: 13,
                  fontWeight: FontWeight.w600,
                  color: textSecondary,
                ),
              ),
              Expanded(
                child: Slider(
                  value: _borderRadius,
                  min: 4,
                  max: 32,
                  onChanged: (val) => setState(() => _borderRadius = val),
                ),
              ),
            ],
          ),
          const SizedBox(height: 12),

          // Obscure toggle & character selection
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                'Obscure Text (PIN Mode):',
                style: TextStyle(
                  fontSize: 13,
                  fontWeight: FontWeight.w600,
                  color: textSecondary,
                ),
              ),
              Switch(
                value: _obscureText,
                onChanged: (val) => setState(() => _obscureText = val),
              ),
            ],
          ),
          if (_obscureText) ...[
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: ['●', '*', 'β˜…', 'β™₯'].map((char) {
                return ChoiceChip(
                  label: Text(char),
                  selected: _obscuringCharacter == char,
                  onSelected: (sel) {
                    if (sel) setState(() => _obscuringCharacter = char);
                  },
                );
              }).toList(),
            ),
          ],
        ],
      ),
    );
  }

  _StyleMeta _getStyleInfo(DemoStyle style) {
    return switch (style) {
      DemoStyle.nexus => _StyleMeta(
        name: 'Nexus Lattice Mesh',
        shortName: 'Nexus',
        tag: 'NEURAL',
        description:
            'Neural lattice network with interconnected node physics and orbital electron arcs.',
        correctCode: '4545',
        icon: Icons.hub_outlined,
        accentColor: const Color(0xFFFF453A),
      ),
      DemoStyle.sigil => _StyleMeta(
        name: 'Sigil Arc Flow',
        shortName: 'Sigil',
        tag: 'COSMIC',
        description:
            'Dark-glowing cosmic slots with interactive autofill toast and smooth arc animations.',
        correctCode: '7890',
        icon: Icons.auto_awesome,
        accentColor: const Color(0xFFA855F7),
      ),
      DemoStyle.liquid => _StyleMeta(
        name: 'Liquid Motion',
        shortName: 'Liquid',
        tag: 'AQUATIC',
        description:
            'Glass containers filled with dynamic wavy liquid, floating bubbles & splash level transitions.',
        correctCode: '1234',
        icon: Icons.water_drop_outlined,
        accentColor: const Color(0xFF3B82F6),
      ),
      DemoStyle.lightning => _StyleMeta(
        name: 'Lightning Discharge',
        shortName: 'Lightning',
        tag: 'ELECTRIC',
        description:
            'Electric discharge sparks running around outline paths with energy flare feedback.',
        correctCode: '4565',
        icon: Icons.bolt_rounded,
        accentColor: const Color(0xFFF59E0B),
      ),
      DemoStyle.motion => _StyleMeta(
        name: 'Motion Elastic',
        shortName: 'Motion',
        tag: 'PHYSICS',
        description:
            'Fluid spring physics with elastic pop-in and celebration checkmark transitions.',
        correctCode: '1234',
        icon: Icons.motion_photos_on_rounded,
        accentColor: const Color(0xFF6366F1),
      ),
      DemoStyle.blob => _StyleMeta(
        name: 'Blob Chomp',
        shortName: 'Blob',
        tag: 'ARCADE',
        description:
            'A chomping blob hatches from the first slot, eats its way across the code, then morphs into the verified badge.',
        correctCode: '5454',
        icon: Icons.bubble_chart_rounded,
        accentColor: const Color(0xFFFF6A2A),
      ),
      DemoStyle.standard => _StyleMeta(
        name: 'Standard Premium',
        shortName: 'Standard',
        tag: 'CLASSIC',
        description:
            'Highly customizable digit boxes with scaling entry animations, PIN masking & loaders.',
        correctCode: '123456',
        icon: Icons.pin_outlined,
        accentColor: const Color(0xFF4F6EF7),
      ),
    };
  }
}

class _StyleMeta {
  final String name;
  final String shortName;
  final String tag;
  final String description;
  final String correctCode;
  final IconData icon;
  final Color accentColor;

  _StyleMeta({
    required this.name,
    required this.shortName,
    required this.tag,
    required this.description,
    required this.correctCode,
    required this.icon,
    required this.accentColor,
  });
}
10
likes
160
points
238
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A highly customizable, beautiful, and interactive OTP and PIN entry widget for Flutter featuring Blob, Sigil, Nexus, Lightning, Liquid, and Motion animations.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on premium_otp_input