premium_otp_input 0.0.4
premium_otp_input: ^0.0.4 copied to clipboard
A highly customizable, beautiful, and interactive OTP and PIN entry widget for Flutter featuring rich animations, verification loaders, and secure masking.
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 StatelessWidget {
/// Creates the [MyApp] widget.
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Premium OTP Input Demo',
theme: ThemeData.dark(useMaterial3: true).copyWith(
scaffoldBackgroundColor: const Color(0xFF0F172A),
colorScheme: const ColorScheme.dark(
primary: Color(0xFFF97316),
surface: Color(0xFF1E293B),
),
),
home: const OtpDemoScreen(),
);
}
}
/// A screen showcasing the different interactive features of the [PremiumOtpInput] widget.
class OtpDemoScreen extends StatefulWidget {
/// Creates the [OtpDemoScreen] widget.
const OtpDemoScreen({super.key});
@override
State<OtpDemoScreen> createState() => _OtpDemoScreenState();
}
class _OtpDemoScreenState extends State<OtpDemoScreen> {
final TextEditingController _otpController = TextEditingController();
final FocusNode _otpFocusNode = FocusNode();
bool _isVerifying = false;
bool _isSuccess = false;
bool _isError = false;
OtpEntryAnimationStyle _entryAnimationStyle = OtpEntryAnimationStyle.scale;
OtpSuccessAnimationStyle _successAnimationStyle = OtpSuccessAnimationStyle.bounce;
bool _obscureText = false;
String _obscuringCharacter = '●';
void _handleOtpCompleted(String value) async {
setState(() {
_isVerifying = true;
_isError = false;
});
// Simulate verification delay
await Future.delayed(const Duration(milliseconds: 1500));
if (!mounted) return;
if (value == "123456") {
setState(() {
_isVerifying = false;
_isSuccess = true;
});
} else {
setState(() {
_isVerifying = false;
_isError = true;
});
// Automatically clear the error state after 2 seconds
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
setState(() {
_isError = false;
_otpController.clear();
_otpFocusNode.requestFocus();
});
}
}
void _reset() {
setState(() {
_isVerifying = false;
_isSuccess = false;
_isError = false;
_otpController.clear();
_otpFocusNode.requestFocus();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Premium OTP Input'),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Info / Status banner
Card(
color: const Color(0xFF1E293B),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text(
'Interactive Demo App',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFFF97316),
),
),
const SizedBox(height: 8),
Text(
_isSuccess
? 'Verification Successful!'
: _isError
? 'Verification Failed (Try again)'
: _isVerifying
? 'Verifying code...'
: 'Enter "123456" for success, any other code for error.',
textAlign: TextAlign.center,
style: TextStyle(
color: _isSuccess
? const Color(0xFF22C55E)
: _isError
? const Color(0xFFEF5350)
: Colors.white70,
),
),
],
),
),
),
const SizedBox(height: 32),
// 2. The OTP Input component
PremiumOtpInput(
length: 6,
controller: _otpController,
focusNode: _otpFocusNode,
isSuccess: _isSuccess,
isError: _isError,
isVerifying: _isVerifying,
onCompleted: _handleOtpCompleted,
obscureText: _obscureText,
obscuringCharacter: _obscuringCharacter,
entryAnimationStyle: _entryAnimationStyle,
successAnimationStyle: _successAnimationStyle,
animateActiveBorder: true,
boxHeight: 64.0,
spacing: 12.0,
borderRadius: 16.0,
activeBorderColor: const Color(0xFFF97316),
defaultBorderColor: Colors.white.withValues(alpha: 0.12),
successColor: const Color(0xFF22C55E),
errorColor: const Color(0xFFEF5350),
),
const SizedBox(height: 32),
// 3. Actions / Controls
if (_isSuccess || _isError || _isVerifying)
Center(
child: TextButton.icon(
onPressed: _reset,
icon: const Icon(Icons.refresh),
label: const Text('Reset Input'),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFFF97316),
),
),
),
const SizedBox(height: 24),
// 4. Configuration Controls Card
Card(
color: const Color(0xFF1E293B),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Customize Settings',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const Divider(height: 24, color: Colors.white10),
// Entry Animation Style
const Text('Digit Entry Animation', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 8),
Wrap(
spacing: 8.0,
children: OtpEntryAnimationStyle.values.map((style) {
return ChoiceChip(
label: Text(style.name),
selected: _entryAnimationStyle == style,
onSelected: (selected) {
if (selected) {
setState(() => _entryAnimationStyle = style);
}
},
);
}).toList(),
),
const SizedBox(height: 20),
// Success Animation Style
const Text('Success State Transition', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 8),
Wrap(
spacing: 8.0,
children: OtpSuccessAnimationStyle.values.map((style) {
return ChoiceChip(
label: Text(style.name),
selected: _successAnimationStyle == style,
onSelected: (selected) {
if (selected) {
setState(() => _successAnimationStyle = style);
}
},
);
}).toList(),
),
const SizedBox(height: 20),
// Obscure text & character selection
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Obscure / Hide Text', style: TextStyle(color: Colors.white70)),
Switch(
value: _obscureText,
activeThumbColor: const Color(0xFFF97316),
onChanged: (val) {
setState(() => _obscureText = val);
},
),
],
),
if (_obscureText) ...[
const SizedBox(height: 12),
const Text('Obscuring Character', style: TextStyle(color: Colors.white70)),
const SizedBox(height: 8),
Wrap(
spacing: 12.0,
children: ['●', '*', '★', '♥'].map((char) {
return ChoiceChip(
label: Text(char),
selected: _obscuringCharacter == char,
onSelected: (selected) {
if (selected) {
setState(() => _obscuringCharacter = char);
}
},
);
}).toList(),
),
],
],
),
),
),
],
),
),
),
);
}
}