proxibuy/lib/presentation/pages/otp_page.dart

267 lines
9.3 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:proxibuy/presentation/auth/bloc/auth_bloc.dart';
import 'package:proxibuy/presentation/pages/user_info_page.dart';
import '../../core/config/app_colors.dart';
import '../../core/gen/assets.gen.dart';
import '../utils/otp_timer_helper.dart';
class OtpPage extends StatefulWidget {
final String phoneNumber;
const OtpPage({super.key, required this.phoneNumber});
@override
State<OtpPage> createState() => _OtpPageState();
}
class _OtpPageState extends State<OtpPage> {
final List<FocusNode> _focusNodes = List.generate(5, (_) => FocusNode());
final List<TextEditingController> _controllers =
List.generate(5, (_) => TextEditingController());
late final OtpTimerHelper _otpTimer;
bool _hasError = false;
String? _errorMessage;
bool _isOtpComplete = false;
@override
void initState() {
super.initState();
_otpTimer = OtpTimerHelper()..startTimer();
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context).requestFocus(_focusNodes[0]);
});
}
@override
void dispose() {
for (final node in _focusNodes) {
node.dispose();
}
for (final controller in _controllers) {
controller.dispose();
}
_otpTimer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Assets.icons.logo.svg(height: 160)),
const SizedBox(height: 40),
Text(
"کد یکبار مصرف",
style: textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
"کد تایید به شماره ${widget.phoneNumber} ارسال شد.",
style: textTheme.titleMedium?.copyWith(
color: Colors.grey,
height: 1.5,
),
),
SizedBox(height: 15,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.icons.vector.path),
const SizedBox(width: 4),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("ویرایش شماره همراه",style: TextStyle(color: AppColors.active),),
),
],
),
const SizedBox(height: 15),
_buildOtpFields(),
const SizedBox(height: 15),
if (_errorMessage != null)
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Center(
child: Text(
_errorMessage!,
style: const TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
),
)
else
const SizedBox(height: 32),
BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
setState(() {
_hasError = true;
_errorMessage = state.message;
});
}
if (state is AuthVerified) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (_) => const UserInfoPage(),
),
(route) => false,
);
}
},
builder: (context, state) {
if (state is AuthLoading) {
return const Center(child: CircularProgressIndicator());
}
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _isOtpComplete ? _verifyOtp : null,
child: const Text("ورود"),
),
);
},
),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.icons.clock.path,
colorFilter: const ColorFilter.mode(
Colors.grey,
BlendMode.srcIn,
),
),
const SizedBox(width: 8),
ValueListenableBuilder<bool>(
valueListenable: _otpTimer.canResend,
builder: (context, canResend, child) {
return canResend
? TextButton(
onPressed: _resendOtp,
child: const Text("ارسال مجدد کد",style: TextStyle(color: AppColors.active),),
)
: ValueListenableBuilder<int>(
valueListenable: _otpTimer.remainingSeconds,
builder: (context, seconds, child) => Text(
"${_otpTimer.formatTime()} تا دریافت مجدد",
style: const TextStyle(color: Colors.grey),
),
);
},
),
],
),
],
),
),
),
),
);
}
Widget _buildOtpFields() {
return Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(5, (index) {
return SizedBox(
width: 60,
height: 60,
child: TextField(
controller: _controllers[index],
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: _hasError ? Colors.red : Colors.black,
),
decoration: InputDecoration(
counterText: '',
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: _hasError
? Colors.red
: (Theme.of(context)
.inputDecorationTheme
.enabledBorder
?.borderSide
.color ??
Colors.grey),
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: _hasError ? Colors.red : AppColors.active,
width: 2,
),
),
),
onChanged: (value) {
if (_hasError) {
setState(() {
_hasError = false;
_errorMessage = null;
});
}
if (value.length == 1 && index < 4) {
FocusScope.of(context).requestFocus(_focusNodes[index + 1]);
}
if (value.isEmpty && index > 0) {
FocusScope.of(context).requestFocus(_focusNodes[index - 1]);
}
final otpCode = _controllers.map((c) => c.text).join();
setState(() {
_isOtpComplete = otpCode.length == 5;
});
},
),
);
}),
),
);
}
void _verifyOtp() {
final otpCode = _controllers.map((c) => c.text).join();
if (otpCode.length == 5) {
context.read<AuthBloc>().add(VerifyOTPEvent(otp: otpCode));
}
}
void _resendOtp() {
setState(() {
_hasError = false;
_errorMessage = null;
for (var controller in _controllers) {
controller.clear();
}
_isOtpComplete = false;
});
context.read<AuthBloc>().add(SendOTPEvent(phoneNumber: widget.phoneNumber));
_otpTimer.resetTimer();
}
}