From 67bf7b05100b75633b523dcad57e9c37c910447a Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 26 May 2025 17:56:29 +0300 Subject: [PATCH] feat(email): verify screen --- devtools_options.yaml | 3 +- lib/core/app_router.dart | 6 ++ lib/core/locator_service.dart | 2 + .../presentation/widgets/author_card.dart | 3 +- .../presentation/widgets/comments_widget.dart | 3 +- .../menu/presentation/pages/menu_screen.dart | 1 + .../repositories/profile_repository.dart | 12 ++- .../verify_email_cubit.dart | 35 ++++++++ .../verify_email_state.dart | 24 +++++ .../pages/verify_email_screen.dart | 88 +++++++++++++------ .../presentation/pages/verify_screen.dart | 1 - pubspec.yaml | 1 + 12 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart create mode 100644 lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart diff --git a/devtools_options.yaml b/devtools_options.yaml index 5c27c3e3..70935407 100644 --- a/devtools_options.yaml +++ b/devtools_options.yaml @@ -1,2 +1,3 @@ extensions: - - provider: true \ No newline at end of file + - provider: true + - shared_preferences: true \ No newline at end of file diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index d667c3b5..c5e2b77a 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -44,6 +44,7 @@ import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_scree import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart'; import 'package:eatvio_app/features/settings/entities/settings_entity.dart'; import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart'; @@ -359,6 +360,11 @@ class AppRouter { name: 'verifyScreen', builder: (context, state) => VerifyScreen(passKey: state.extra as String), ), + GoRoute( + path: '/verify-email-screen', + name: 'verifyEmailScreen', + builder: (context, state) => const VerifyEmailScreen(), + ), GoRoute( path: MyRecipesScreen.path, name: MyRecipesScreen.name, diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index fe28d2ec..a606b5bc 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -84,6 +84,7 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart'; import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart'; import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart'; @@ -138,6 +139,7 @@ Future init() async { sl.registerFactory(() => ConsultationsCubit(consultationsRepository: sl())); sl.registerFactory(() => ConsultantCommentsCubit(diaryRepository: sl())); sl.registerFactory(() => CancelPlanCubit(cancelPlanRepository: sl())); + sl.registerFactory(() => VerifyEmailCubit(profileRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/features/diets/presentation/widgets/author_card.dart b/lib/features/diets/presentation/widgets/author_card.dart index c1113f93..6fff0b93 100644 --- a/lib/features/diets/presentation/widgets/author_card.dart +++ b/lib/features/diets/presentation/widgets/author_card.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; @@ -36,7 +37,7 @@ class AuthorCard extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( - image: NetworkImage(author.authorProfileIcon), + image: NetworkImage(baseUrl + author.authorProfileIcon), fit: BoxFit.cover, ), ), diff --git a/lib/features/feed/presentation/widgets/comments_widget.dart b/lib/features/feed/presentation/widgets/comments_widget.dart index b12b691e..16f52f90 100644 --- a/lib/features/feed/presentation/widgets/comments_widget.dart +++ b/lib/features/feed/presentation/widgets/comments_widget.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; @@ -77,7 +78,7 @@ class _CommentsWidgetState extends State { ); }, child: CircleAvatar( - foregroundImage: NetworkImage(comment.authorProfileIcon), + foregroundImage: NetworkImage(baseUrl + comment.authorProfileIcon), backgroundImage: const AssetImage('assets/images/no_avatar.png'), radius: radius, ), diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 6ccb0973..72532065 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -58,6 +58,7 @@ class MenuScreen extends StatelessWidget { ), _divider(context), _tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)), + _tile(leading: ImageConstant.icnExit, title: 'Verify email', onTap: () => GoRouter.of(context).pushNamed('verifyEmailScreen')), ], ), ), diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 46b3bcf6..75879d15 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -183,7 +183,8 @@ class ProfileRepository { } Future getPassKey(String phone) async { - return (jsonDecode((await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!; + return (jsonDecode( + (await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}'))))['key_is']!; } Future getMyPhone() async { @@ -194,7 +195,12 @@ class ProfileRepository { return await profileService.sendEmailCode(); } - Future tryVerifyEmailByCode(String code) async { - return await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}"); + Future tryVerifyEmailByCode(String code) async { + try { + await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}"); + return true; + } catch (e) { + return false; + } } } diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart new file mode 100644 index 00000000..864951ae --- /dev/null +++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart @@ -0,0 +1,35 @@ +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class VerifyEmailCubit extends Cubit { + final ProfileRepository profileRepository; + + VerifyEmailCubit({ + required this.profileRepository, + }) : super(const VerifyEmailState.loading()); + + sendEmailCode() async { + try { + final profile = await profileRepository.getMyProfile(); + await profileRepository.sendEmailCode(); + emit(VerifyEmailState.loaded(email: profile.email)); + } catch (e) { + emit(const VerifyEmailState.error(errorMessage: 'Ошибка отправки кода.\nПовторите попытку позже')); + } + } + + tryVerify(String code) async { + try { + emit(const VerifyEmailState.verifyLoading()); + final result = await profileRepository.tryVerifyEmailByCode(code); + if (result == true) { + emit(const VerifyEmailState.verifySuccess()); + } else { + emit(const VerifyEmailState.verifyError()); + } + } catch (e) { + emit(const VerifyEmailState.verifyError()); + } + } +} diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart new file mode 100644 index 00000000..718b2cce --- /dev/null +++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'verify_email_state.freezed.dart'; + +@freezed +abstract class VerifyEmailState with _$VerifyEmailState { + const factory VerifyEmailState.loading() = VerifyEmailStateLoading; + + const factory VerifyEmailState.loaded({ + required String email, + }) = VerifyEmailStateLoaded; + + const factory VerifyEmailState.error({ + required String errorMessage, + }) = VerifyEmailStateError; + + const factory VerifyEmailState() = _VerifyEmailState; + + const factory VerifyEmailState.verifyLoading() = VerifyEmailStateVerifyLoading; + + const factory VerifyEmailState.verifySuccess() = VerifyEmailStateVerifySuccess; + + const factory VerifyEmailState.verifyError() = VerifyEmailStateVerifyError; +} diff --git a/lib/features/registration/presentation/pages/verify_email_screen.dart b/lib/features/registration/presentation/pages/verify_email_screen.dart index 9f0da694..75f38712 100644 --- a/lib/features/registration/presentation/pages/verify_email_screen.dart +++ b/lib/features/registration/presentation/pages/verify_email_screen.dart @@ -1,4 +1,10 @@ +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/utils/toasts.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart'; +import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; class VerifyEmailScreen extends StatefulWidget { const VerifyEmailScreen({super.key}); @@ -10,6 +16,7 @@ class VerifyEmailScreen extends StatefulWidget { class _VerifyEmailScreenState extends State { final List _focusNodes = List.generate(4, (_) => FocusNode()); final List _controllers = List.generate(4, (_) => TextEditingController()); + String email = ''; @override void dispose() { @@ -22,36 +29,66 @@ class _VerifyEmailScreenState extends State { super.dispose(); } + void _checkAndSubmitCode(BuildContext context) { + final code = _controllers.map((c) => c.text).join(); + if (code.length == 4) { + context.read().tryVerify(code); + } + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text("Подтвердите почту")), - body: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Код отправлен на почту\nnazvanie@mail.com", textAlign: TextAlign.center), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate(4, (index) => _buildCodeField(index)), + return BlocProvider( + create: (_) => sl()..sendEmailCode(), + child: BlocConsumer( + listener: (context, state) { + if (state is VerifyEmailStateLoaded) { + setState(() { + email = state.email; + }); + } else if (state is VerifyEmailStateVerifySuccess) { + showSuccessToast('Почта успешно подтверждена'); + GoRouter.of(context).pop(); + } else if (state is VerifyEmailStateVerifyError) { + showErrorToast("Неверный код. Попробуйте снова."); + for (var controller in _controllers) { + controller.clear(); + } + FocusScope.of(context).requestFocus(_focusNodes[0]); + } + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar(title: const Text("Подтвердите почту")), + body: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Код отправлен на почту\n$email", textAlign: TextAlign.center), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(4, (index) => _buildCodeField(context, index)), + ), + const SizedBox(height: 20), + const Text("Запросить код повторно", style: TextStyle(color: Colors.grey)), + TextButton( + onPressed: () { + context.read().sendEmailCode(); + }, + child: const Text("Не приходит код?"), + ), + ], + ), ), - const SizedBox(height: 20), - const Text("Запросить код повторно", style: TextStyle(color: Colors.grey)), - TextButton( - onPressed: () { - // логика запроса нового кода - }, - child: const Text("Не приходит код?"), - ) - ], - ), + ); + }, ), ); } - Widget _buildCodeField(int index) { + Widget _buildCodeField(BuildContext context, int index) { return SizedBox( width: 50, child: TextField( @@ -63,9 +100,7 @@ class _VerifyEmailScreenState extends State { style: const TextStyle(fontSize: 24), decoration: InputDecoration( counterText: "", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - ), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.0)), ), onChanged: (value) { if (value.isNotEmpty && index < 3) { @@ -74,6 +109,7 @@ class _VerifyEmailScreenState extends State { if (value.isEmpty && index > 0) { FocusScope.of(context).requestFocus(_focusNodes[index - 1]); } + _checkAndSubmitCode(context); }, ), ); diff --git a/lib/features/registration/presentation/pages/verify_screen.dart b/lib/features/registration/presentation/pages/verify_screen.dart index 6059a0b5..7f8aede5 100644 --- a/lib/features/registration/presentation/pages/verify_screen.dart +++ b/lib/features/registration/presentation/pages/verify_screen.dart @@ -69,7 +69,6 @@ class _VerifyScreenState extends State { InkWell( onTap: () { Navigator.of(context).pop(); - GoRouter.of(context).pop(); }, child: Ink( width: MediaQuery.of(context).size.width - 32, diff --git a/pubspec.yaml b/pubspec.yaml index 3b1a20b4..9821d31a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,6 +79,7 @@ dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^5.0.0 flutter: