290 lines
9.9 KiB
Dart
290 lines
9.9 KiB
Dart
// lib/presentation/pages/otp_page.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();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
for (final node in _focusNodes) {
|
||
node.dispose();
|
||
}
|
||
for (final controller in _controllers) {
|
||
controller.dispose();
|
||
}
|
||
_otpTimer.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
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 ??
|
||
AppColors.grey),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
borderSide: BorderSide(
|
||
color: _hasError ? Colors.red : AppColors.primary,
|
||
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;
|
||
});
|
||
},
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
);
|
||
}
|
||
|
||
@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,
|
||
),
|
||
),
|
||
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
SvgPicture.asset(Assets.icons.vector.path),
|
||
Center(
|
||
child: TextButton(
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
child: const Text("ویرایش شماره همراه"),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
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: 40),
|
||
|
||
BlocConsumer<AuthBloc, AuthState>(
|
||
listener: (context, state) {
|
||
if (state is AuthLoading) {
|
||
setState(() {
|
||
_hasError = false;
|
||
_errorMessage = null;
|
||
});
|
||
} else if (state is AuthFailure) {
|
||
setState(() {
|
||
_hasError = true;
|
||
_errorMessage = state.message;
|
||
});
|
||
} else if (state is AuthVerified) {
|
||
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("ارسال مجدد کد"),
|
||
)
|
||
: ValueListenableBuilder<int>(
|
||
valueListenable: _otpTimer.remainingSeconds,
|
||
builder:
|
||
(context, seconds, child) => Text(
|
||
"${_otpTimer.formatTime()} تا دریافت مجدد",
|
||
style: const TextStyle(
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _verifyOtp() {
|
||
final otpCode = _controllers.map((c) => c.text).join();
|
||
// یک بررسی اضافه میکنیم که اگر دکمه فعال بود حتما کد کامل باشد
|
||
if (otpCode.length == 5) {
|
||
context.read<AuthBloc>().add(VerifyOtpEvent(otpCode));
|
||
}
|
||
}
|
||
|
||
void _resendOtp() {
|
||
setState(() {
|
||
_hasError = false;
|
||
_errorMessage = null;
|
||
});
|
||
context.read<AuthBloc>().add(SendOtpEvent(widget.phoneNumber));
|
||
_otpTimer.resetTimer();
|
||
}
|
||
}
|