amoriai/lib/widgets/auth/verification_code_input.dart

152 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../core/theme/app_theme.dart';
const Color primaryBrown = Color(0xFF4A3C3C);
/// Reusable 5-digit verification code input widget
class VerificationCodeInput extends StatefulWidget {
final List<TextEditingController> controllers;
final bool isError;
final VoidCallback? onErrorReset;
final bool enabled;
const VerificationCodeInput({
super.key,
required this.controllers,
this.isError = false,
this.onErrorReset,
this.enabled = true,
});
@override
State<VerificationCodeInput> createState() => _VerificationCodeInputState();
/// Get the combined code from all controllers
String get code => controllers.map((c) => c.text).join();
/// Check if all 5 digits are entered
bool get isComplete => code.length == 5;
}
class _VerificationCodeInputState extends State<VerificationCodeInput> {
late List<FocusNode> _focusNodes;
@override
void initState() {
super.initState();
_focusNodes = List.generate(5, (index) => FocusNode());
}
@override
void dispose() {
for (var node in _focusNodes) {
node.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
5,
(index) => SizedBox(
width: 50,
height: 50,
child: RawKeyboardListener(
focusNode: FocusNode(), // Separate dummy node for listener
onKey: (event) {
// Handle backspace on empty field to move focus back
if (event is RawKeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.backspace) {
if (widget.controllers[index].text.isEmpty && index > 0) {
_focusNodes[index - 1].requestFocus();
}
}
},
child: TextField(
controller: widget.controllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
enabled: widget.enabled,
style: const TextStyle(
fontFamily: 'Poppins',
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryBrown,
),
decoration: InputDecoration(
counterText: "",
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: widget.isError
? AppTheme.error
: primaryBrown.withOpacity(0.3),
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: widget.isError
? AppTheme.error
: primaryBrown.withOpacity(0.3),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: widget.isError ? AppTheme.error : primaryBrown,
width: 2,
),
),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: primaryBrown.withOpacity(0.2)),
),
),
onChanged: (val) {
// Reset error state when user types
if (widget.isError && widget.onErrorReset != null) {
widget.onErrorReset!();
}
if (val.isNotEmpty) {
// Move to next field if current is filled
if (index < 4) {
_focusNodes[index + 1].requestFocus();
} else if (index == 4) {
// Hide keyboard on last field if filled
FocusScope.of(context).unfocus();
}
} else {
// Move to previous field if current is cleared
if (index > 0) {
_focusNodes[index - 1].requestFocus();
}
}
},
),
),
),
),
);
}
/// Clear all controllers
void clear() {
for (var c in widget.controllers) {
c.clear();
}
// Return focus to first field after clearing
if (_focusNodes.isNotEmpty) {
_focusNodes[0].requestFocus();
}
}
}