OnboardingPozoAppv1/flutter_app/lib/screens/otp_verification_screen.dart

383 lines
15 KiB
Dart
Raw Normal View History

import 'dart:async';
import 'package:flutter/material.dart';
import '../theme.dart';
import 'business_setup_screen.dart';
class OtpVerificationScreen extends StatefulWidget {
final String mobileNumber;
const OtpVerificationScreen({super.key, required this.mobileNumber});
@override
State<OtpVerificationScreen> createState() => _OtpVerificationScreenState();
}
class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
late Timer _timer;
int _timeLeft = 45;
final List<TextEditingController> _controllers = List.generate(6, (_) => TextEditingController());
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
@override
void initState() {
super.initState();
_startCountdown();
}
void _startCountdown() {
_timeLeft = 45;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_timeLeft <= 0) {
_timer.cancel();
} else {
setState(() {
_timeLeft--;
});
}
});
}
@override
void dispose() {
_timer.cancel();
for (var controller in _controllers) {
controller.dispose();
}
for (var node in _focusNodes) {
node.dispose();
}
super.dispose();
}
String _getFormattedTime() {
return '00:${_timeLeft.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final padding = context.responsivePadding;
final displayMobile = widget.mobileNumber.length == 10
? '+91 ${widget.mobileNumber.substring(0, 5)} ${widget.mobileNumber.substring(5)}'
: '+91 ${widget.mobileNumber}';
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Color(0xFFBA1A20)),
onPressed: () => Navigator.pop(context),
),
title: Text(
'PozoApp',
style: theme.textTheme.headlineMedium?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
actions: [
Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: Center(
child: Text(
'Step 2.5 of 5',
style: theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
)
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(6),
child: Row(
children: List.generate(5, (index) {
return Expanded(
child: Container(
height: 6,
margin: const EdgeInsets.symmetric(horizontal: 2.0),
decoration: BoxDecoration(
color: index <= 1
? theme.colorScheme.primary
: (index == 2 ? theme.colorScheme.primary.withOpacity(0.5) : theme.colorScheme.surfaceContainerHighest),
borderRadius: BorderRadius.circular(3),
),
),
);
}),
),
),
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Alert envelope pulse symbol
Center(
child: Container(
width: 96,
height: 96,
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
shape: BoxShape.circle,
),
child: Center(
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
)
],
),
child: Icon(
Icons.sms,
color: theme.colorScheme.primary,
size: 38,
),
),
),
),
),
const SizedBox(height: 16),
Text(
'Verifying your number',
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"Sent code to $displayMobile",
style: theme.textTheme.bodyMedium,
),
const SizedBox(width: 8),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Text(
'Edit',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
const SizedBox(height: 16),
// 6 Custom Inputs Fields
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(11, (index) {
if (index.isOdd) {
return const SizedBox(width: 8);
}
final fieldIndex = index ~/ 2;
return SizedBox(
width: 44,
child: TextField(
controller: _controllers[fieldIndex],
focusNode: _focusNodes[fieldIndex],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
decoration: InputDecoration(
counterText: '',
contentPadding: const EdgeInsets.symmetric(vertical: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.colorScheme.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: theme.colorScheme.primary, width: 2),
),
),
onChanged: (value) {
if (value.isNotEmpty && fieldIndex < 5) {
_focusNodes[fieldIndex + 1].requestFocus();
}
},
),
);
}),
),
const SizedBox(height: 16),
// Auto detect loader
Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
),
),
const SizedBox(width: 12),
Text(
'Auto-detecting OTP...',
style: theme.textTheme.bodySmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(height: 16),
// Resending timer action column
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.schedule, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Text(
_timeLeft > 0
? 'Resend code in ${_getFormattedTime()}'
: 'Resend Code via SMS',
style: theme.textTheme.bodyMedium,
),
],
),
const SizedBox(height: 16),
// WhatsApp custom CTA button
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
side: const BorderSide(color: Color(0xFFE2E2E2)),
elevation: 1,
minimumSize: const Size(200, 48),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
'Resend via WhatsApp',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
],
),
)
],
),
),
),
),
),
),
// Pinned Bottom Dock
Container(
height: 80,
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey[200]!)),
),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFooterAction(Icons.help_outline, 'Help'),
_buildFooterAction(Icons.save, 'Save Draft'),
TextButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BusinessSetupScreen(),
),
);
},
icon: const Text(
'Verify Manually',
style: TextStyle(fontWeight: FontWeight.bold),
),
label: const Icon(Icons.arrow_forward, size: 16),
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: theme.colorScheme.primary,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
)
],
),
),
),
),
)
],
),
),
);
}
Widget _buildFooterAction(IconData icon, String label) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Colors.grey[600], size: 22),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
)
],
);
}
}