From c88b010cc53758af343f19106e4704b8566a2d9a Mon Sep 17 00:00:00 2001 From: misha Date: Mon, 29 Sep 2025 09:13:45 +0300 Subject: [PATCH] feat(glossary): added pass key for glossary --- lib/core/app_router.dart | 2 +- .../dio/phone_verify_error_interceptor.dart | 4 +- lib/core/locator_service.dart | 2 + .../presentation/pages/glossary_screen.dart | 10 +- .../bloc/menu_cubit/menu_cubit.dart | 20 +++ .../bloc/menu_cubit/menu_state.dart | 1 + .../menu/presentation/pages/menu_screen.dart | 140 ++++++++++-------- .../profile/models/pass_key_params.dart | 15 ++ .../repositories/profile_repository.dart | 9 +- .../profile/services/profile_service.dart | 3 +- lib/generated/l10n.dart | 7 +- 11 files changed, 137 insertions(+), 76 deletions(-) create mode 100644 lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart create mode 100644 lib/features/menu/presentation/bloc/menu_cubit/menu_state.dart create mode 100644 lib/features/profile/models/pass_key_params.dart diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index cc028255..dda4395a 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -257,7 +257,7 @@ class AppRouter { return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool); }, ), - GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()), + GoRoute(path: '/glossary-screen', name: 'glossaryScreen', builder: (context, state) => GlossaryScreen(passKey: state.extra as String,)), GoRoute( path: '/about-recipes-screen', name: 'aboutRecipesScreen', diff --git a/lib/core/dio/phone_verify_error_interceptor.dart b/lib/core/dio/phone_verify_error_interceptor.dart index ab6188bf..4073cb85 100644 --- a/lib/core/dio/phone_verify_error_interceptor.dart +++ b/lib/core/dio/phone_verify_error_interceptor.dart @@ -12,8 +12,8 @@ class PhoneVerifyErrorInterceptor extends Interceptor { final data = err.response?.data; if (data is Map && data['type'] == 'unverificated_phone') { - final String phone = await sl().getMyPhone(); - final String passKey = await sl().getPassKey(phone); + final String phone = await sl().getMyPhone(); + final String passKey = await sl().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone}); await showErrorDialog(AppRouter.navigatorKey.currentContext!, title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона'); AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey); diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 37713f29..917c9dd1 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -45,6 +45,7 @@ import 'package:eatvio_app/features/blog_post_create/services/blog_post_service. import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart'; +import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart'; @@ -183,6 +184,7 @@ Future init() async { sl.registerFactory(() => NotificationSettingsCubit(notificationSettingsRepository: sl())); sl.registerFactory(() => ConsultantRatingCubit(sl())); sl.registerFactory(() => ViewsCubit(viewsRepository: sl())); + sl.registerFactory(() => MenuCubit(sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/features/glossary/presentation/pages/glossary_screen.dart b/lib/features/glossary/presentation/pages/glossary_screen.dart index 7205b3d3..1c4dd4cd 100644 --- a/lib/features/glossary/presentation/pages/glossary_screen.dart +++ b/lib/features/glossary/presentation/pages/glossary_screen.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; class GlossaryScreen extends StatefulWidget { - const GlossaryScreen({super.key}); - + const GlossaryScreen({super.key, required this.passKey}); + final String passKey; @override State createState() => _GlossaryScreenState(); } @@ -18,15 +18,13 @@ class _GlossaryScreenState extends State { _controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..loadRequest(Uri.parse('$baseUrl/product_nutritional_value')); + ..loadRequest(Uri.parse('$baseUrl/login_with_pass_key?id=${widget.passKey}')); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Глоссарий'), - ), + appBar: AppBar(title: const Text('Глоссарий')), body: WebViewWidget(controller: _controller), ); } diff --git a/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart b/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart new file mode 100644 index 00000000..bb60d139 --- /dev/null +++ b/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart @@ -0,0 +1,20 @@ +import 'dart:developer'; + +import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart'; +import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MenuCubit extends Cubit { + final ProfileRepository profileRepository; + MenuCubit(this.profileRepository) : super(MenuState()); + + Future makePassKey() async { + try { + final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {}); + return passKey; + } catch (e) { + log(e.toString()); + } + return null; + } +} diff --git a/lib/features/menu/presentation/bloc/menu_cubit/menu_state.dart b/lib/features/menu/presentation/bloc/menu_cubit/menu_state.dart new file mode 100644 index 00000000..70f37bb8 --- /dev/null +++ b/lib/features/menu/presentation/bloc/menu_cubit/menu_state.dart @@ -0,0 +1 @@ +class MenuState {} \ No newline at end of file diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index 5c350d3c..77bbbd20 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -9,12 +9,14 @@ import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart'; +import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -26,66 +28,86 @@ class MenuScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(centerTitle: false, title: const Text('Меню')), - body: SingleChildScrollView( - child: Column( - children: [ - _tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')), - if (profileModel.roles.contains('consultant')) _divider(context), - if (profileModel.roles.contains('consultant')) - _tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)), - if (profileModel.roles.contains('consultant')) _divider(context), - if (profileModel.roles.contains('consultant')) - _tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)), - if (profileModel.roles.contains('consultant')) _divider(context), - if (profileModel.roles.contains('consultant')) - _tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)), - if (!hidePlans) ...[ - _divider(context), - _tile( - leading: 'assets/icons/finance.svg', - title: 'Мои финансы', - onTap: () { - if (profileModel.roles.contains('consultant')) { - GoRouter.of(context).push(ConsultantFinancesScreen.path); - return; - } - GoRouter.of(context).push(UserPurchasesScreen.path); - }, + return BlocProvider( + create: (context) => sl(), + child: Builder( + builder: (context) { + return Scaffold( + appBar: AppBar(centerTitle: false, title: const Text('Меню')), + body: SingleChildScrollView( + child: Column( + children: [ + _tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')), + if (profileModel.roles.contains('consultant')) _divider(context), + if (profileModel.roles.contains('consultant')) + _tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)), + if (profileModel.roles.contains('consultant')) _divider(context), + if (profileModel.roles.contains('consultant')) + _tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)), + if (profileModel.roles.contains('consultant')) _divider(context), + if (profileModel.roles.contains('consultant')) + _tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)), + if (!hidePlans) ...[ + _divider(context), + _tile( + leading: 'assets/icons/finance.svg', + title: 'Мои финансы', + onTap: () { + if (profileModel.roles.contains('consultant')) { + GoRouter.of(context).push(ConsultantFinancesScreen.path); + return; + } + GoRouter.of(context).push(UserPurchasesScreen.path); + }, + ), + ], + if (profileModel.roles.contains('consultant')) _divider(context), + if (profileModel.roles.contains('consultant')) + _tile( + leading: 'assets/icons/finance.svg', + title: 'Мой рейтинг', + onTap: () { + GoRouter.of(context).push(ConsultantRatingScreen.path); + }, + ), + _divider(context), + _tile( + leading: ImageConstant.icnGlossary, + title: 'Глоссарий', + onTap: () async { + final passKey = await context.read().makePassKey(); + if (passKey != null && context.mounted) { + GoRouter.of(context).push('/glossary-screen', extra: passKey); + } + }, + ), + _divider(context), + _tile( + leading: 'assets/icons/notifications.svg', + title: 'Уведомления', + onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'), + ), + _divider(context), + _tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')), + _divider(context), + _tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')), + // _divider(context), + // _tile( + // leading: ImageConstant.icnDevTools, + // title: 'Для разработчиков', + // onTap: () { + // GoRouter.of(context).pushNamed(DevToolsScreen.name); + // }, + // ), + _divider(context), + _tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)), + const SizedBox(height: 10), + const AppInfo(), + ], ), - ], - if (profileModel.roles.contains('consultant')) _divider(context), - if (profileModel.roles.contains('consultant')) - _tile( - leading: 'assets/icons/finance.svg', - title: 'Мой рейтинг', - onTap: () { - GoRouter.of(context).push(ConsultantRatingScreen.path); - }, - ), - _divider(context), - _tile(leading: ImageConstant.icnGlossary, title: 'Глоссарий', onTap: () => GoRouter.of(context).push('/glossary-screen')), - _divider(context), - _tile(leading: 'assets/icons/notifications.svg', title: 'Уведомления', onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen')), - _divider(context), - _tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')), - _divider(context), - _tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')), - // _divider(context), - // _tile( - // leading: ImageConstant.icnDevTools, - // title: 'Для разработчиков', - // onTap: () { - // GoRouter.of(context).pushNamed(DevToolsScreen.name); - // }, - // ), - _divider(context), - _tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)), - const SizedBox(height: 10), - const AppInfo(), - ], - ), + ), + ); + }, ), ); } diff --git a/lib/features/profile/models/pass_key_params.dart b/lib/features/profile/models/pass_key_params.dart new file mode 100644 index 00000000..972f0f9f --- /dev/null +++ b/lib/features/profile/models/pass_key_params.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'pass_key_params.freezed.dart'; +part 'pass_key_params.g.dart'; + +@freezed +abstract class PassKeyParams with _$PassKeyParams { + + factory PassKeyParams({ + @JsonKey(name: 'route_name') required String routeName, + @JsonKey(name: 'route_params') Map? routeParams, + }) = _PassKeyParams; + + factory PassKeyParams.fromJson(Map json) => _$PassKeyParamsFromJson(json); +} \ No newline at end of file diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 4867d85a..0f2e903c 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; +import 'package:eatvio_app/features/profile/models/pass_key_params.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/sub_model.dart'; import 'package:eatvio_app/features/profile/models/weight_model.dart'; @@ -154,10 +155,10 @@ 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']!; + Future getPassKey({required String routeName, Map? routeParams}) async { + return (await profileService.getPassKey( + json: PassKeyParams(routeName: routeName, routeParams: routeParams), + )).data['key_is']!; } Future getMyPhone() async { diff --git a/lib/features/profile/services/profile_service.dart b/lib/features/profile/services/profile_service.dart index b313f27f..95117d36 100644 --- a/lib/features/profile/services/profile_service.dart +++ b/lib/features/profile/services/profile_service.dart @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/models/chosen_diets_pagination_model.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; +import 'package:eatvio_app/features/profile/models/pass_key_params.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/sub_model.dart'; import 'package:eatvio_app/features/profile/models/weight_model.dart'; @@ -75,7 +76,7 @@ abstract class ProfileService { Future getChosenDiets(); @POST('/api/profile/make_pass_key') - Future getPassKey({@Body() required String json}); + Future getPassKey({@Body() required PassKeyParams json}); @GET('/api/profile/my_phone') Future getMyPhone(); diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 85f74918..6144d39c 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,9 +28,10 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = + (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName;