proxibuy_bussiness/lib/presentation/pages/otp_page.dart

307 lines
11 KiB
Dart

import 'package:business_panel/core/config/app_colors.dart';
import 'package:business_panel/gen/assets.gen.dart';
import 'package:business_panel/presentation/auth/bloc/auth_bloc.dart';
import 'package:business_panel/presentation/pages/store_info.dart';
import 'package:business_panel/presentation/store_info/bloc/store_info_bloc.dart';
import 'package:business_panel/presentation/utils/otp_timer_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.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(
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: SvgPicture.asset(Assets.icons.logo, height: 160)),
SizedBox(height: 15),
Center(
child: Text(
"پنل فروشگاهی شما",
style: TextStyle(color: AppColors.active),
),
),
const SizedBox(height: 40),
Text(
"کد یکبار مصرف",
style: textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text.rich(
TextSpan(
style: textTheme.titleMedium?.copyWith(
color: Colors.grey,
height: 1.5,
),
children: <TextSpan>[
const TextSpan(
text: 'کد تایید به شماره ',
style: TextStyle(fontSize: 15),
),
TextSpan(
text: widget.phoneNumber,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const TextSpan(
text: ' ارسال شد.',
style: TextStyle(fontSize: 15),
),
],
),
textDirection: TextDirection.rtl,
),
SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(Assets.icons.vector),
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:
(_) => BlocProvider(
create: (context) => StoreInfoBloc(),
child: const StoreInfoPage(),
),
),
(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,
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();
}
}