feat(glossary): added pass key for glossary

This commit is contained in:
misha
2025-09-29 09:13:45 +03:00
parent 68ea8da6e3
commit c88b010cc5
11 changed files with 137 additions and 76 deletions

View File

@@ -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',

View File

@@ -12,8 +12,8 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
final data = err.response?.data;
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
final String phone = await sl<ProfileRepository>().getMyPhone();
final String passKey = await sl<ProfileRepository>().getPassKey(phone);
final String phone = await sl<ProfileRepository>().getMyPhone();
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
await showErrorDialog(AppRouter.navigatorKey.currentContext!,
title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона');
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);

View File

@@ -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<void> init() async {
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
sl.registerFactory<MenuCubit>(() => MenuCubit(sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));

View File

@@ -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<GlossaryScreen> createState() => _GlossaryScreenState();
}
@@ -18,15 +18,13 @@ class _GlossaryScreenState extends State<GlossaryScreen> {
_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),
);
}

View File

@@ -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<MenuState> {
final ProfileRepository profileRepository;
MenuCubit(this.profileRepository) : super(MenuState());
Future<String?> makePassKey() async {
try {
final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {});
return passKey;
} catch (e) {
log(e.toString());
}
return null;
}
}

View File

@@ -0,0 +1 @@
class MenuState {}

View File

@@ -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<MenuCubit>(),
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<MenuCubit>().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(),
],
),
),
);
},
),
);
}

View File

@@ -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<String, dynamic>? routeParams,
}) = _PassKeyParams;
factory PassKeyParams.fromJson(Map<String, dynamic> json) => _$PassKeyParamsFromJson(json);
}

View File

@@ -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<String> getPassKey(String phone) async {
return (jsonDecode(
(await profileService.getPassKey(json: '{"route_name": "user_detailz.change-phone-verify", "route_params": {"phone": "$phone"}}')),
))['key_is']!;
Future<String> getPassKey({required String routeName, Map<String, dynamic>? routeParams}) async {
return (await profileService.getPassKey(
json: PassKeyParams(routeName: routeName, routeParams: routeParams),
)).data['key_is']!;
}
Future<String> getMyPhone() async {

View File

@@ -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<ChosenDietsPaginationModel> getChosenDiets();
@POST('/api/profile/make_pass_key')
Future<String> getPassKey({@Body() required String json});
Future<HttpResponse> getPassKey({@Body() required PassKeyParams json});
@GET('/api/profile/my_phone')
Future<String> getMyPhone();

View File

@@ -28,9 +28,10 @@ class S {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> 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;