flutter_otp_kit 3.1.1 copy "flutter_otp_kit: ^3.1.1" to clipboard
flutter_otp_kit: ^3.1.1 copied to clipboard

A comprehensive Flutter OTP verification package with SMS autofill, biometric support, performance monitoring, and extensive customization options.

example/lib/main.dart

import 'dart:developer';

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'OtpKit Examples',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const OtpKitExamplesPage(),
    );
  }
}

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

  @override
  State<OtpKitExamplesPage> createState() => _OtpKitExamplesPageState();
}

class _OtpKitExamplesPageState extends State<OtpKitExamplesPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 8, vsync: this);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('OtpKit Examples'),
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          tabs: const [
            Tab(text: 'Basic'),
            Tab(text: 'Modern'),
            Tab(text: 'Rounded'),
            Tab(text: 'Underlined'),
            Tab(text: 'Animated'),
            Tab(text: 'Custom'),
            Tab(text: 'Advanced'),
            Tab(text: 'Cursors'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: const [
          BasicExample(),
          ModernExample(),
          RoundedExample(),
          UnderlinedExample(),
          AnimatedExample(),
          CustomExample(),
          AdvancedExample(),
          CursorsExample(),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Enhanced OTP Kit - All Features & Fixes',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 16),
              const Text(
                '✅ All Critical Bug Fixes Applied:\n'
                '• Error widget displays on verification failure\n'
                '• Custom buttons trigger verification\n'
                '• Fields clear after failed attempts\n'
                '• Async operations handled properly\n'
                '• Real-time field updates\n'
                '• State management integration ready\n'
                '• Enhanced button styling with border radius control',
                style: TextStyle(fontSize: 14, color: Colors.green),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Verify Phone Number',
                subtitle: 'Enter the code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                contactInfo: '+1 (555) 123-4567',
                maskingType: MaskingType.phone,

                // Enhanced field configuration
                fieldConfig: OtpFieldConfig(
                  fieldWidth: 60,
                  fieldHeight: 60,
                  borderRadius: 12,
                  borderWidth: 2,
                  primaryColor: Colors.blue,
                  secondaryColor: Colors.grey.shade400,
                  backgroundColor: Colors.white,
                  fieldFontSize: 24,
                  fieldFontWeight: FontWeight.w600,
                  enableShadow: true,
                  shadowBlurRadius: 4,
                  shadowColor: Colors.blue.withOpacity(0.2),
                ),

                // Enhanced button styling with border radius control
                buttonBackgroundColor: Colors.blue,
                buttonTextColor: Colors.white,
                buttonBorderRadius: 25, // Fully rounded button
                buttonHeight: 50,
                buttonWidth: double.infinity,
                buttonElevation: 2,
                buttonPadding: const EdgeInsets.symmetric(vertical: 12),

                // Error configuration for field clearing
                errorConfig: const OtpErrorConfig(
                  clearFieldsOnError: true,
                  enableHapticFeedbackOnError: true,
                  errorHapticFeedbackType: ErrorHapticFeedbackType.medium,
                ),

                // Custom error widget
                errorWidget: Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: Colors.red.shade50,
                    border: Border.all(color: Colors.red, width: 1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const Icon(Icons.error_outline, color: Colors.red, size: 20),
                      const SizedBox(width: 8),
                      Text(
                        'Invalid OTP! Fields will be cleared.',
                        style: TextStyle(
                          color: Colors.red.shade700,
                          fontWeight: FontWeight.w600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),

                // Enhanced callbacks with real-time updates
                onVerify: (otp) async {
                  print('🚀 Verifying OTP: $otp');

                  // Simulate API call with delay
                  await Future.delayed(const Duration(milliseconds: 800));

                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                        behavior: SnackBarBehavior.floating,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                        behavior: SnackBarBehavior.floating,
                      ),
                    );
                    return false; // Failure - will trigger error widget and field clearing
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Code resent!'),
                      behavior: SnackBarBehavior.floating,
                    ),
                  );
                },

                // Real-time callbacks
                onChanged: (otp) {
                  print('📝 OTP changed: $otp');
                },
                onCompleted: (otp) {
                  print('✅ OTP completed: $otp');
                },
                onErrorStateChanged: (hasError) {
                  print('🚨 Error state changed: $hasError');
                },
                onValidationStateChanged: (isValid) {
                  print('✅ Validation state changed: $isValid');
                },
                onCompletionStateChanged: (isCompleted) {
                  print('🎯 Completion state changed: $isCompleted');
                },
              ),
              const SizedBox(height: 24),
              const Text(
                '🎯 Test Instructions:\n'
                '1. Enter any OTP (e.g., "5678") and click Verify\n'
                '2. Watch the error widget appear and fields clear\n'
                '3. Enter "1234" and click Verify for success\n'
                '4. Notice real-time field updates as you type\n'
                '5. Try the custom button styling and animations',
                style: TextStyle(fontSize: 14, color: Colors.blue),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Modern Design',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Verify Email',
                subtitle:
                    'Enter the verification code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldConfig: OtpFieldConfig.preset(OtpFieldPreset.modern),
                primaryColor: Colors.purple,
                successColor: Colors.green,
                contactInfo: 'john.doe@company.com',
                maskingType: MaskingType.email,
                // Rotate animation for modern example
                animationConfig: const OtpAnimationConfig(
                  enableAnimation: true,
                  enableFieldStateAnimation: true,
                  fieldFillAnimationType: FieldFillAnimationType.rotate,
                  fieldFillRotationRadians: 0.15,
                  errorFieldAnimationType: ErrorFieldAnimationType.bounce,
                  errorShakeAmplitude: 6.0,
                  enableCursorAnimation: true,
                  cursorBlinkDuration: Duration(milliseconds: 700),
                ),
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Code resent!')),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Rounded Fields',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Circular Design',
                subtitle:
                    'Enter the 4-digit code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldConfig: OtpFieldConfig.preset(OtpFieldPreset.rounded),
                primaryColor: Colors.orange,
                successColor: Colors.teal,
                backgroundColor: Colors.white,
                secondaryColor: Colors.grey,
                contactInfo: '+44 20 7946 0958',
                maskingType: MaskingType.phone,
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Code resent!')),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Underlined Style',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Underlined Style',
                subtitle:
                    'Minimal Design - Code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldConfig: OtpFieldConfig.preset(OtpFieldPreset.underlined),
                primaryColor: Colors.black,
                backgroundColor: Colors.transparent,
                contactInfo: 'admin@techstartup.io',
                maskingType: MaskingType.email,
                // Smart slide animation that detects text direction
                animationConfig: const OtpAnimationConfig(
                  enableAnimation: true,
                  enableFieldStateAnimation: true,
                  fieldFillAnimationType: FieldFillAnimationType.autoSlide,
                  // fieldFillSlideOffset: Offset(6, 0), // Optional: overrides default 6px
                  fieldTransitionDuration: Duration(milliseconds: 120),
                  fieldTransitionCurve: Curves.easeOut,
                ),
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Code resent!')),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Advanced Animations',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Animated Fields',
                subtitle:
                    'Watch the magic happen - Code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  fieldWidth: 60,
                  fieldHeight: 60,
                  borderRadius: 15,
                  borderWidth: 2,
                  enableShadow: true,
                  shadowBlurRadius: 8,
                  primaryColor: Colors.indigo,
                ),
                animationConfig: const OtpAnimationConfig(
                  animationDuration: Duration(milliseconds: 500),
                  animationCurve: Curves.easeInOutCubic,
                  fieldFillAnimationType: FieldFillAnimationType.scale,
                  errorFieldAnimationType: ErrorFieldAnimationType.bounce,
                  errorShakeAmplitude: 6.0,
                  errorShakeFrequency: 15.0,
                ),
                errorConfig: const OtpErrorConfig(
                  errorShakeEffect: true,
                  enableHapticFeedbackOnError: true,
                ),
                primaryColor: Colors.indigo,
                successColor: Colors.pink,
                contactInfo: '+33 1 42 86 83 26',
                maskingType: MaskingType.phone,
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Code resent!')),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Fully Customized',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Custom Everything',
                subtitle:
                    'Maximum customization - Code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldSpacing: 16,
                fieldConfig: OtpFieldConfig(
                  fieldWidth: 70,
                  fieldHeight: 70,
                  borderRadius: 20,
                  borderWidth: 3,
                  fieldShape: OtpFieldShape.custom,
                  enableShadow: true,
                  shadowColor: Colors.purple.withValues(alpha: 0.3),
                  shadowBlurRadius: 12,
                  shadowSpreadRadius: 2,
                  primaryColor: Colors.purple,
                  backgroundColor: Colors.purple.shade50,
                  fieldFontSize: 28,
                  fieldFontWeight: FontWeight.bold,
                  cursorColor: Colors.purple,
                  cursorWidth: 3,
                ),
                animationConfig: const OtpAnimationConfig(
                  animationDuration: Duration(milliseconds: 600),
                  animationCurve: Curves.easeOutBack,
                  fieldFillAnimationType: FieldFillAnimationType.rotate,
                  fieldFillRotationRadians: 0.15,
                  errorFieldAnimationType: ErrorFieldAnimationType.wiggle,
                  errorShakeAmplitude: 8.0,
                  errorShakeFrequency: 15.0,
                ),
                errorConfig: const OtpErrorConfig(
                  errorShakeEffect: true,
                  errorShakeDuration: Duration(milliseconds: 800),
                  errorShakeCount: 4,
                  enableHapticFeedbackOnError: true,
                  errorHapticFeedbackType: ErrorHapticFeedbackType.heavy,
                ),
                primaryColor: Colors.purple,
                secondaryColor: Colors.grey.shade600,
                backgroundColor: Colors.white,
                errorColor: Colors.red.shade600,
                contactInfo: 'support@flutter.dev',
                maskingType: MaskingType.email,
                successColor: Colors.green.shade600,
                titleStyle: const TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                  color: Colors.purple,
                ),
                subtitleStyle: TextStyle(
                  fontSize: 16,
                  color: Colors.grey.shade600,
                ),
                buttonStyle: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('✅ Success! OTP $otp is correct'),
                        backgroundColor: Colors.green,
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('❌ Error! OTP $otp is incorrect. Try 1234'),
                        backgroundColor: Colors.red,
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Code resent!'),
                      backgroundColor: Colors.purple,
                    ),
                  );
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text(
                'Advanced Features',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 32),
              OtpKit(
                title: 'Enterprise OTP',
                subtitle:
                    'All features enabled - Code sent to {contactInfo} (try 1234)',
                fieldCount: 4,
                fieldSpacing: 12,
                fieldConfig: OtpFieldConfig(
                  fieldWidth: 60,
                  fieldHeight: 60,
                  borderRadius: 15,
                  borderWidth: 2,
                  fieldShape: OtpFieldShape.roundedRectangle,
                  enableShadow: true,
                  shadowColor: Colors.indigo.withValues(alpha: 0.3),
                  shadowBlurRadius: 8,
                  primaryColor: Colors.indigo,
                  backgroundColor: Colors.indigo.shade50,
                  fieldFontSize: 24,
                  fieldFontWeight: FontWeight.w600,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableAnimation: true,
                  animationDuration: Duration(milliseconds: 400),
                  animationCurve: Curves.easeOutCubic,
                  enableFieldStateAnimation: true,
                  fieldTransitionDuration: Duration(milliseconds: 200),
                  fieldFillAnimationType: FieldFillAnimationType.scale,
                  fieldFillSlideOffset: Offset(8, 0),
                  fieldFillRotationRadians: 0.12,
                  errorFieldAnimationType: ErrorFieldAnimationType.pulse,
                  errorShakeAmplitude: 6.0,
                  errorShakeFrequency: 12.0,
                  enableCursorAnimation: true,
                  cursorBlinkDuration: Duration(milliseconds: 800),
                  cursorColor: Colors.indigo,
                  cursorWidth: 2.5,
                  enableDecorationAnimation: true,
                  decorationAnimationDuration: Duration(milliseconds: 300),
                ),
                smsConfig: const OtpSmsConfig(
                  enableSmsAutofill: true,
                  enableSmartAuth: true,
                  enableSmsRetrieverAPI: true,
                  enableSmsUserConsentAPI: true,
                  enableSmsValidation: true,
                  smsValidationRegex: r'\b\d{6}\b',
                  enableSmsErrorHandling: true,
                ),
                performanceConfig: const OtpPerformanceConfig(
                  enableLazyLoading: true,
                  maxVisibleFields: 8,
                  enableMemoryOptimization: true,
                  animationCleanupDelay: Duration(seconds: 3),
                  enableAnimationPooling: true,
                  maxAnimationPoolSize: 12,
                  enableFieldRecycling: true,
                  enableBackgroundCleanup: true,
                  cleanupInterval: Duration(minutes: 2),
                  enablePerformanceMonitoring: true,
                  enableMemoryLeakDetection: true,
                  enableAnimationOptimization: true,
                  enableWidgetOptimization: true,
                ),
                securityConfig: const OtpSecurityConfig(
                  enableRateLimiting: true,
                  maxAttemptsPerMinute: 3,
                  maxAttemptsPerHour: 15,
                  lockoutDuration: Duration(minutes: 10),
                  enableBiometricIntegration: true,
                  biometricTimeout: Duration(seconds: 20),
                  enableAdvancedValidation: true,
                  validationChecksum: false,
                  validationPattern: r'^\d{6}$',
                  enableEncryption: false,
                  enableAuditLogging: true,
                  enableSecurityMonitoring: true,
                  enableAntiTampering: false,
                  enableSessionManagement: true,
                  sessionTimeout: Duration(minutes: 5),
                ),
                errorConfig: const OtpErrorConfig(
                  maxErrorRetries: 3,
                  enableFieldLockout: true,
                  fieldLockoutDuration: Duration(seconds: 30),
                  clearFieldsOnError: true,
                  errorShakeDuration: Duration(milliseconds: 500),
                  errorShakeCount: 3,
                  errorTextMaxLines: 2,
                  showErrorIcon: true,
                  errorIcon: Icons.error_outline,
                  autoClearErrorOnInput: true,
                ),
                primaryColor: Colors.indigo,
                successColor: Colors.green,
                backgroundColor: Colors.white,
                secondaryColor: Colors.grey,
                errorColor: Colors.red,
                contactInfo: 'admin@enterprise.com',
                maskingType: MaskingType.email,
                showTimer: true,
                timerDuration: 120,
                enablePaste: true,
                autoFocus: true,
                enableAutoValidation: true,
                obscureText: false,
                enableInteractiveSelection: true,
                unfocusOnTapOutside: true,
                enableScreenReaderSupport: true,
                onVerify: (otp) async {
                  if (otp == '1234') {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content:
                            Text('✅ Enterprise Success! OTP $otp verified'),
                        backgroundColor: Colors.green,
                        duration: const Duration(seconds: 3),
                      ),
                    );
                    return true; // Success
                  } else {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text(
                            '❌ Enterprise Error! OTP $otp invalid. Try 1234'),
                        backgroundColor: Colors.red,
                        duration: const Duration(seconds: 3),
                      ),
                    );
                    return false; // Failure
                  }
                },
                onResend: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('🔄 Enterprise code resent!'),
                      backgroundColor: Colors.blue,
                      duration: Duration(seconds: 2),
                    ),
                  );
                },
                onChanged: (otp) {
                  log('OTP changed: $otp');
                },
                onCompleted: (otp) {
                  log('OTP completed: $otp');
                },
                onTimerChanged: (remaining) {
                  log('Timer: $remaining seconds remaining');
                },
                onErrorStateChanged: (hasError) {
                  log('Error state: $hasError');
                },
                onValidationStateChanged: (isValid) {
                  log('Validation state: $isValid');
                },
                onCompletionStateChanged: (isCompleted) {
                  log('Completion state: $isCompleted');
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              const Text(
                'Cursor Styles',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 16),
              const Text('System (default)'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'System Cursor',
                subtitle: 'Default platform caret',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.system,
                ),
                contactInfo: '+1 555 101 2020',
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Bar cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Bar Cursor',
                subtitle: 'Custom vertical bar with animation',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.bar,
                  cursorWidth: 2,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                  cursorBlinkDuration: Duration(milliseconds: 750),
                ),
                primaryColor: Colors.blue,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Block cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Block Cursor',
                subtitle: 'Filled block with border',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.block,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                ),
                primaryColor: Colors.deepPurple,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Underline cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Underline Cursor',
                subtitle: 'Thin underline at baseline',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.underline,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                ),
                primaryColor: Colors.teal,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Outline cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Outline Cursor',
                subtitle: 'Rounded rectangle outline',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.outline,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                ),
                primaryColor: Colors.orange,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Double bar cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Double Bar Cursor',
                subtitle: 'Two bars side by side',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.doubleBar,
                  cursorWidth: 2,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                  cursorBlinkDuration: Duration(milliseconds: 650),
                ),
                primaryColor: Colors.cyan,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Dashed underline cursor'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Dashed Underline Cursor',
                subtitle: 'Decorative dashed baseline',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.dashedUnderline,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                ),
                primaryColor: Colors.brown,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('No cursor (focus visible by border only)'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'No Cursor',
                subtitle: 'Hides caret entirely',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                  cursorStyle: CursorStyle.none,
                ),
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: false,
                ),
                primaryColor: Colors.grey,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text(
                  'Global override from OtpKit (overrides field config)'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Global Override',
                subtitle: 'OtpKit.cursorStyle wins over fieldConfig',
                fieldCount: 4,
                // Intentionally set a different style at field level
                // fieldConfig: const OtpFieldConfig(cursorStyle: CursorStyle.bar),
                cursorStyle: CursorStyle.outline,
                animationConfig: const OtpAnimationConfig(
                  enableCursorAnimation: true,
                ),
                primaryColor: Colors.black,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Beam Cap'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Beam Cap',
                subtitle: 'Vertical beam with top/bottom caps',
                fieldCount: 4,
                fieldConfig:
                    const OtpFieldConfig(cursorStyle: CursorStyle.beamCap),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.deepOrange,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Beam Notch'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Beam Notch',
                subtitle: 'Vertical beam with a center notch',
                fieldCount: 4,
                fieldConfig:
                    const OtpFieldConfig(cursorStyle: CursorStyle.beamNotch),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.green,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Wedge'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Wedge',
                subtitle: 'Small triangular insertion pointer',
                fieldCount: 4,
                fieldConfig:
                    const OtpFieldConfig(cursorStyle: CursorStyle.wedge),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.redAccent,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Ring'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Ring',
                subtitle: 'Circular ring insertion point',
                fieldCount: 4,
                fieldConfig:
                    const OtpFieldConfig(cursorStyle: CursorStyle.ring),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.indigo,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Strikethrough'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Strikethrough',
                subtitle: 'Line through the middle',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                    cursorStyle: CursorStyle.strikethrough),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.blueGrey,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Double Underline'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Double Underline',
                subtitle: 'Two lines under baseline',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                    cursorStyle: CursorStyle.doubleUnderline),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.teal,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Gradient Bar'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Gradient Bar',
                subtitle: 'Vertical gradient caret',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                    cursorStyle: CursorStyle.gradientBar, cursorWidth: 3),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.pink,
                onVerify: (_) async => true,
                onResend: () {},
              ),
              const SizedBox(height: 24),
              const Text('Glow Bar'),
              const SizedBox(height: 8),
              OtpKit(
                title: 'Glow Bar',
                subtitle: 'Beam with soft glow',
                fieldCount: 4,
                fieldConfig: const OtpFieldConfig(
                    cursorStyle: CursorStyle.glowBar, cursorWidth: 2),
                animationConfig:
                    const OtpAnimationConfig(enableCursorAnimation: true),
                primaryColor: Colors.amber,
                onVerify: (_) async => true,
                onResend: () {},
              ),
            ],
          ),
        ),
      ),
    );
  }
}
33
likes
160
points
227
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter OTP verification package with SMS autofill, biometric support, performance monitoring, and extensive customization options.

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #otp #verification #sms-autofill #biometric

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_otp_kit