amoriai/lib/widgets/auth/verification_step_widget.dart

176 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import 'verification_code_input.dart';
const Color primaryBrown = Color(0xFF4A3C3C);
/// Complete verification step widget with OTP code input
class VerificationStepWidget extends StatelessWidget {
/// Controllers for the 5-digit code input
final List<TextEditingController> codeControllers;
/// Whether code verification failed
final bool isError;
/// Callback when error should be reset (user starts typing)
final VoidCallback? onErrorReset;
/// Remaining seconds for timer (0 = show resend button)
final int remainingSeconds;
/// Formatted time string (e.g., "3.25")
final String formattedTime;
/// Loading state
final bool isLoading;
/// Callback when verify button pressed
final VoidCallback? onVerify;
/// Callback when resend code button pressed
final VoidCallback? onResend;
/// Callback when "Ganti Email" button pressed
final VoidCallback? onChangeEmail;
/// Image path for normal state
final String normalImagePath;
/// Image path for error state
final String errorImagePath;
/// Builder for the verify button (allows custom gradient button)
final Widget Function(String text, VoidCallback? onPressed, bool isLoading)
buttonBuilder;
const VerificationStepWidget({
super.key,
required this.codeControllers,
required this.isError,
this.onErrorReset,
required this.remainingSeconds,
required this.formattedTime,
required this.isLoading,
this.onVerify,
this.onResend,
this.onChangeEmail,
this.normalImagePath = 'assets/images/registerletter1.gif',
this.errorImagePath = 'assets/images/registerletter2.gif',
required this.buttonBuilder,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Animated Image based on error state
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Image.asset(
isError ? errorImagePath : normalImagePath,
key: ValueKey<bool>(isError),
height: 180,
fit: BoxFit.contain,
),
),
),
// Timer or Resend Button
if (remainingSeconds > 0)
Text(
formattedTime,
style: const TextStyle(
fontFamily: 'Poppins',
fontSize: 16,
fontWeight: FontWeight.w600,
color: primaryBrown,
),
)
else
TextButton.icon(
onPressed: isLoading ? null : onResend,
icon: const Icon(Icons.refresh, color: primaryBrown),
label: const Text(
'Kirim Ulang Kode',
style: TextStyle(
fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
color: primaryBrown,
),
),
),
const SizedBox(height: 16),
// Instruction text
const Text(
'Masukkan kode verifikasi yang dikirim ke email Anda',
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
color: primaryBrown,
),
),
const SizedBox(height: 24),
// Code Input
VerificationCodeInput(
controllers: codeControllers,
isError: isError,
onErrorReset: onErrorReset,
enabled: !isLoading,
),
// Error Message
if (isError) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
code.length < 5
? 'Masukkan 5 digit kode verifikasi'
: 'Kode verifikasi salah atau sudah expired!',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.red,
fontSize: 12,
fontFamily: 'Poppins',
),
),
),
],
const SizedBox(height: 30),
// Verify Button
buttonBuilder('Verifikasi', isLoading ? null : onVerify, isLoading),
// Change Email Button
if (onChangeEmail != null) ...[
const SizedBox(height: 16),
TextButton(
onPressed: onChangeEmail,
child: const Text(
'Ganti Metode',
style: TextStyle(color: Colors.grey, fontFamily: 'Poppins'),
),
),
],
],
);
}
/// Get the combined code from all controllers
String get code => codeControllers.map((c) => c.text).join();
/// Check if all 5 digits are entered
bool get isComplete => code.length == 5;
}