...dart
class FormExample extends StatefulWidget {
const FormExample({super.key});
@override
State
class _FormExampleState extends State
final FocusNode _emailNode = FocusNode();
final FocusNode _fullnameNode = FocusNode();
final FocusNode _usernameNode = FocusNode();
final FocusNode _phoneNode = FocusNode();
@override
void dispose() {
_emailController.dispose();
_usernameController.dispose();
_fullnameController.dispose();
_phoneController.dispose();
_emailNode.dispose();
_fullnameNode.dispose();
_usernameNode.dispose();
_phoneNode.dispose();
super.dispose();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form submitted successfully')),
);
print('Email: ${_emailController.text}');
print('Username: ${_usernameController.text}');
print('Full Name: ${_fullnameController.text}');
print('Phone: ${_phoneController.text}');
} else {
// Show error message when form is invalid on submit
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please fix the errors in the form'),
backgroundColor: Colors.red,
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Custom Fields Demo')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Email Field
const Text(
'Email Field with Validation',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
EmailTextField(
controller: _emailController,
focusNode: _emailNode,
iconColor: Theme.of(context).primaryColor,
autovalidateMode: AutovalidateMode.onUserInteraction,
invalidEmailMessage: 'Please enter a valid email address',
requiredEmailMessage: 'Email is required',
),
const SizedBox(height: 16),
const Text(
'Full Name with Validation',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FullNameTextField(
focusNode: _fullnameNode,
controller: _fullnameController,
iconColor: Theme.of(context).primaryColor,
autovalidateMode: AutovalidateMode.onUserInteraction,
invalidNameMessage: 'Please enter a valid name',
requiredNameMessage: 'Full name is required',
),
const SizedBox(height: 16),
const Text(
'Phone Number',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: _phoneController,
focusNode: _phoneNode,
style: PhoneFieldStyle.dropdown,
isShowError: true,
),
const SizedBox(height: 15),
const Text(
'Fixed Country Code Style',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: TextEditingController(),
focusNode: FocusNode(),
style: PhoneFieldStyle.simple,
isShowError: true,
),
const SizedBox(height: 15),
const Text(
'Phone number with Icons',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: TextEditingController(),
focusNode: FocusNode(),
style: PhoneFieldStyle.withIcons,
isShowError: true,
),
const SizedBox(height: 15),
const Text(
'Phone number with Normal',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: TextEditingController(),
focusNode: FocusNode(),
style: PhoneFieldStyle.integrated,
isShowError: true,
),
const SizedBox(height: 15),
const Text(
'User Name TextField',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
UsernameTextfield(
controller: _usernameController,
focusNode: _usernameNode,
autovalidateMode: AutovalidateMode.onUserInteraction,
patternErrorMessage:
'Username must be 3-20 characters and can only contain letters, numbers, and underscore',
),
const SizedBox(height: 24),
const Text(
'OTP Field',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
SizedBox(width: double.infinity, child: AdvancedOTPDemoScreen()),
// AdvancedOTPField(
// length: 6,
// onCompleted: (value) {
// print('Completed: $value');
// },
// fieldStyle: const OTPFieldStyle(
// width: 45,
// height: 55,
// margin: EdgeInsets.symmetric(horizontal: 4),
// textStyle: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// ),
// ),
// fieldDecoration: const OTPFieldDecoration(
// borderRadius: BorderRadius.all(Radius.circular(12)),
// borderWidth: 1.5,
// ),
// ),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: const Text('Submit'),
),
],
),
),
),
);
}
}
|
...dart
class PhonenumberTf extends StatefulWidget {
const PhonenumberTf({super.key});
@override
State
class _PhonenumberTfState extends State
const SizedBox(height: 15),
const Text(
'Fixed Country Code Style ',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: _phoneController_fixedCode,
onSubmitted: () {
print(
'Flexible Phone Number in Fixed Country code: ${_phoneController_fixedCode.text}',
);
},
focusNode: phonefixedfocus,
hintText: "Enter your mobile number",
countryCode: "+91",
style: PhoneFieldStyle.integrated,
),
// FlexiblePhoneField(
// controller: _phoneController_fixedCode,
// initialCountryCode: '+91',
// hint: 'Enter your mobile number',
// displayMode: PhoneFieldDisplayMode.fixed,
// separator: "",
// maxLength: 10,
// onPhoneChanged: (fullPhoneNumber, phoneNumber, countryCode) {
// setState(() {
// print(
// 'Flexible Phone Number in Fixed Country code: $fullPhoneNumber, $phoneNumber, $countryCode',
// );
// });
// },
// ),
const SizedBox(height: 15),
const Text(
'Phone number with Icons',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: _phoneController_icon,
onSubmitted: () {
print(
'Flexible Phone Number in ICON Country code: ${_phoneController_icon.text}',
);
},
focusNode: phoneIconFocus,
hintText: "Enter your mobile number",
countryCode: "+91",
style: PhoneFieldStyle.withIcons,
),
const Text(
'Phone number with Normal',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
FlexiblePhoneField(
controller: _phoneController_normal,
onSubmitted: () {
setState(() {
print(
'Flexible Phone Number in Normal Country code: ${_phoneController_normal.text}',
);
});
},
focusNode: phoneNormalFocus,
hintText: "Enter your mobile number",
countryCode: "+91",
style: PhoneFieldStyle.simple,
),
// FlexiblePhoneField(
// regexPattern: r'^[6-9]\d{9}$',
// controller: _phoneController_icon,
// hint: 'Enter your phone number',
// label: 'Mobile Number',
// maxLength: 10,
// displayMode: PhoneFieldDisplayMode.phoneOnly,
// separator: "",
// decoration: InputDecoration(
// labelText: 'Mobile Number',
// hintText: 'Mobile number (10 digits)',
// prefixIcon: const Icon(Icons.smartphone),
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(12),
// ),
// filled: true,
// fillColor: Colors.grey.shade100,
// ),
// onPhoneChanged: (fullPhoneNumber, phoneNumber, countryCode) {
// setState(() {
// print(
// 'Flexible Phone Number only: $fullPhoneNumber, $phoneNumber, $countryCode',
// );
// });
// },
// ),
],
),
);
}
}
|
class AdvancedOTPDemoScreen extends StatefulWidget {
const AdvancedOTPDemoScreen({super.key});
@override
State
class _AdvancedOTPDemoScreenState extends State
// Configuration options
OTPAnimationType _animationType = OTPAnimationType.scale;
OTPInputType _inputType = OTPInputType.numeric;
String _placeholderText = '*';
bool _obscureText = false;
bool _enableActiveFill = true;
@override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {
_isFocused = _focusNode.hasFocus;
});
});
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final availableWidth = screenWidth - (24 * 2);
final fieldWidth = (availableWidth - (24 * 3)) / 4;
return SingleChildScrollView(
padding: const EdgeInsets.all(0.0),
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
_isFocused = true;
_focusNode.requestFocus();
});
},
child: AdvancedOTPField(
key: _otpFieldKey,
length: 4,
focusNode: _focusNode,
inputType: _inputType,
animationType: _animationType,
obscureText: _obscureText,
enableActiveFill: _enableActiveFill,
autoFocus: false,
fieldStyle: OTPFieldStyle(
width: fieldWidth,
height: 70,
margin: const EdgeInsets.symmetric(horizontal: 10),
placeholderText: _placeholderText,
textStyle: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
placeholderStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w300,
color: Colors.grey[400],
),
showCursor: _isFocused,
cursorColor: Colors.blue,
cursorWidth: 2,
cursorHeight: 32,
showPlaceholderOnlyForEmptyBox: true,
),
fieldDecoration: OTPFieldDecoration(
borderRadius: BorderRadius.circular(12),
borderWidth: 1.5,
activeColor:
Colors
.grey[300], // _isFocused ? Colors.blue :Colors.grey[300],
inactiveColor: Colors.grey[300],
selectedColor: _isFocused ? Colors.blue : Colors.grey[300],
errorColor: Colors.red[400],
backgroundColor: Colors.grey[50],
boxShadow: BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
),
errorText: _errorMessage,
onChanged: (value) {
setState(() {
_enteredOTP = value;
_errorMessage = null;
_activeIndex = value.length;
if (_isFirstInput && value.isNotEmpty) {
_isFirstInput = false;
}
});
},
onCompleted: (value) {
_verifyOTP(value);
},
),
),
const SizedBox(height: 32),
_buildVerifyButton(),
// const SizedBox(height: 24),
// _buildActionButtons(),
],
),
);
}
Widget _buildVerifyButton() {
return SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed:
_enteredOTP.length == 4 &&
!_isVerifying // Enable when 4 digits entered
? () => _verifyOTP(_enteredOTP)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 0,
shadowColor: Theme.of(context).colorScheme.primary.withOpacity(0.3),
),
child:
_isVerifying
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation
Widget _buildActionButtons() {
return Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
_otpFieldKey.currentState?.clear();
setState(() {
_enteredOTP = '';
_errorMessage = null;
_activeIndex = 0;
});
},
icon: const Icon(Icons.refresh_rounded),
label: const Text('Clear'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
_showSnackBar('New OTP sent successfully!', Colors.green);
_otpFieldKey.currentState?.clear();
setState(() {
_activeIndex = 0;
});
},
icon: const Icon(Icons.send_rounded),
label: const Text('Resend'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
],
);
}
Widget _buildFeaturesDemo() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Demo Features',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildFeatureChip('Auto-fill Support', Icons.auto_fix_high),
_buildFeatureChip('Paste Support', Icons.content_paste),
_buildFeatureChip('Custom Animations', Icons.animation),
_buildFeatureChip('Error Handling', Icons.error_outline),
_buildFeatureChip('Customizable UI', Icons.palette),
_buildFeatureChip('Accessibility', Icons.accessibility),
],
),
],
),
);
}
Widget _buildFeatureChip(String label, IconData icon) {
return Chip(
avatar: Icon(icon, size: 16),
label: Text(label, style: const TextStyle(fontSize: 12)),
backgroundColor: Theme.of(
context,
).colorScheme.primaryContainer.withOpacity(0.3),
side: BorderSide.none,
);
}
void _showConfigDialog() {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('Configuration'),
content: StatefulBuilder(
builder:
(context, setDialogState) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Animation Type
const Text(
'Animation Type:',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
DropdownButton
// Input Type
const Text(
'Input Type:',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
DropdownButton<OTPInputType>(
value: _inputType,
isExpanded: true,
items:
OTPInputType.values.map((type) {
return DropdownMenuItem(
value: type,
child: Text(type.name.toUpperCase()),
);
}).toList(),
onChanged: (value) {
setDialogState(() {
_inputType = value!;
});
},
),
const SizedBox(height: 16),
// Placeholder Text
const Text(
'Placeholder:',
style: TextStyle(fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
TextFormField(
initialValue: _placeholderText,
decoration: const InputDecoration(
hintText: 'Enter placeholder character',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
maxLength: 1,
onChanged: (value) {
setDialogState(() {
_placeholderText = value;
});
},
),
const SizedBox(height: 16),
// Toggle Options
SwitchListTile(
title: const Text('Obscure Text'),
value: _obscureText,
onChanged: (value) {
setDialogState(() {
_obscureText = value;
});
},
contentPadding: EdgeInsets.zero,
),
SwitchListTile(
title: const Text('Active Fill'),
value: _enableActiveFill,
onChanged: (value) {
setDialogState(() {
_enableActiveFill = value;
});
},
contentPadding: EdgeInsets.zero,
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
setState(() {
// Apply changes
});
Navigator.pop(context);
_otpFieldKey.currentState?.clear();
},
child: const Text('Apply'),
),
],
),
);
}
void _verifyOTP(String otp) {
setState(() {
_isVerifying = true;
_errorMessage = null;
});
// Simulate API call
Future.delayed(const Duration(seconds: 2), () {
setState(() {
_isVerifying = false;
});
if (otp == _correctOTP) {
_showSnackBar('OTP verified successfully!', Colors.green);
} else {
setState(() {
_errorMessage = 'Invalid OTP. Please try again.';
});
_otpFieldKey.currentState?.clear(); // Clear the field on invalid OTP
_activeIndex = 0;
_otpFieldKey.currentState?.animateError(); // Show error animation
}
});
}
void _showSnackBar(String message, Color color) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Icon(
color == Colors.green ? Icons.check_circle : Icons.error,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
Expanded(child: Text(message)),
],
),
backgroundColor: color,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
margin: const EdgeInsets.all(16),
),
);
}
}
|