From eb7459c13fed0b3dd14e62616f9879180be73019 Mon Sep 17 00:00:00 2001 From: jahongireshonqulov Date: Wed, 29 Oct 2025 20:20:06 +0500 Subject: [PATCH] feat: sending otp and resend otp done --- lib/core/constants/time_delay_cons.dart | 1 + lib/core/di/injection_container.config.dart | 18 ++ lib/core/helpers/time_formatters.dart | 8 + lib/core/router/app_routes.dart | 12 +- .../verify_otp_code_login_usecase.dart | 10 +- .../verify_otp_bloc/verify_otp_bloc.dart | 138 ++++++++++++++ .../verify_otp_bloc/verify_otp_event.dart | 19 ++ .../verify_otp_bloc/verify_otp_state.dart | 10 + .../verify_phone_bloc/verify_phone_bloc.dart | 60 ++++++ .../verify_phone_bloc/verify_phone_event.dart | 14 ++ .../verify_phone_bloc/verify_phone_state.dart | 8 + .../pages/login_page/widgets/login_body.dart | 5 +- .../pages/register_page/register_page.dart | 8 +- .../widgets/w_register_body.dart | 5 +- .../reset_password_page.dart | 4 +- .../verify_otp_code_page.dart | 176 +++++++++++++++++- .../verify_phone_page.dart | 156 ++++++++++------ pubspec.lock | 16 ++ pubspec.yaml | 1 + 19 files changed, 591 insertions(+), 78 deletions(-) create mode 100644 lib/core/helpers/time_formatters.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_event.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_state.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_event.dart create mode 100644 lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_state.dart diff --git a/lib/core/constants/time_delay_cons.dart b/lib/core/constants/time_delay_cons.dart index 20f86b4..ba78c74 100644 --- a/lib/core/constants/time_delay_cons.dart +++ b/lib/core/constants/time_delay_cons.dart @@ -3,5 +3,6 @@ abstract class TimeDelayConst { static const Duration durationMill300 = Duration(milliseconds: 300); static const Duration durationMill800 = Duration(milliseconds: 800); static const Duration durationMill3500 = Duration(milliseconds: 3500); + static const Duration duration1 = Duration(seconds: 1); static const Duration duration3 = Duration(seconds: 3); } diff --git a/lib/core/di/injection_container.config.dart b/lib/core/di/injection_container.config.dart index 92fe95b..16e7e64 100644 --- a/lib/core/di/injection_container.config.dart +++ b/lib/core/di/injection_container.config.dart @@ -30,6 +30,10 @@ import '../../feature/auth/domain/usecases/verify_phone_register_usecase.dart' as _i664; import '../../feature/auth/presentation/blocs/login_bloc/login_bloc.dart' as _i1065; +import '../../feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart' + as _i323; +import '../../feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart' + as _i224; import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728; import '../../feature/browse/presentation/blocs/browse_bloc/browse_bloc.dart' as _i991; @@ -101,6 +105,20 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i406.VerifyOtpCodeRegisterUseCase>( () => _i406.VerifyOtpCodeRegisterUseCase(gh<_i884.AuthRepository>()), ); + gh.factory<_i323.VerifyOtpBloc>( + () => _i323.VerifyOtpBloc( + gh<_i406.VerifyOtpCodeRegisterUseCase>(), + gh<_i318.VerifyOtpCodeForgotPasswordUseCase>(), + gh<_i664.VerifyPhoneRegisterUseCase>(), + gh<_i801.VerifyPhoneNumberLoginUseCase>(), + ), + ); + gh.factory<_i224.VerifyPhoneBloc>( + () => _i224.VerifyPhoneBloc( + gh<_i801.VerifyPhoneNumberLoginUseCase>(), + gh<_i664.VerifyPhoneRegisterUseCase>(), + ), + ); gh.factory<_i1065.LoginBloc>( () => _i1065.LoginBloc( gh<_i241.LoginUseCase>(), diff --git a/lib/core/helpers/time_formatters.dart b/lib/core/helpers/time_formatters.dart new file mode 100644 index 0000000..a001c86 --- /dev/null +++ b/lib/core/helpers/time_formatters.dart @@ -0,0 +1,8 @@ +abstract class TimeFormatters { + static String formatMinutesToTime(int totalMinutes) { + final hours = totalMinutes ~/ 60; + final minutes = totalMinutes % 60; + + return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}'; + } +} diff --git a/lib/core/router/app_routes.dart b/lib/core/router/app_routes.dart index f9071af..517d76c 100644 --- a/lib/core/router/app_routes.dart +++ b/lib/core/router/app_routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart'; import 'package:food_delivery_client/feature/auth/presentation/pages/forgot_password_page/forgot_password_page.dart'; import 'package:food_delivery_client/feature/auth/presentation/pages/login_page/login_page.dart'; import 'package:food_delivery_client/feature/auth/presentation/pages/register_page/register_page.dart'; @@ -25,7 +26,9 @@ class AppRoutes { GoRoute( path: Routes.register, - pageBuilder: (context, state) => CupertinoPage(child: RegisterPage()), + pageBuilder: (context, state) => CupertinoPage( + child: RegisterPage(phoneNumber: state.extra as String), + ), ), GoRoute( path: Routes.forgotPassword, @@ -42,14 +45,15 @@ class AppRoutes { GoRoute( path: Routes.verifyOtpCode, pageBuilder: (context, state) => CupertinoPage( - child: VerifyOtpCodePage(isRegister: state.extra as bool), + child: VerifyOtpCodePage(params: state.extra as OtpCodePageParams), ), ), GoRoute( path: Routes.resetPassword, - pageBuilder: (context, state) => - CupertinoPage(child: ResetPasswordPage()), + pageBuilder: (context, state) => CupertinoPage( + child: ResetPasswordPage(phoneNumber: state.extra as String), + ), ), GoRoute( diff --git a/lib/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart b/lib/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart index c6dd264..181428d 100644 --- a/lib/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart +++ b/lib/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart @@ -6,13 +6,12 @@ import 'package:food_delivery_client/food_delivery_client.dart'; import '../../../../core/usecase/usecase.dart'; @injectable - class VerifyOtpCodeForgotPasswordUseCase +class VerifyOtpCodeForgotPasswordUseCase implements UseCase { final AuthRepository _authRepository; VerifyOtpCodeForgotPasswordUseCase(this._authRepository); - @override Future> call(VerifyOtpCodeParams params) async { return _authRepository.verifyOtpCodeResetPassword(params: params); @@ -25,3 +24,10 @@ class VerifyOtpCodeParams { VerifyOtpCodeParams({required this.otpCode, required this.phoneNumber}); } + +class OtpCodePageParams { + final String phoneNumber; + final bool isRegister; + + OtpCodePageParams({required this.phoneNumber, required this.isRegister}); +} diff --git a/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart new file mode 100644 index 0000000..672efef --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart @@ -0,0 +1,138 @@ +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_otp_code_register_usecase.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_phone_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_phone_register_usecase.dart'; +import 'package:food_delivery_client/feature/common/presentation/widgets/w_toastification.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +part 'verify_otp_event.dart'; + +part 'verify_otp_state.dart'; + +part 'verify_otp_bloc.freezed.dart'; + +@injectable +class VerifyOtpBloc extends Bloc { + final VerifyOtpCodeRegisterUseCase _registerUseCase; + final VerifyOtpCodeForgotPasswordUseCase _passwordUseCase; + final VerifyPhoneRegisterUseCase _phoneRegisterUseCase; + final VerifyPhoneNumberLoginUseCase _phoneNumberLoginUseCase; + + VerifyOtpBloc( + this._registerUseCase, + this._passwordUseCase, + this._phoneRegisterUseCase, + this._phoneNumberLoginUseCase, + ) : super(const VerifyOtpState()) { + Timer? timer1; + + on<_CancelTimer>((event, emit) { + timer1?.cancel(); + }); + + on<_Started>((event, emit) { + int seconds = state.time; + emit(state.copyWith(time: seconds)); + timer1 = Timer.periodic(TimeDelayConst.duration1, (timer) { + if (seconds == 0) { + timer.cancel(); + } else { + seconds--; + add(VerifyOtpEvent.ticked(seconds)); + } + }); + }); + + on<_Ticked>(_onTicked); + on<_VerifyOtpReset>(_onVerifyOtpReset); + on<_VerifyOtpRegister>(_onVerifyOtpRegister); + on<_ResendRegister>(_onResendVerifyPhoneRegister); + on<_ResendForgot>(_onResendVerifyPhoneForgot); + } + + void _onTicked(_Ticked event, Emitter emit) { + emit(state.copyWith(time: event.seconds)); + } + + Future _onVerifyOtpReset( + _VerifyOtpReset event, + Emitter emit, + ) async { + emit(state.copyWith(status: RequestStatus.loading)); + final response = await _passwordUseCase.call(event.params); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(status: RequestStatus.error)); + }, + (r) { + emit(state.copyWith(status: RequestStatus.loaded)); + add(VerifyOtpEvent.cancelTimer()); + }, + ); + } + + Future _onVerifyOtpRegister( + _VerifyOtpRegister event, + Emitter emit, + ) async { + emit(state.copyWith(status: RequestStatus.loading)); + final response = await _registerUseCase.call(event.params); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(status: RequestStatus.error)); + }, + (r) { + emit(state.copyWith(status: RequestStatus.loaded)); + add(VerifyOtpEvent.cancelTimer()); + }, + ); + } + + Future _onResendVerifyPhoneRegister( + _ResendRegister event, + Emitter emit, + ) async { + emit(state.copyWith(resendStatus: RequestStatus.loading)); + final response = await _phoneRegisterUseCase.call( + VerifyPhoneNumberParams(phoneNumber: event.phoneNUmber), + ); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(resendStatus: RequestStatus.error)); + }, + (r) { + emit(state.copyWith(resendStatus: RequestStatus.loaded)); + add(VerifyOtpEvent.ticked(120)); + add(VerifyOtpEvent.started()); + }, + ); + } + + Future _onResendVerifyPhoneForgot( + _ResendForgot event, + Emitter emit, + ) async { + emit(state.copyWith(resendStatus: RequestStatus.loading)); + final response = await _phoneNumberLoginUseCase.call( + VerifyPhoneNumberParams(phoneNumber: event.phoneNUmber), + ); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(resendStatus: RequestStatus.error)); + }, + (r) { + emit(state.copyWith(resendStatus: RequestStatus.loaded)); + add(VerifyOtpEvent.ticked(120)); + add(VerifyOtpEvent.started()); + }, + ); + } +} diff --git a/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_event.dart b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_event.dart new file mode 100644 index 0000000..4bc725e --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_event.dart @@ -0,0 +1,19 @@ +part of 'verify_otp_bloc.dart'; + +@freezed +class VerifyOtpEvent with _$VerifyOtpEvent { + const factory VerifyOtpEvent.started() = _Started; + const factory VerifyOtpEvent.cancelTimer() = _CancelTimer; + const factory VerifyOtpEvent.resendRegister(String phoneNUmber) = + _ResendRegister; + + const factory VerifyOtpEvent.resendForgot(String phoneNUmber) = _ResendForgot; + + const factory VerifyOtpEvent.ticked(int seconds) = _Ticked; + + const factory VerifyOtpEvent.verifyOtpReset(VerifyOtpCodeParams params) = + _VerifyOtpReset; + + const factory VerifyOtpEvent.verifyOtpRegister(VerifyOtpCodeParams params) = + _VerifyOtpRegister; +} diff --git a/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_state.dart b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_state.dart new file mode 100644 index 0000000..aaa6621 --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_state.dart @@ -0,0 +1,10 @@ +part of 'verify_otp_bloc.dart'; + +@freezed +abstract class VerifyOtpState with _$VerifyOtpState { + const factory VerifyOtpState({ + @Default(RequestStatus.initial) RequestStatus status, + @Default(RequestStatus.initial) RequestStatus resendStatus, + @Default(120) int time, + }) = _VerifyOtpState; +} diff --git a/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart new file mode 100644 index 0000000..de1b26f --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart @@ -0,0 +1,60 @@ +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_phone_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_phone_register_usecase.dart'; +import 'package:food_delivery_client/feature/common/presentation/widgets/w_toastification.dart'; +import 'package:food_delivery_client/food_delivery_client.dart'; + +part 'verify_phone_event.dart'; + +part 'verify_phone_state.dart'; + +part 'verify_phone_bloc.freezed.dart'; + +@injectable +class VerifyPhoneBloc extends Bloc { + final VerifyPhoneNumberLoginUseCase _loginUseCase; + final VerifyPhoneRegisterUseCase _registerUseCase; + + VerifyPhoneBloc(this._loginUseCase, this._registerUseCase) + : super(const VerifyPhoneState()) { + on<_VerifyPhoneRegister>(_onVerifyPhoneNumberRegister); + on<_VerifyPhoneReset>(_onVerifyPhoneReset); + } + + Future _onVerifyPhoneNumberRegister( + _VerifyPhoneRegister event, + Emitter emit, + ) async { + emit(state.copyWith(status: RequestStatus.loading)); + final response = await _registerUseCase.call(event.params); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(status: RequestStatus.error)); + }, + (r) { + showSuccessToast(r.message); + emit(state.copyWith(status: RequestStatus.loaded)); + }, + ); + } + + Future _onVerifyPhoneReset( + _VerifyPhoneReset event, + Emitter emit, + ) async { + emit(state.copyWith(status: RequestStatus.loading)); + final response = await _loginUseCase.call(event.params); + + response.fold( + (l) { + showErrorToast(l.errorMessage); + emit(state.copyWith(status: RequestStatus.error)); + }, + (r) { + showSuccessToast(r.message); + emit(state.copyWith(status: RequestStatus.loaded)); + }, + ); + } +} diff --git a/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_event.dart b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_event.dart new file mode 100644 index 0000000..eb3c247 --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_event.dart @@ -0,0 +1,14 @@ +part of 'verify_phone_bloc.dart'; + +@freezed +class VerifyPhoneEvent with _$VerifyPhoneEvent { + const factory VerifyPhoneEvent.started() = _Started; + + const factory VerifyPhoneEvent.verifyPhoneRegister( + VerifyPhoneNumberParams params, + ) = _VerifyPhoneRegister; + + const factory VerifyPhoneEvent.verifyPhoneReset( + VerifyPhoneNumberParams params, + ) = _VerifyPhoneReset; +} diff --git a/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_state.dart b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_state.dart new file mode 100644 index 0000000..94318ef --- /dev/null +++ b/lib/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_state.dart @@ -0,0 +1,8 @@ +part of 'verify_phone_bloc.dart'; + +@freezed +abstract class VerifyPhoneState with _$VerifyPhoneState { + const factory VerifyPhoneState({ + @Default(RequestStatus.initial) RequestStatus status, + }) = _VerifyPhoneState; +} diff --git a/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart index cb988b4..3442605 100644 --- a/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart +++ b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart @@ -1,6 +1,5 @@ import 'package:food_delivery_client/core/helpers/formatters.dart'; import 'package:food_delivery_client/core/helpers/validator_helpers.dart'; -import 'package:food_delivery_client/feature/auth/domain/usecases/login_usecase.dart'; import 'package:food_delivery_client/feature/auth/presentation/widgets/w_auth_background.dart'; import '../../../../../../food_delivery_client.dart'; import '../../../blocs/login_bloc/login_bloc.dart'; @@ -110,7 +109,7 @@ class _WLoginBodyState extends State { alignment: AlignmentGeometry.centerRight, child: TextButton( onPressed: () { - // context.push(Routes.verifyPhoneNumber, extra: false); + context.push(Routes.verifyPhoneNumber, extra: false); }, child: Text( context.loc.forgot_password, @@ -154,7 +153,7 @@ class _WLoginBodyState extends State { ), TextButton( onPressed: () { - // context.push(Routes.verifyPhoneNumber, extra: true); + context.push(Routes.verifyPhoneNumber, extra: true); }, child: Text( context.loc.sign_up, diff --git a/lib/feature/auth/presentation/pages/register_page/register_page.dart b/lib/feature/auth/presentation/pages/register_page/register_page.dart index ccd5875..6e57e8a 100644 --- a/lib/feature/auth/presentation/pages/register_page/register_page.dart +++ b/lib/feature/auth/presentation/pages/register_page/register_page.dart @@ -3,12 +3,12 @@ import 'package:food_delivery_client/feature/auth/presentation/pages/register_pa import '../../../../../food_delivery_client.dart'; class RegisterPage extends StatelessWidget { - const RegisterPage({super.key}); + const RegisterPage({super.key, required this.phoneNumber}); + + final String phoneNumber; @override Widget build(BuildContext context) { - return WLayout( - top: false, - child: Scaffold(body: WRegisterBody())); + return WLayout(top: false, child: Scaffold(body: WRegisterBody())); } } diff --git a/lib/feature/auth/presentation/pages/register_page/widgets/w_register_body.dart b/lib/feature/auth/presentation/pages/register_page/widgets/w_register_body.dart index ff606a1..8003d5d 100644 --- a/lib/feature/auth/presentation/pages/register_page/widgets/w_register_body.dart +++ b/lib/feature/auth/presentation/pages/register_page/widgets/w_register_body.dart @@ -172,7 +172,10 @@ class _WRegisterBodyState extends State { AppIcons.icArrowRightLight, ).paddingOnly(left: 8), onPressed: () { - if (_formKey.currentState?.validate() ?? false) {} + if (_formKey.currentState?.validate() ?? false) { + + + } }, ), 20.verticalSpace, diff --git a/lib/feature/auth/presentation/pages/reset_password_page/reset_password_page.dart b/lib/feature/auth/presentation/pages/reset_password_page/reset_password_page.dart index c10a6f4..12ed4b4 100644 --- a/lib/feature/auth/presentation/pages/reset_password_page/reset_password_page.dart +++ b/lib/feature/auth/presentation/pages/reset_password_page/reset_password_page.dart @@ -3,8 +3,8 @@ import 'package:food_delivery_client/feature/auth/presentation/widgets/w_auth_ba import '../../../../../food_delivery_client.dart'; class ResetPasswordPage extends StatelessWidget { - const ResetPasswordPage({super.key}); - + const ResetPasswordPage({super.key, required this.phoneNumber}); + final String phoneNumber; @override Widget build(BuildContext context) { return WLayout( diff --git a/lib/feature/auth/presentation/pages/verify_otp_code_page/verify_otp_code_page.dart b/lib/feature/auth/presentation/pages/verify_otp_code_page/verify_otp_code_page.dart index 77f7549..8379572 100644 --- a/lib/feature/auth/presentation/pages/verify_otp_code_page/verify_otp_code_page.dart +++ b/lib/feature/auth/presentation/pages/verify_otp_code_page/verify_otp_code_page.dart @@ -1,17 +1,181 @@ +import 'package:food_delivery_client/core/helpers/time_formatters.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/presentation/blocs/verify_otp_bloc/verify_otp_bloc.dart'; import 'package:food_delivery_client/feature/auth/presentation/widgets/w_auth_background.dart'; +import 'package:pinput/pinput.dart'; import '../../../../../food_delivery_client.dart'; -class VerifyOtpCodePage extends StatelessWidget { - const VerifyOtpCodePage({super.key, required this.isRegister}); +class VerifyOtpCodePage extends StatefulWidget { + const VerifyOtpCodePage({super.key, required this.params}); - final bool isRegister; + final OtpCodePageParams params; + + @override + State createState() => _VerifyOtpCodePageState(); +} + +class _VerifyOtpCodePageState extends State { + late TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + final defaultPinTheme = PinTheme( + height: 56, + width: 56, + textStyle: AppTextStyles.size18Medium, + decoration: BoxDecoration( + borderRadius: AppUtils.kBorderRadius8, + border: Border.all(color: Color.fromRGBO(234, 239, 243, 1)), + color: Color.fromRGBO(234, 234, 243, 1), + ), + ); @override Widget build(BuildContext context) { - return WLayout( - top: false, - child: Scaffold(body: WAuthBackground(child: Column())), + return BlocProvider( + create: (context) => sl()..add(VerifyOtpEvent.started()), + child: BlocConsumer( + listener: (context, state) { + if (state.status.isLoaded()) { + if (widget.params.isRegister) { + context.push(Routes.register, extra: widget.params.phoneNumber); + } else { + context.push( + Routes.resetPassword, + extra: widget.params.phoneNumber, + ); + } + } + }, + builder: (context, state) { + return WLayout( + top: false, + child: Scaffold( + body: WAuthBackground( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + 40.verticalSpace, + Text( + "Enter the 5-digit code sent to you at ${widget.params.phoneNumber}", + style: AppTextStyles.size20Medium, + ), + 20.verticalSpace, + Pinput( + length: 5, + enabled: true, + controller: _controller, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + crossAxisAlignment: CrossAxisAlignment.center, + autofocus: true, + showCursor: true, + defaultPinTheme: defaultPinTheme, + + focusedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + color: Color.fromRGBO(234, 239, 243, 1), + ), + ), + submittedPinTheme: defaultPinTheme.copyWith( + decoration: defaultPinTheme.decoration!.copyWith( + color: Color.fromRGBO(234, 239, 243, 1), + ), + ), + onChanged: (value) { + if (value.length == 5 && state.time > 0) {} + }, + ), + 10.verticalSpace, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("You can resend otp after"), + + if (state.time != 0) + Text(TimeFormatters.formatMinutesToTime(state.time)), + + if (state.time == 0) + state.resendStatus.isLoading() + ? CircularProgressIndicator.adaptive() + : TextButton( + onPressed: () { + if (widget.params.isRegister) { + context.read().add( + VerifyOtpEvent.resendRegister( + widget.params.phoneNumber, + ), + ); + } else { + context.read().add( + VerifyOtpEvent.resendForgot( + widget.params.phoneNumber, + ), + ); + } + }, + child: Text("Resend"), + ), + ], + ), + + 20.verticalSpace, + AppButton( + name: context.loc.continue_str, + onPressed: () { + if (_controller.text.trim().length == 5 && + state.time > 0) { + if (widget.params.isRegister) { + context.read().add( + VerifyOtpEvent.verifyOtpRegister( + VerifyOtpCodeParams( + phoneNumber: widget.params.phoneNumber, + otpCode: _controller.text.trim().substring( + 0, + 4, + ), + ), + ), + ); + } else { + context.read().add( + VerifyOtpEvent.verifyOtpReset( + VerifyOtpCodeParams( + phoneNumber: widget.params.phoneNumber, + otpCode: _controller.text.trim().substring( + 0, + 4, + ), + ), + ), + ); + } + } + }, + backgroundColor: AppColors.c34A853, + borderRadius: 15, + trailing: SvgPicture.asset( + AppIcons.icArrowRightLight, + ).paddingOnly(left: 8), + ), + ], + ).paddingSymmetric(horizontal: 16), + ), + ), + ); + }, + ), ); } } diff --git a/lib/feature/auth/presentation/pages/verify_phone_number_page/verify_phone_page.dart b/lib/feature/auth/presentation/pages/verify_phone_number_page/verify_phone_page.dart index 170c1e5..76b528a 100644 --- a/lib/feature/auth/presentation/pages/verify_phone_number_page/verify_phone_page.dart +++ b/lib/feature/auth/presentation/pages/verify_phone_number_page/verify_phone_page.dart @@ -1,5 +1,8 @@ import 'package:food_delivery_client/core/helpers/formatters.dart'; import 'package:food_delivery_client/core/helpers/validator_helpers.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_otp_code_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/domain/usecases/verify_phone_login_usecase.dart'; +import 'package:food_delivery_client/feature/auth/presentation/blocs/verify_phone_bloc/verify_phone_bloc.dart'; import 'package:food_delivery_client/feature/auth/presentation/widgets/w_auth_background.dart'; import '../../../../../food_delivery_client.dart'; @@ -31,65 +34,106 @@ class _VerifyPhoneNumberPageState extends State { @override Widget build(BuildContext context) { - return Form( - key: _formKey, - child: WLayout( - top: false, - child: Scaffold( - body: WAuthBackground( - child: SizedBox( - width: context.w, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 50.verticalSpace, - Text( - context.loc.enter_phone_number, - style: AppTextStyles.size20Medium, - ), - 20.verticalSpace, - AppTextFormField( - controller: _phoneNumberController, - borderRadius: AppUtils.kBorderRadius8, - hintText: context.loc.phone_number, - keyBoardType: TextInputType.phone, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - Formatters.phoneFormatter, - LengthLimitingTextInputFormatter(12), - ], + return BlocProvider( + create: (context) => sl(), + child: BlocConsumer( + listener: (context, state) { + if (state.status.isLoaded()) { + context.push( + Routes.verifyOtpCode, + extra: OtpCodePageParams( + phoneNumber: + "+998${_phoneNumberController.text.trim().replaceAll(" ", "")}", + isRegister: widget.isRegister, + ), + ); + } + }, + builder: (context, state) { + return Form( + key: _formKey, + child: WLayout( + top: false, + child: Scaffold( + body: WAuthBackground( + child: SizedBox( + width: context.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 50.verticalSpace, + Text( + context.loc.enter_phone_number, + style: AppTextStyles.size20Medium, + ), + 20.verticalSpace, + AppTextFormField( + controller: _phoneNumberController, + borderRadius: AppUtils.kBorderRadius8, + hintText: context.loc.phone_number, + keyBoardType: TextInputType.phone, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + Formatters.phoneFormatter, + LengthLimitingTextInputFormatter(12), + ], - prefixIcon: Text( - "+ 998", - style: AppTextStyles.size16Regular, - ), - validator: (value) { - return Validators.validatePhoneNumber( - _phoneNumberController.text.trim(), - ); - }, + prefixIcon: Text( + "+ 998", + style: AppTextStyles.size16Regular, + ), + validator: (value) { + return Validators.validatePhoneNumber( + _phoneNumberController.text.trim(), + ); + }, + ), + 25.verticalSpace, + AppButton( + onPressed: () { + if (_formKey.currentState?.validate() ?? false) { + + + if (widget.isRegister) { + context.read().add( + VerifyPhoneEvent.verifyPhoneRegister( + VerifyPhoneNumberParams( + phoneNumber: + "+998${_phoneNumberController.text.trim().replaceAll(" ", "")}", + ), + ), + ); + } else { + context.read().add( + VerifyPhoneEvent.verifyPhoneReset( + VerifyPhoneNumberParams( + phoneNumber: + "+998${_phoneNumberController.text.trim().replaceAll(" ", "")}", + ), + ), + ); + } + } + }, + name: context.loc.continue_str, + borderRadius: 15, + backgroundColor: AppColors.c34A853, + ), + 10.verticalSpace, + Text( + context.loc.consent_message("Felix Eats"), + style: AppTextStyles.size12Medium.copyWith( + color: AppColors.c888888, + ), + ), + ], + ).paddingSymmetric(horizontal: 16), ), - 25.verticalSpace, - AppButton( - onPressed: () { - if (_formKey.currentState?.validate() ?? false) {} - }, - name: context.loc.continue_str, - borderRadius: 15, - backgroundColor: AppColors.c34A853, - ), - 10.verticalSpace, - Text( - context.loc.consent_message("Felix Eats"), - style: AppTextStyles.size12Medium.copyWith( - color: AppColors.c888888, - ), - ), - ], - ).paddingSymmetric(horizontal: 16), + ), + ), ), - ), - ), + ); + }, ), ); } diff --git a/pubspec.lock b/pubspec.lock index c7bc367..3f8524d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -653,6 +653,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: c41f42ee301505ae2375ec32871c985d3717bf8aee845620465b286e0140aad2 + url: "https://pub.dev" + source: hosted + version: "5.0.2" platform: dependency: transitive description: @@ -954,6 +962,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9d1bbb7..82675a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,6 +23,7 @@ dependencies: equatable: ^2.0.7 dartz: ^0.10.1 mask_text_input_formatter: ^2.9.0 + pinput: ^5.0.2 #for notify user about error or success toastification: ^3.0.3