Merge remote-tracking branch 'upstream/main' into issue-712

This commit is contained in:
misha
2025-10-08 14:03:05 +03:00
42 changed files with 1836 additions and 1021 deletions

54
lib/core/app_config.dart Normal file
View File

@@ -0,0 +1,54 @@
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/internet_connection_checker/internet_connection_checker_screen.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
/// Конфигурация приложения
class AppConfig {
/// Список глобальных BlocProvider'ов
static List<BlocProvider> get globalBlocProviders => [
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
BlocProvider<NotificationsCubit>(
create: (context) => sl<NotificationsCubit>()..getNotifications(),
),
BlocProvider<FeedCubit>(
create: (context) => sl<FeedCubit>()..loadFeedBlogPostList(),
),
BlocProvider<ProfileAvatarCubit>(
create: (context) => sl<ProfileAvatarCubit>()..getMyProfile(),
),
BlocProvider<ChatsCubit>(
create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading(),
),
BlocProvider<ViewsCubit>(
create: (context) => sl<ViewsCubit>(),
),
BlocProvider<InternetConnectionCubit>(
lazy: false,
create: (context) => sl<InternetConnectionCubit>(),
),
];
/// Делегаты локализации
static const List<LocalizationsDelegate> localizationDelegates = [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
];
/// Поддерживаемые локали
static const List<Locale> supportedLocales = [
Locale.fromSubtags(languageCode: 'ru'),
];
}

View File

@@ -0,0 +1,68 @@
import 'dart:io' as io;
import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/locator_service.dart' as di;
import 'package:eatvio_app/firebase_options.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Класс для инициализации приложения
class AppInitializer {
/// Инициализация всех сервисов приложения
static Future<void> initialize() async {
await _initializeSentry();
await _initializeEnvironment();
await _initializeFirebase();
await _initializeDependencyInjection();
await _initializeDebugMode();
await _initializeAnalytics();
}
/// Инициализация Sentry
static Future<void> _initializeSentry() async {
final widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
}
/// Загрузка переменных окружения
static Future<void> _initializeEnvironment() async {
await dotenv.load(fileName: ".env");
}
/// Инициализация Firebase
static Future<void> _initializeFirebase() async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await FirebaseMessaging.instance.requestPermission();
}
/// Инициализация системы внедрения зависимостей
static Future<void> _initializeDependencyInjection() async {
await di.init();
}
/// Настройка режима отладки
static Future<void> _initializeDebugMode() async {
if (kDebugMode) {
io.HttpClient.enableTimelineLogging = true;
}
}
/// Инициализация аналитики
static Future<void> _initializeAnalytics() async {
AppMetrica.activate(AppMetricaConfig(EnvConfig.appMetricaApiKey));
AppMetrica.reportEvent('Application started');
}
/// Запуск приложения с Sentry
static Future<void> runAppWithSentry(VoidCallback appRunner) async {
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.tracesSampleRate = 1.0;
options.debug = false;
}, appRunner: appRunner);
}
}

View File

@@ -1,455 +1,53 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_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/pages/blocked_users_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultations_archive_screen.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
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';
import 'package:eatvio_app/features/settings/presentation/pages/change_password_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/general_info_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/other_info_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/physical_activity_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
import 'package:eatvio_app/features/support/presentation/pages/support_screen.dart';
import 'package:eatvio_app/features/update_screen/presentation/update_screen.dart';
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_screen.dart';
import 'package:eatvio_app/core/routing/auth_routes.dart';
import 'package:eatvio_app/core/routing/content_routes.dart';
import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
import 'package:eatvio_app/core/routing/other_routes.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/routing/settings_routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:version/version.dart';
import 'dart:developer';
/// Главный роутер приложения
class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static GoRouter appRouter = GoRouter(
initialLocation: '/update-screen',
initialLocation: RouteConstants.updateScreen,
navigatorKey: navigatorKey,
redirect: (context, state) {
final segments = state.uri.pathSegments;
if (segments.length >= 3) {
final type = segments[1];
final contentIdentifier = segments[2];
switch (type) {
case 'recipe':
FlutterNativeSplash.remove();
return '/about-recipes-screen?slug=$contentIdentifier';
case 'blog_post':
FlutterNativeSplash.remove();
return '/blog-post-screen?id=$contentIdentifier';
case 'plan':
FlutterNativeSplash.remove();
return '/about-plan-screen?slug=$contentIdentifier';
case 'diet':
FlutterNativeSplash.remove();
return '/about-diet-screen?slug=$contentIdentifier';
}
}
return null;
},
redirect: _handleRedirect,
routes: [
GoRoute(
path: '/auth-screen',
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
routes: [
GoRoute(path: 'registration-screen', name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
GoRoute(
path: 'forget-password-screen',
name: 'forgetPasswordScreen',
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
),
GoRoute(path: 'scan-qr-code-screen', name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
],
),
ShellRoute(
builder: (context, state, child) {
return BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return BottomNavigationBarWidget(profileModel: state.profile, child: child);
}
return const SizedBox.shrink();
},
);
},
routes: [
GoRoute(
path: '/my-profile-screen',
name: 'myProfileScreen',
pageBuilder: (context, state) {
return const NoTransitionPage(child: ProfileScreen());
},
),
GoRoute(
path: '/feed-screen',
name: 'feedScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
redirect: (context, state) async {
try {
await sl<WsManager>().initialize();
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
return null;
},
),
GoRoute(
path: '/diary-screen',
name: 'diaryScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
),
GoRoute(
path: ConsultationsScreen.path,
name: ConsultationsScreen.name,
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
),
GoRoute(
path: '/chat-select-screen',
name: 'chatSelectScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
),
GoRoute(
path: '/notifications-screen',
name: 'notificationsScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
),
GoRoute(
path: '/menu-screen',
name: 'menuScreen',
pageBuilder: (context, state) => NoTransitionPage(
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return MenuScreen(profileModel: state.profile);
}
return const SizedBox.shrink();
},
),
),
),
],
),
GoRoute(name: 'blockedUsersScreen', path: '/blocked-users-screen', builder: (context, state) => const BlockedUsersScreen()),
GoRoute(
path: '/welcome-screen',
name: 'welcomeScreen',
builder: (context, state) => const WelcomeScreen(),
redirect: (context, state) async {
try {
final sharedPreferences = await SharedPreferences.getInstance();
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
await sl<FirebaseApi>().registerPushToken();
if (welcomeScreenWasShowed == false) {
return null;
} else if (await TokenStorage.haveTokenInStorage()) {
return '/feed-screen';
} else {
return '/auth-screen';
}
} catch (e) {
return '/feed-screen';
}
},
),
GoRoute(
path: '/update-screen',
name: 'updateScreen',
builder: (context, state) => const UpdateScreen(),
redirect: (context, state) async {
try {
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version;
Version currentVersion = Version.parse(version);
Version latestVersion = Version.parse(versions['min_version']);
if (latestVersion > currentVersion) {
return null;
} else {
return '/welcome-screen';
}
} catch (e) {
return '/welcome-screen';
}
},
),
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
GoRoute(path: '/user-finance-screen', name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
GoRoute(
path: CardSettingsScreen.path,
name: CardSettingsScreen.name,
builder: (context, state) {
final chosenPlanId = state.extra as int?;
return CardSettingsScreen(chosenPlanId: chosenPlanId);
},
),
GoRoute(
path: '/about-plan-screen',
name: 'aboutPlanScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map<String, dynamic>;
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: '/about-recipes-screen',
name: 'aboutRecipesScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map<String, dynamic>;
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: RecipeCreateScreen.path,
name: RecipeCreateScreen.name,
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
),
GoRoute(
path: '/profile-screen',
name: 'profileScreen',
builder: (context, state) => ProfileScreen(author: state.extra as Author),
),
GoRoute(
path: '/subs-screen/:slug',
name: 'subsScreen',
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
),
GoRoute(
path: '/about-diet-screen',
name: 'aboutDietScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
}
final data = state.extra as Map<String, dynamic>;
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: '/blog-post-create-screen',
name: 'blogPostCreateScreen',
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
),
GoRoute(
path: '/blog-post-screen',
name: 'blogPostScreen',
builder: (context, state) {
final id = state.uri.queryParameters['id'];
final parsedId = int.tryParse(id ?? '');
if (parsedId != null) {
return BlogPostScreen(id: parsedId);
}
return BlogPostScreen(id: state.extra as int);
},
),
GoRoute(
path: '/chat-screen',
name: 'chatScreen',
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final uuid = data['uuid'] as String;
final sendInitialMessage = data['sendInitialMessage'] as bool?;
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
},
),
GoRoute(
path: '/empty-chat-screen',
name: 'emptyChatScreen',
builder: (context, state) => EmptyChatScreen(author: state.extra as Author),
),
GoRoute(
path: '/settings-screen',
name: 'settingsScreen',
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: 'general-info-screen',
name: 'generalInfoScreen',
builder: (context, state) => GeneralInfoScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'other-info-screen',
name: 'otherInfoScreen',
builder: (context, state) => OtherInfoScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'physical-activity-screen',
name: 'physicalActivityScreen',
builder: (context, state) => PhysicalActivityScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'physical-form-screen',
name: 'physicalFormScreen',
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'pressure-screen',
name: 'pressureScreen',
builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'change-password-screen',
name: 'changePasswordScreen',
builder: (context, state) => ChangePasswordScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: 'change-email-screen',
name: 'changeEmailScreen',
builder: (context, state) => ChangeEmailScreen(settingsEntity: state.extra as SettingsEntity),
),
],
),
GoRoute(path: '/become-consultant-screen', name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
GoRoute(
path: '/stepper-become-consultant-screen',
name: 'stepperBecomeConsultantScreen',
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
),
GoRoute(path: '/calendar-screen', name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
GoRoute(path: '/support-screen', name: 'supportScreen', builder: (context, state) => const SupportScreen()),
GoRoute(
path: '/photo-view-screen/:index',
name: 'photoViewScreen',
builder: (context, state) => PhotoViewScreen(imageUrls: state.extra as List<String>, index: int.parse(state.pathParameters['index']!)),
),
GoRoute(
path: CreateDietScreen.path,
name: CreateDietScreen.name,
builder: (context, state) {
final diet = state.extra as Diet?;
return CreateDietScreen(diet: diet);
},
),
GoRoute(
path: DietPreviewScreen.path,
name: DietPreviewScreen.name,
builder: (context, state) {
final data = state.extra as Diet;
return DietPreviewScreen(diet: data);
},
),
GoRoute(path: CreatePlanScreen.path, name: CreatePlanScreen.name, builder: (context, state) => const CreatePlanScreen()),
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
GoRoute(
path: UserDetailsScreen.path,
name: UserDetailsScreen.name,
builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
),
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
GoRoute(
path: InfoByOrderScreen.path,
name: InfoByOrderScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?;
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
},
),
GoRoute(
path: PaymentScreen.path,
name: PaymentScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final url = data['url'] as String;
return PaymentScreen(url: url, planId: planId);
},
),
GoRoute(
path: UserDiaryScreen.path,
name: UserDiaryScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, int>;
final diaryId = data['diaryId'] as int;
final currentPlanDay = data['currentDiaryDay'] as int;
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
},
),
GoRoute(
path: '/verify-screen',
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, builder: (context, state) => const MyRecipesScreen()),
GoRoute(path: '/notifications-settings-screen', name: 'notificationsSettingsScreen', builder: (context, state) => const NotificationsSettingsScreen()),
...AuthRoutes.createRoutes(),
...MainNavigationRoutes.createRoutes(),
...ContentRoutes.createRoutes(),
...SettingsRoutes.createRoutes(),
...OtherRoutes.createRoutes(),
],
);
/// Обработка редиректов для deep links
static String? _handleRedirect(BuildContext context, GoRouterState state) {
final segments = state.uri.pathSegments;
if (segments.length >= 3) {
final type = segments[1];
final contentIdentifier = segments[2];
switch (type) {
case 'recipe':
FlutterNativeSplash.remove();
return '${RouteConstants.aboutRecipesScreen}?slug=$contentIdentifier';
case 'blog_post':
FlutterNativeSplash.remove();
return '${RouteConstants.blogPostScreen}?id=$contentIdentifier';
case 'plan':
FlutterNativeSplash.remove();
return '${RouteConstants.aboutPlanScreen}?slug=$contentIdentifier';
case 'diet':
FlutterNativeSplash.remove();
return '${RouteConstants.aboutDietScreen}?slug=$contentIdentifier';
}
}
return null;
}
}

View File

@@ -0,0 +1,72 @@
/// Константы приложения
class AppConstants {
// Размеры
static const double defaultPadding = 16.0;
static const double smallPadding = 8.0;
static const double largePadding = 24.0;
static const double extraLargePadding = 32.0;
// Радиусы
static const double defaultBorderRadius = 8.0;
static const double smallBorderRadius = 4.0;
static const double largeBorderRadius = 16.0;
// Высоты
static const double defaultButtonHeight = 48.0;
static const double smallButtonHeight = 36.0;
static const double largeButtonHeight = 56.0;
// Ширины
static const double maxContentWidth = 400.0;
static const double minContentWidth = 280.0;
// Анимации
static const Duration defaultAnimationDuration = Duration(milliseconds: 300);
static const Duration fastAnimationDuration = Duration(milliseconds: 150);
static const Duration slowAnimationDuration = Duration(milliseconds: 500);
// Таймауты
static const Duration defaultTimeout = Duration(seconds: 30);
static const Duration shortTimeout = Duration(seconds: 10);
static const Duration longTimeout = Duration(minutes: 2);
// Лимиты
static const int maxImageSize = 5 * 1024 * 1024; // 5MB
static const int maxVideoSize = 100 * 1024 * 1024; // 100MB
static const int maxTextLength = 1000;
static const int maxTitleLength = 100;
static const int maxDescriptionLength = 500;
// Пагинация
static const int defaultPageSize = 20;
static const int smallPageSize = 10;
static const int largePageSize = 50;
// Форматы
static const List<String> supportedImageFormats = ['jpg', 'jpeg', 'png', 'webp'];
static const List<String> supportedVideoFormats = ['mp4', 'mov', 'avi'];
// Регулярные выражения
static const String emailRegex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
static const String phoneRegex = r'^\+?[1-9]\d{1,14}$';
static const String urlRegex = r'^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$';
// Сообщения
static const String defaultErrorMessage = 'Произошла ошибка. Попробуйте еще раз.';
static const String networkErrorMessage = 'Проблема с подключением к интернету.';
static const String timeoutErrorMessage = 'Превышено время ожидания.';
static const String unknownErrorMessage = 'Неизвестная ошибка.';
// Ключи для SharedPreferences
static const String welcomeScreenShownKey = 'welcomeScreenWasShowed';
static const String userTokenKey = 'userToken';
static const String userDataKey = 'userData';
static const String settingsKey = 'settings';
// API endpoints
static const String apiVersion = '/api';
static const String authEndpoint = '/auth';
static const String usersEndpoint = '/users';
static const String postsEndpoint = '/posts';
static const String commentsEndpoint = '/comments';
}

View File

@@ -0,0 +1,42 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
/// Базовый класс для всех Cubit'ов с унифицированной обработкой ошибок
abstract class BaseCubit<State> extends Cubit<State> {
BaseCubit(super.initialState);
/// Унифицированная обработка ошибок
void handleError(Object error, [StackTrace? stackTrace]) {
log('Error in $runtimeType: $error', stackTrace: stackTrace);
// Здесь можно добавить отправку ошибок в Sentry или другую систему мониторинга
}
/// Безопасное выполнение асинхронных операций
Future<T?> safeExecute<T>(Future<T> Function() operation) async {
try {
return await operation();
} catch (error, stackTrace) {
handleError(error, stackTrace);
return null;
}
}
/// Безопасное выполнение асинхронных операций с обработкой состояния
Future<T?> safeExecuteWithState<T>(
Future<T> Function() operation, {
required State loadingState,
required State Function(T result) successState,
required State Function(Object error) errorState,
}) async {
try {
emit(loadingState);
final result = await operation();
emit(successState(result));
return result;
} catch (error, stackTrace) {
handleError(error, stackTrace);
emit(errorState(error));
return null;
}
}
}

View File

@@ -0,0 +1,28 @@
/// Утилиты для работы с состояниями Cubit'ов
class StateUtils {
/// Проверяет, является ли состояние загрузочным
static bool isLoading(dynamic state) {
return state.toString().contains('loading') || state.toString().contains('Loading');
}
/// Проверяет, является ли состояние загруженным
static bool isLoaded(dynamic state) {
return state.toString().contains('loaded') || state.toString().contains('Loaded');
}
/// Проверяет, является ли состояние ошибочным
static bool isError(dynamic state) {
return state.toString().contains('error') || state.toString().contains('Error');
}
/// Получает сообщение об ошибке из состояния
static String? getErrorMessage(dynamic state) {
if (isError(state)) {
// Попытка извлечь сообщение об ошибке из состояния
final stateString = state.toString();
final errorMatch = RegExp(r'error[:\s]+([^,}]+)').firstMatch(stateString);
return errorMatch?.group(1)?.trim();
}
return null;
}
}

View File

@@ -0,0 +1,144 @@
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_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/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/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.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/settings/presentation/bloc/settings_cubit/settings_cubit.dart';
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:get_it/get_it.dart';
/// Регистрация всех Cubit'ов в системе внедрения зависимостей
class CubitRegistrations {
static final sl = GetIt.instance;
/// Регистрация всех Cubit'ов
static void registerAll() {
_registerSingletonCubits();
_registerFactoryCubits();
}
/// Регистрация Cubit'ов как синглтонов
static void _registerSingletonCubits() {
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
sl.registerLazySingleton<InternetConnectionCubit>(() => InternetConnectionCubit());
}
/// Регистрация Cubit'ов как фабрик
static void _registerFactoryCubits() {
// Auth related cubits
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
// Feed related cubits
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
// Chat related cubits
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
// Profile related cubits
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
// Content creation cubits
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
// Content viewing cubits
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
// User content cubits
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
// Other feature cubits
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
sl.registerFactory<DiaryCubit>(
() => DiaryCubit(
chatRepository: sl(),
diaryRepository: sl(),
glossaryRepository: sl(),
profileRepository: sl(),
dietRepository: sl(),
settingsRepository: sl(),
),
);
sl.registerFactory<SettingsCubit>(
() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()),
);
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl()));
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(feedRepository: sl(), repository: sl(), profileRepository: sl()));
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
}
}

View File

@@ -0,0 +1,33 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:get_it/get_it.dart';
/// Регистрация внешних зависимостей в системе внедрения зависимостей
class ExternalRegistrations {
static final sl = GetIt.instance;
/// Регистрация всех внешних зависимостей
static void registerAll() {
_registerDio();
}
/// Регистрация Dio HTTP клиента
static void _registerDio() {
sl.registerLazySingleton(
() => Dio(
BaseOptions(
baseUrl: baseUrl,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
},
),
)..interceptors.addAll([
TokenInterceptor(),
PhoneVerifyErrorInterceptor(),
]),
);
}
}

View File

@@ -0,0 +1,89 @@
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
import 'package:eatvio_app/features/registration/repositories/registration_repository.dart';
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
import 'package:get_it/get_it.dart';
/// Регистрация всех Repository в системе внедрения зависимостей
class RepositoryRegistrations {
static final sl = GetIt.instance;
/// Регистрация всех Repository
static void registerAll() {
_registerSingletonRepositories();
_registerFactoryRepositories();
}
/// Регистрация Repository как синглтонов
static void _registerSingletonRepositories() {
// Core repositories
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
sl.registerLazySingleton<SettingsRepository>(() => SettingsRepository(settingsService: sl()));
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
sl.registerLazySingleton<RecipeRepository>(() => RecipeRepository(recipeService: sl()));
sl.registerLazySingleton<ComplainingRepository>(() => ComplainingRepository(service: sl()));
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(authService: sl()));
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
// Content repositories
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
sl.registerLazySingleton<CreateDietRepository>(() => CreateDietRepository(createDietService: sl()));
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
// Auth repositories
sl.registerLazySingleton<RegistrationRepository>(() => RegistrationRepository(registrationService: sl()));
}
/// Регистрация Repository как фабрик
static void _registerFactoryRepositories() {
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
}
}

View File

@@ -0,0 +1,88 @@
import 'package:eatvio_app/core/comments/services/comments_service.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
import 'package:eatvio_app/features/plans/services/plan_service.dart';
import 'package:eatvio_app/features/plans/services/plans_service.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart';
import 'package:eatvio_app/features/settings/services/settings_service.dart';
import 'package:get_it/get_it.dart';
/// Регистрация всех Service в системе внедрения зависимостей
class ServiceRegistrations {
static final sl = GetIt.instance;
/// Регистрация всех Service
static void registerAll() {
_registerSingletonServices();
}
/// Регистрация Service как синглтонов
static void _registerSingletonServices() {
// Core services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
sl.registerLazySingleton<DietService>(() => DietService(sl()));
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
sl.registerLazySingleton<WsService>(() => WsService(sl()));
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
sl.registerLazySingleton<ComplainingService>(() => ComplainingService(sl()));
sl.registerLazySingleton<AuthService>(() => AuthService(sl()));
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
// Content services
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
sl.registerLazySingleton<BlogPostService>(() => BlogPostService(sl()));
sl.registerLazySingleton<CreateDietService>(() => CreateDietService(sl()));
// Auth services
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
}
}

View File

@@ -43,4 +43,12 @@ class EnvConfig {
// Connection Check URLs
static String get googleCheckUrl => dotenv.env['GOOGLE_CHECK_URL'] ?? '';
static String get yandexCheckUrl => dotenv.env['YANDEX_CHECK_URL'] ?? '';
// VK ID Configuration
static String get vkClientId => dotenv.env['VK_CLIENT_ID'] ?? '';
// Yandex ID Configuration
static String get yandexClientId => dotenv.env['YANDEX_CLIENT_ID'] ?? '';
static String get yandexClientSecret => dotenv.env['YANDEX_CLIENT_SECRET'] ?? '';
static String get yandexRedirectUri => dotenv.env['YANDEX_REDIRECT_URI'] ?? '';
}

View File

@@ -1,265 +1,27 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/core/comments/services/comments_service.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/dio/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/core/di/cubit_registrations.dart';
import 'package:eatvio_app/core/di/external_registrations.dart';
import 'package:eatvio_app/core/di/repository_registrations.dart';
import 'package:eatvio_app/core/di/service_registrations.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart';
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
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/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';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart';
import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart';
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_cubit.dart';
import 'package:eatvio_app/features/plans/repositories/plans_repository.dart';
import 'package:eatvio_app/features/plans/services/plans_service.dart';
import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart';
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
import 'package:eatvio_app/features/plans/services/plan_service.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/profile/services/profile_service.dart';
import 'package:eatvio_app/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_cubit.dart';
import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/my_recieps_cubit.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.dart';
import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart';
import 'package:eatvio_app/features/recipe/services/my_recipes_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';
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
import 'package:eatvio_app/features/settings/services/settings_service.dart';
import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_cubit.dart';
import 'package:get_it/get_it.dart';
import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart' as profile_plans;
/// Глобальный экземпляр GetIt для внедрения зависимостей
final sl = GetIt.instance;
/// Инициализация системы внедрения зависимостей
Future<void> init() async {
// Cubit singletons
// Регистрация внешних зависимостей (Dio, etc.)
ExternalRegistrations.registerAll();
// Регистрация сервисов
ServiceRegistrations.registerAll();
// Регистрация репозиториев
RepositoryRegistrations.registerAll();
// Регистрация Cubit'ов
CubitRegistrations.registerAll();
// Регистрация WebSocket менеджера
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl(), notificationsRepository: sl()));
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
sl.registerLazySingleton<ChatsCubit>(() => ChatsCubit(chatRepository: sl(), wsManager: sl(), profileRepository: sl()));
sl.registerLazySingleton<InternetConnectionCubit>(() => InternetConnectionCubit());
// Cubit factories
sl.registerFactory<FeedCubit>(() => FeedCubit(feedRepository: sl()));
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(cardsRepository: sl(), chatRepository: sl(), wsManager: sl()));
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl(), consultantRepository: sl()));
sl.registerFactory<profile_plans.PlansCubit>(() => profile_plans.PlansCubit(plansRepository: sl()));
sl.registerFactory<DietsCubit>(() => DietsCubit(feedRepository: sl(), dietsRepository: sl()));
sl.registerFactory<CommentsCubit>(() => CommentsCubit(feedRepository: sl(), commentsRepository: sl()));
sl.registerFactory<PlansCubit>(() => PlansCubit(feedRepository: sl(), planRepository: sl()));
sl.registerFactory<SettingsCubit>(() => SettingsCubit(profileRepository: sl(), settingsRepository: sl(), consultantRepository: sl(), consultationsRepository: sl()));
sl.registerFactory<RegisterConsultantCubit>(() => RegisterConsultantCubit(consultantRepository: sl()));
sl.registerFactory<DiaryCubit>(
() => DiaryCubit(
chatRepository: sl(),
diaryRepository: sl(),
glossaryRepository: sl(),
profileRepository: sl(),
dietRepository: sl(),
settingsRepository: sl(),
),
);
sl.registerFactory<AboutDietCubit>(() => AboutDietCubit(dietsRepository: sl(), profileRepository: sl()));
sl.registerFactory<BlogPostCreateCubit>(() => BlogPostCreateCubit(blogPostRepository: sl()));
sl.registerFactory<SubCubit>(() => SubCubit(profileRepository: sl()));
sl.registerFactory<CreateDietCubit>(() => CreateDietCubit(profileRepository: sl(), createDietRepository: sl(), glossaryRepository: sl()));
sl.registerFactory<CreatePlanCubit>(() => CreatePlanCubit(createPlanRepository: sl()));
sl.registerFactory<FeedProfileCubit>(() => FeedProfileCubit(profileRepository: sl()));
sl.registerFactory<AboutPlanCubit>(() => AboutPlanCubit(planRepository: sl()));
sl.registerFactory<CreateRecipeCubit>(() => CreateRecipeCubit(recipeRepository: sl(), glossaryRepository: sl()));
sl.registerFactory<MyDietsCubit>(() => MyDietsCubit(myDietsRepository: sl(), createDietRepository: sl()));
sl.registerFactory<MyPlansCubit>(() => MyPlansCubit(myPlansRepository: sl()));
sl.registerFactory<UserDetailsCubit>(() => UserDetailsCubit(profileRepository: sl()));
sl.registerFactory<ConsultantsCubit>(() => ConsultantsCubit(feedRepository: sl()));
sl.registerFactory<AboutRecipeCubit>(() => AboutRecipeCubit(recipeRepository: sl(), profileRepository: sl()));
sl.registerFactory<AuthCubit>(() => AuthCubit(authRepository: sl()));
sl.registerFactory<ComplainingCubit>(() => ComplainingCubit(repository: sl()));
sl.registerFactory<UserCardsCubit>(() => UserCardsCubit(cardsRepository: sl()));
sl.registerFactory<OrderInfoCubit>(() => OrderInfoCubit(chatRepository: sl()));
sl.registerFactory<ConsultationsCubit>(() => ConsultationsCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantCommentsCubit>(() => ConsultantCommentsCubit(diaryRepository: sl()));
sl.registerFactory<CancelPlanCubit>(() => CancelPlanCubit(cancelPlanRepository: sl()));
sl.registerFactory<VerifyEmailCubit>(() => VerifyEmailCubit(profileRepository: sl()));
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
sl.registerFactory<ResetPasswordCubit>(() => ResetPasswordCubit(resetPasswordRepository: sl()));
sl.registerFactory<BlogPostCubit>(() => BlogPostCubit(sl()));
sl.registerFactory<BlockedUsersCubit>(() => BlockedUsersCubit(blockedUsersRepository: sl()));
sl.registerFactory<NotificationSettingsCubit>(() => NotificationSettingsCubit(notificationSettingsRepository: sl()));
sl.registerFactory<ConsultantRatingCubit>(() => ConsultantRatingCubit(sl()));
sl.registerFactory<ViewsCubit>(() => ViewsCubit(viewsRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));
sl.registerLazySingleton<FeedRepository>(() => FeedRepository(feedService: sl()));
sl.registerLazySingleton<RegistrationRepository>(() => RegistrationRepository(registrationService: sl()));
sl.registerLazySingleton<CalendarRepository>(() => CalendarRepository(calendarService: sl()));
sl.registerLazySingleton<ProfileRepository>(() => ProfileRepository(profileService: sl()));
sl.registerLazySingleton<RecipeRepository>(() => RecipeRepository(recipeService: sl()));
sl.registerLazySingleton<SettingsRepository>(() => SettingsRepository(settingsService: sl()));
sl.registerLazySingleton<ConsultantRepository>(() => ConsultantRepository(consultantService: sl()));
sl.registerLazySingleton<DiaryRepository>(() => DiaryRepository(diaryService: sl()));
sl.registerLazySingleton<GlossaryRepository>(() => GlossaryRepository(glossaryService: sl()));
sl.registerLazySingleton<DietRepository>(() => DietRepository(dietsService: sl()));
sl.registerLazySingleton<BlogPostRepository>(() => BlogPostRepository(blogPostService: sl()));
sl.registerLazySingleton<CreateDietRepository>(() => CreateDietRepository(createDietService: sl()));
sl.registerLazySingleton<PlansRepository>(() => PlansRepository(plansService: sl()));
sl.registerLazySingleton<PlanRepository>(() => PlanRepository(planService: sl()));
sl.registerLazySingleton<MyDietsRepository>(() => MyDietsRepository(myDietsService: sl()));
sl.registerLazySingleton<MyPlansRepository>(() => MyPlansRepository(myPlansService: sl()));
sl.registerLazySingleton<AboutRecipeRepository>(() => AboutRecipeRepository(recipeService: sl()));
sl.registerLazySingleton<ComplainingRepository>(() => ComplainingRepository(service: sl()));
sl.registerLazySingleton<AuthRepository>(() => AuthRepository(authService: sl()));
sl.registerLazySingleton<CardsRepository>(() => CardsRepository(cardsService: sl()));
sl.registerLazySingleton<ConsultationsRepository>(() => ConsultationsRepository(consultationService: sl()));
sl.registerLazySingleton<CancelPlanRepository>(() => CancelPlanRepository(cancelService: sl()));
sl.registerLazySingleton<CreatePlanRepository>(() => CreatePlanRepository(createPlanService: sl()));
sl.registerLazySingleton<NotificationsRepository>(() => NotificationsRepository(notificationsService: sl()));
sl.registerFactory<MyRecipesRepository>(() => MyRecipesRepository(myRecipesService: sl()));
sl.registerFactory<ResetPasswordRepository>(() => ResetPasswordRepository(resetPasswordService: sl()));
sl.registerLazySingleton<BlockedUsersRepository>(() => BlockedUsersRepository(blockedUserService: sl()));
sl.registerLazySingleton<CommentsRepository>(() => CommentsRepository(service: sl()));
sl.registerLazySingleton<NotificationSettingsRepository>(() => NotificationSettingsRepository(sl()));
sl.registerFactory<ConsultantRatingRepository>(() => ConsultantRatingRepository(sl()));
sl.registerFactory<ViewsRepository>(() => ViewsRepository(viewsService: sl()));
// Services
sl.registerLazySingleton<ChatService>(() => ChatService(sl()));
sl.registerLazySingleton<FeedService>(() => FeedService(sl()));
sl.registerLazySingleton<RegistrationService>(() => RegistrationService(sl()));
sl.registerLazySingleton<CalendarService>(() => CalendarService(sl()));
sl.registerLazySingleton<ProfileService>(() => ProfileService(sl()));
sl.registerLazySingleton<CreateRecipeService>(() => CreateRecipeService(sl()));
sl.registerLazySingleton<SettingsService>(() => SettingsService(sl()));
sl.registerLazySingleton<ConsultantService>(() => ConsultantService(sl()));
sl.registerLazySingleton<DiaryService>(() => DiaryService(sl()));
sl.registerLazySingleton<GlossaryService>(() => GlossaryService(sl()));
sl.registerLazySingleton<DietService>(() => DietService(sl()));
sl.registerLazySingleton<BlogPostService>(() => BlogPostService(sl()));
sl.registerLazySingleton<CreateDietService>(() => CreateDietService(sl()));
sl.registerLazySingleton<PlansService>(() => PlansService(sl()));
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
sl.registerLazySingleton<WsService>(() => WsService(sl()));
sl.registerLazySingleton<AboutRecipeService>(() => AboutRecipeService(sl()));
sl.registerLazySingleton<ComplainingService>(() => ComplainingService(sl()));
sl.registerLazySingleton<AuthService>(() => AuthService(sl()));
sl.registerLazySingleton<CardsService>(() => CardsService(sl()));
sl.registerLazySingleton<ConsultationService>(() => ConsultationService(sl()));
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi());
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));
sl.registerLazySingleton<MyRecipesService>(() => MyRecipesService(sl()));
sl.registerLazySingleton<ConsultantRatingService>(() => ConsultantRatingService(sl()));
sl.registerLazySingleton<NotificationSettingsService>(() => NotificationSettingsService(sl()));
sl.registerLazySingleton<ViewsService>(() => ViewsService(sl()));
// External
sl.registerLazySingleton(
() =>
Dio(BaseOptions(baseUrl: baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'}))
..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]),
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/forget_password_screen.dart';
import 'package:eatvio_app/features/auth/presentation/pages/scan_qr_code_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/verify_screen.dart';
import 'package:eatvio_app/features/registration/presentation/pages/verify_email_screen.dart';
import 'package:go_router/go_router.dart';
/// Маршруты для аутентификации
class AuthRoutes {
/// Создание маршрутов аутентификации
static List<RouteBase> createRoutes() {
return [
GoRoute(
path: RouteConstants.authScreen,
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
routes: [
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
GoRoute(
path: RouteConstants.forgetPasswordScreen,
name: 'forgetPasswordScreen',
builder: (context, state) => ForgetPasswordScreen(isAuth: state.extra as bool),
),
GoRoute(path: RouteConstants.scanQRCodeScreen, name: 'scanQRCodeScreen', builder: (context, state) => const ScanQRCodeScreen()),
],
),
GoRoute(
path: RouteConstants.verifyScreen,
name: 'verifyScreen',
builder: (context, state) => VerifyScreen(passKey: state.extra as String),
),
GoRoute(path: RouteConstants.verifyEmailScreen, name: 'verifyEmailScreen', builder: (context, state) => const VerifyEmailScreen()),
];
}
}

View File

@@ -0,0 +1,73 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart';
import 'package:eatvio_app/features/diets/presentation/pages/about_diet_screen.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/plans/presentation/pages/about_plan_screen.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/about_recipe_screen.dart';
import 'package:go_router/go_router.dart';
/// Маршруты для контента
class ContentRoutes {
/// Создание маршрутов контента
static List<RouteBase> createRoutes() {
return [
GoRoute(
path: RouteConstants.aboutPlanScreen,
name: 'aboutPlanScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutPlanScreen(slug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map<String, dynamic>;
return AboutPlanScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: RouteConstants.aboutRecipesScreen,
name: 'aboutRecipesScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutRecipeScreen(recipeSlug: slug, navigateToComments: false, index: null);
}
final data = state.extra as Map<String, dynamic>;
return AboutRecipeScreen(recipeSlug: data['recipe'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: RouteConstants.aboutDietScreen,
name: 'aboutDietScreen',
builder: (context, state) {
final slug = state.uri.queryParameters['slug'];
if (slug != null) {
return AboutDietScreen(slug: slug, index: null, navigateToComments: false);
}
final data = state.extra as Map<String, dynamic>;
return AboutDietScreen(slug: data['slug'] as String, index: data['index'] as int?, navigateToComments: data['navigateToComments'] as bool);
},
),
GoRoute(
path: RouteConstants.blogPostCreateScreen,
name: 'blogPostCreateScreen',
builder: (context, state) => BlogPostCreateScreen(feedBlogPostModel: state.extra != null ? state.extra as FeedBlogPostModel : null),
),
GoRoute(
path: RouteConstants.blogPostScreen,
name: 'blogPostScreen',
builder: (context, state) {
final id = state.uri.queryParameters['id'];
final parsedId = int.tryParse(id ?? '');
if (parsedId != null) {
return BlogPostScreen(id: parsedId);
}
return BlogPostScreen(id: state.extra as int);
},
),
];
}
}

View File

@@ -0,0 +1,94 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chats_screen.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
import 'package:eatvio_app/features/feed/presentation/pages/feed_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/menu_screen.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'dart:developer';
/// Маршруты для основной навигации
class MainNavigationRoutes {
/// Создание маршрутов основной навигации
static List<RouteBase> createRoutes() {
return [
ShellRoute(
builder: (context, state, child) {
return BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return BottomNavigationBarWidget(profileModel: state.profile, child: child);
}
return const SizedBox.shrink();
},
);
},
routes: [
GoRoute(
path: RouteConstants.myProfileScreen,
name: 'myProfileScreen',
pageBuilder: (context, state) {
return const NoTransitionPage(child: ProfileScreen());
},
),
GoRoute(
path: RouteConstants.feedScreen,
name: 'feedScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()),
redirect: (context, state) async {
try {
await sl<WsManager>().initialize();
} catch (e, st) {
log(e.toString(), stackTrace: st);
}
return null;
},
),
GoRoute(
path: RouteConstants.diaryScreen,
name: 'diaryScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: DiaryScreen()),
),
GoRoute(
path: ConsultationsScreen.path,
name: ConsultationsScreen.name,
pageBuilder: (context, state) => const NoTransitionPage(child: ConsultationsScreen()),
),
GoRoute(
path: RouteConstants.chatSelectScreen,
name: 'chatSelectScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: ChatsScreen()),
),
GoRoute(
path: RouteConstants.notificationsScreen,
name: 'notificationsScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: NotificationsScreen()),
),
GoRoute(
path: RouteConstants.menuScreen,
name: 'menuScreen',
pageBuilder: (context, state) => NoTransitionPage(
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return MenuScreen(profileModel: state.profile);
}
return const SizedBox.shrink();
},
),
),
),
],
),
];
}
}

View File

@@ -0,0 +1,229 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/calendar/presentation/pages/calendar_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/chat_screen.dart';
import 'package:eatvio_app/features/diary/presentation/pages/diary_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/empty_chat_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/payment_screen.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/become_consultant_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/consultant_rating_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen.dart';
import 'package:eatvio_app/features/create_recipe/presentation/pages/recipe_create_screen.dart';
import 'package:eatvio_app/features/dev_tools/presentation/dev_tools_screen.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/glossary/presentation/pages/glossary_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/blocked_users_screen.dart';
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/card_settings.dart';
import 'package:eatvio_app/features/my_finance/presentation/pages/user_finance_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/profile_screen.dart';
import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
import 'package:eatvio_app/features/support/presentation/pages/support_screen.dart';
import 'package:eatvio_app/features/update_screen/presentation/update_screen.dart';
import 'package:eatvio_app/features/user_details/presentation/user_details_screen.dart';
import 'package:eatvio_app/features/welcome/presentation/pages/welcome_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/consultations_archive_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:version/version.dart';
/// Остальные маршруты приложения
class OtherRoutes {
/// Создание остальных маршрутов
static List<RouteBase> createRoutes() {
return [
// Welcome and update screens
GoRoute(
path: RouteConstants.welcomeScreen,
name: 'welcomeScreen',
builder: (context, state) => const WelcomeScreen(),
redirect: (context, state) async {
try {
final sharedPreferences = await SharedPreferences.getInstance();
final welcomeScreenWasShowed = sharedPreferences.getBool('welcomeScreenWasShowed') ?? false;
await sl<FirebaseApi>().registerPushToken();
if (welcomeScreenWasShowed == false) {
return null;
} else if (await TokenStorage.haveTokenInStorage()) {
return RouteConstants.feedScreen;
} else {
return RouteConstants.authScreen;
}
} catch (e) {
return RouteConstants.feedScreen;
}
},
),
GoRoute(
path: RouteConstants.updateScreen,
name: 'updateScreen',
builder: (context, state) => const UpdateScreen(),
redirect: (context, state) async {
try {
final versions = (await sl<Dio>().get('$baseUrl/api/app_versions')).data;
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version;
Version currentVersion = Version.parse(version);
Version latestVersion = Version.parse(versions['min_version']);
if (latestVersion > currentVersion) {
return null;
} else {
return RouteConstants.welcomeScreen;
}
} catch (e) {
return RouteConstants.welcomeScreen;
}
},
),
// Archive routes
GoRoute(path: ConsultationsArchiveScreen.path, name: ConsultationsArchiveScreen.name, builder: (context, state) => const ConsultationsArchiveScreen()),
GoRoute(path: ConsultantFinancesScreen.path, name: ConsultantFinancesScreen.name, builder: (context, state) => const ConsultantFinancesScreen()),
GoRoute(path: UserPurchasesScreen.path, name: UserPurchasesScreen.name, builder: (context, state) => const UserPurchasesScreen()),
// Finance routes
GoRoute(path: RouteConstants.userFinanceScreen, name: 'userFinanceScreen', builder: (context, state) => const UserFinanceScreen()),
GoRoute(
path: CardSettingsScreen.path,
name: CardSettingsScreen.name,
builder: (context, state) {
final chosenPlanId = state.extra as int?;
return CardSettingsScreen(chosenPlanId: chosenPlanId);
},
),
// Content creation routes
GoRoute(
path: RecipeCreateScreen.path,
name: RecipeCreateScreen.name,
builder: (context, state) => RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null),
),
GoRoute(
path: CreateDietScreen.path,
name: CreateDietScreen.name,
builder: (context, state) {
final diet = state.extra as Diet?;
return CreateDietScreen(diet: diet);
},
),
GoRoute(
path: DietPreviewScreen.path,
name: DietPreviewScreen.name,
builder: (context, state) {
final data = state.extra as Diet;
return DietPreviewScreen(diet: data);
},
),
GoRoute(path: RouteConstants.createPlanScreen, name: 'createPlanScreen', builder: (context, state) => const CreatePlanScreen()),
GoRoute(path: MyDietsScreen.path, name: MyDietsScreen.name, builder: (context, state) => const MyDietsScreen()),
GoRoute(path: MyPlansScreen.path, name: MyPlansScreen.name, builder: (context, state) => const MyPlansScreen()),
GoRoute(path: MyRecipesScreen.path, name: MyRecipesScreen.name, builder: (context, state) => const MyRecipesScreen()),
// Profile routes
GoRoute(
path: RouteConstants.profileScreen,
name: 'profileScreen',
builder: (context, state) => ProfileScreen(author: state.extra as Author),
),
GoRoute(
path: RouteConstants.subsScreen,
name: 'subsScreen',
builder: (context, state) => SubsScreen(slug: state.pathParameters['slug']!, subType: state.extra as SubType),
),
// Chat routes
GoRoute(
path: RouteConstants.chatScreen,
name: 'chatScreen',
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final uuid = data['uuid'] as String;
final sendInitialMessage = data['sendInitialMessage'] as bool?;
return ChatScreen(uuid: uuid, sendInitialMessage: sendInitialMessage);
},
),
GoRoute(
path: RouteConstants.emptyChatScreen,
name: 'emptyChatScreen',
builder: (context, state) => EmptyChatScreen(author: state.extra as Author),
),
// Consultant routes
GoRoute(path: RouteConstants.becomeConsultantScreen, name: 'becomeConsultantScreen', builder: (context, state) => const BecomeConsultantScreen()),
GoRoute(
path: RouteConstants.stepperBecomeConsultantScreen,
name: 'stepperBecomeConsultantScreen',
builder: (context, state) => StepperBecomeConsultantScreen(slug: state.extra == null ? null : state.extra as String),
),
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
GoRoute(path: RouteConstants.supportScreen, name: 'supportScreen', builder: (context, state) => const SupportScreen()),
GoRoute(
path: RouteConstants.photoViewScreen,
name: 'photoViewScreen',
builder: (context, state) => PhotoViewScreen(imageUrls: state.extra as List<String>, index: int.parse(state.pathParameters['index']!)),
),
// Other routes
GoRoute(path: RouteConstants.glossaryScreen, name: 'glossaryScreen', builder: (context, state) => const GlossaryScreen()),
GoRoute(path: RouteConstants.blockedUsersScreen, name: 'blockedUsersScreen', builder: (context, state) => const BlockedUsersScreen()),
GoRoute(
path: RouteConstants.notificationsSettingsScreen,
name: 'notificationsSettingsScreen',
builder: (context, state) => const NotificationsSettingsScreen(),
),
GoRoute(
path: UserDetailsScreen.path,
name: UserDetailsScreen.name,
builder: (context, state) => UserDetailsScreen(slug: state.extra as String),
),
GoRoute(path: DevToolsScreen.path, name: DevToolsScreen.name, builder: (context, state) => const DevToolsScreen()),
GoRoute(path: ConsultantRatingScreen.path, name: ConsultantRatingScreen.name, builder: (context, state) => const ConsultantRatingScreen()),
GoRoute(
path: InfoByOrderScreen.path,
name: InfoByOrderScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?;
return InfoByOrderScreen(planId: planId, successfulPayment: successfulPayment);
},
),
GoRoute(
path: PaymentScreen.path,
name: PaymentScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int;
final url = data['url'] as String;
return PaymentScreen(url: url, planId: planId);
},
),
GoRoute(
path: UserDiaryScreen.path,
name: UserDiaryScreen.name,
builder: (context, state) {
final data = state.extra as Map<String, int>;
final diaryId = data['diaryId'] as int;
final currentPlanDay = data['currentDiaryDay'] as int;
return UserDiaryScreen(diaryId: diaryId, currentPlanDay: currentPlanDay);
},
),
];
}
}

View File

@@ -0,0 +1,84 @@
/// Константы маршрутов приложения
class RouteConstants {
// Auth routes
static const String authScreen = '/auth-screen';
static const String registrationScreen = 'registration-screen';
static const String forgetPasswordScreen = 'forget-password-screen';
static const String scanQRCodeScreen = 'scan-qr-code-screen';
static const String verifyScreen = '/verify-screen';
static const String verifyEmailScreen = '/verify-email-screen';
// Main navigation routes
static const String myProfileScreen = '/my-profile-screen';
static const String feedScreen = '/feed-screen';
static const String diaryScreen = '/diary-screen';
static const String consultationsScreen = '/consultations-screen';
static const String chatSelectScreen = '/chat-select-screen';
static const String notificationsScreen = '/notifications-screen';
static const String menuScreen = '/menu-screen';
// Content routes
static const String aboutPlanScreen = '/about-plan-screen';
static const String aboutRecipesScreen = '/about-recipes-screen';
static const String aboutDietScreen = '/about-diet-screen';
static const String blogPostScreen = '/blog-post-screen';
static const String blogPostCreateScreen = '/blog-post-create-screen';
// Profile routes
static const String profileScreen = '/profile-screen';
static const String subsScreen = '/subs-screen';
// Settings routes
static const String settingsScreen = '/settings-screen';
static const String generalInfoScreen = 'general-info-screen';
static const String otherInfoScreen = 'other-info-screen';
static const String physicalActivityScreen = 'physical-activity-screen';
static const String physicalFormScreen = 'physical-form-screen';
static const String pressureScreen = 'pressure-screen';
static const String changePasswordScreen = 'change-password-screen';
static const String changeEmailScreen = 'change-email-screen';
// Consultant routes
static const String becomeConsultantScreen = '/become-consultant-screen';
static const String stepperBecomeConsultantScreen = '/stepper-become-consultant-screen';
static const String consultantRatingScreen = '/consultant-rating-screen';
// Other routes
static const String welcomeScreen = '/welcome-screen';
static const String updateScreen = '/update-screen';
static const String calendarScreen = '/calendar-screen';
static const String supportScreen = '/support-screen';
static const String photoViewScreen = '/photo-view-screen';
static const String chatScreen = '/chat-screen';
static const String emptyChatScreen = '/empty-chat-screen';
static const String glossaryScreen = '/glossary-screen';
static const String blockedUsersScreen = '/blocked-users-screen';
static const String notificationsSettingsScreen = '/notifications-settings-screen';
// Finance routes
static const String userFinanceScreen = '/user-finance-screen';
static const String cardSettingsScreen = '/card-settings-screen';
static const String orderInfoScreen = '/order-info-screen';
static const String paymentScreen = '/payment-screen';
// Content creation routes
static const String recipeCreateScreen = '/recipe-create-screen';
static const String createDietScreen = '/create-diet-screen';
static const String dietPreviewScreen = '/diet-preview-screen';
static const String createPlanScreen = '/create-plan-screen';
static const String myDietsScreen = '/my-diets-screen';
static const String myPlansScreen = '/my-plans-screen';
static const String myRecipesScreen = '/my-recipes-screen';
// User details
static const String userDetailsScreen = '/user-details-screen';
static const String userDiaryScreen = '/user-diary-screen';
// Dev tools
static const String devToolsScreen = '/dev-tools-screen';
// Archive routes
static const String consultationsArchiveScreen = '/consultations-archive-screen';
static const String consultantFinancesScreen = '/consultant-finances-screen';
static const String userPurchasesScreen = '/user-purchases-screen';
}

View File

@@ -0,0 +1,62 @@
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/change_password_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/general_info_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/other_info_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/physical_activity_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/physical_form_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
import 'package:go_router/go_router.dart';
/// Маршруты для настроек
class SettingsRoutes {
/// Создание маршрутов настроек
static List<RouteBase> createRoutes() {
return [
GoRoute(
path: RouteConstants.settingsScreen,
name: 'settingsScreen',
builder: (context, state) => const SettingsScreen(),
routes: [
GoRoute(
path: RouteConstants.generalInfoScreen,
name: 'generalInfoScreen',
builder: (context, state) => GeneralInfoScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.otherInfoScreen,
name: 'otherInfoScreen',
builder: (context, state) => OtherInfoScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.physicalActivityScreen,
name: 'physicalActivityScreen',
builder: (context, state) => PhysicalActivityScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.physicalFormScreen,
name: 'physicalFormScreen',
builder: (context, state) => PhysicalFormScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.pressureScreen,
name: 'pressureScreen',
builder: (context, state) => PressureScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.changePasswordScreen,
name: 'changePasswordScreen',
builder: (context, state) => ChangePasswordScreen(settingsEntity: state.extra as SettingsEntity),
),
GoRoute(
path: RouteConstants.changeEmailScreen,
name: 'changeEmailScreen',
builder: (context, state) => ChangeEmailScreen(settingsEntity: state.extra as SettingsEntity),
),
],
),
];
}
}

View File

@@ -0,0 +1,87 @@
import 'package:eatvio_app/core/constants/app_constants.dart';
/// Утилиты для валидации
class ValidationUtils {
/// Валидация email
static bool isValidEmail(String email) {
if (email.isEmpty) return false;
final regex = RegExp(AppConstants.emailRegex);
return regex.hasMatch(email);
}
/// Валидация телефона
static bool isValidPhone(String phone) {
if (phone.isEmpty) return false;
final regex = RegExp(AppConstants.phoneRegex);
return regex.hasMatch(phone);
}
/// Валидация URL
static bool isValidUrl(String url) {
if (url.isEmpty) return false;
final regex = RegExp(AppConstants.urlRegex);
return regex.hasMatch(url);
}
/// Валидация длины текста
static bool isValidTextLength(String text, {int? maxLength}) {
final length = maxLength ?? AppConstants.maxTextLength;
return text.length <= length;
}
/// Валидация длины заголовка
static bool isValidTitleLength(String title) {
return title.isNotEmpty && title.length <= AppConstants.maxTitleLength;
}
/// Валидация длины описания
static bool isValidDescriptionLength(String description) {
return description.length <= AppConstants.maxDescriptionLength;
}
/// Валидация пароля
static bool isValidPassword(String password) {
// Минимум 8 символов, хотя бы одна буква и одна цифра
if (password.length < 8) return false;
final hasLetter = RegExp(r'[a-zA-Z]').hasMatch(password);
final hasDigit = RegExp(r'[0-9]').hasMatch(password);
return hasLetter && hasDigit;
}
/// Валидация имени пользователя
static bool isValidUsername(String username) {
// От 3 до 20 символов, только буквы, цифры и подчеркивания
if (username.length < 3 || username.length > 20) return false;
final regex = RegExp(r'^[a-zA-Z0-9_]+$');
return regex.hasMatch(username);
}
/// Получение сообщения об ошибке валидации
static String? getValidationErrorMessage(String? value, ValidationType type) {
if (value == null || value.isEmpty) {
return 'Поле не может быть пустым';
}
switch (type) {
case ValidationType.email:
return isValidEmail(value) ? null : 'Введите корректный email';
case ValidationType.phone:
return isValidPhone(value) ? null : 'Введите корректный номер телефона';
case ValidationType.url:
return isValidUrl(value) ? null : 'Введите корректный URL';
case ValidationType.password:
return isValidPassword(value) ? null : 'Пароль должен содержать минимум 8 символов, включая буквы и цифры';
case ValidationType.username:
return isValidUsername(value) ? null : 'Имя пользователя должно содержать от 3 до 20 символов (буквы, цифры, подчеркивания)';
case ValidationType.title:
return isValidTitleLength(value) ? null : 'Заголовок не может быть пустым и должен содержать не более ${AppConstants.maxTitleLength} символов';
case ValidationType.description:
return isValidDescriptionLength(value) ? null : 'Описание не может содержать более ${AppConstants.maxDescriptionLength} символов';
case ValidationType.text:
return isValidTextLength(value) ? null : 'Текст не может содержать более ${AppConstants.maxTextLength} символов';
}
}
}
/// Типы валидации
enum ValidationType { email, phone, url, password, username, title, description, text }

View File

@@ -0,0 +1,51 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:vk_id/vk_id.dart';
part 'auth_result_entity.freezed.dart';
@freezed
abstract class AuthResultEntity with _$AuthResultEntity {
const factory AuthResultEntity({
required String accessToken,
required String userId,
String? refreshToken,
String? firstName,
String? lastName,
String? email,
String? phone,
GenderEnum? gender,
String? birthDate,
}) = _AuthResultEntity;
}
enum GenderEnum {
male,
female,
notSet;
static GenderEnum? fromVkGender(VkSex? vkGender) {
switch (vkGender) {
case VkSex.male:
return GenderEnum.male;
case VkSex.female:
return GenderEnum.female;
case VkSex.notStated:
return GenderEnum.notSet;
case null:
return null;
}
}
static GenderEnum? fromString(String? gender) {
switch (gender) {
case 'male':
return GenderEnum.male;
case 'female':
return GenderEnum.female;
case 'notSet':
return GenderEnum.notSet;
default:
return null;
}
}
}

View File

@@ -0,0 +1,30 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'yandex_profile_model.freezed.dart';
part 'yandex_profile_model.g.dart';
@freezed
abstract class YandexProfileModel with _$YandexProfileModel {
factory YandexProfileModel({
required String id,
required String login,
required String birthday,
@JsonKey(name: 'client_id') required String clientId,
@JsonKey(name: 'display_name') required String displayName,
@JsonKey(name: 'real_name') required String realName,
@JsonKey(name: 'first_name') required String firstName,
@JsonKey(name: 'last_name') required String lastName,
@JsonKey(name: 'sex') required String sex,
@JsonKey(name: 'default_email') required String defaultEmail,
@JsonKey(name: 'default_phone') required YandexPhoneData defaultPhone,
}) = _YandexProfileModel;
factory YandexProfileModel.fromJson(Map<String, dynamic> json) => _$YandexProfileModelFromJson(json);
}
@freezed
abstract class YandexPhoneData with _$YandexPhoneData {
factory YandexPhoneData({required int id, required String number}) = _YandexPhoneData;
factory YandexPhoneData.fromJson(Map<String, dynamic> json) => _$YandexPhoneDataFromJson(json);
}

View File

@@ -1,6 +1,7 @@
// ignore_for_file: unused_element, duplicate_ignore
import 'dart:async';
import 'dart:developer';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/firebase_api.dart';
@@ -9,6 +10,7 @@ import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart';
import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state.dart';
@@ -119,6 +121,42 @@ class _AuthScreenState extends State<AuthScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Social auth buttons at the top (outlined)
const Text('Авторизация через:', style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_SocialButton(
onTap: () => _onVkPressed(),
backgroundColor: const Color(0xFF2787F5),
tooltip: 'VK ID',
child: SvgPicture.asset('assets/icons/vk_logo.svg', height: 24, width: 24),
),
_SocialButton(
onTap: _onOkPressed,
backgroundColor: const Color(0xFFF58220),
tooltip: 'Одноклассники',
child: SvgPicture.asset('assets/icons/ok_logo.svg', height: 24, width: 24),
),
_SocialButton(
onTap: _onMailRuPressed,
backgroundColor: const Color(0xFF005FF9),
tooltip: 'Mail.ru',
child: SvgPicture.asset('assets/icons/mail_logo.svg', height: 24, width: 24),
),
_SocialButton(
onTap: _onYandexPressed,
backgroundColor: const Color(0xFFFFCC00),
tooltip: 'Яндекс ID',
child: SvgPicture.asset('assets/icons/yandex_logo.svg', height: 24, width: 24),
),
],
),
const SizedBox(height: 16),
const CustomDivider(),
const SizedBox(height: 8),
const SizedBox(height: 16),
Text(s.email),
const SizedBox(height: 4),
CustomTextFormField(
@@ -168,52 +206,6 @@ class _AuthScreenState extends State<AuthScreen> {
),
],
),
// const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_SocialButton(
onTap: () => _onVkPressed(context),
backgroundColor: const Color(0xFF2787F5),
tooltip: 'VK ID',
child: SvgPicture.asset(
'assets/icons/vk.svg',
height: 24,
width: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
_SocialButton(
onTap: _onOkPressed,
backgroundColor: const Color(0xFFF58220),
tooltip: 'Одноклассники',
child: SvgPicture.asset(
'assets/icons/ok.svg',
height: 24,
width: 24,
colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn),
),
),
const _SocialButton(
onTap: _onMailRuPressed,
backgroundColor: Color(0xFF005FF9),
tooltip: 'Mail.ru',
child: Text(
'Mail',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700),
),
),
const _SocialButton(
onTap: _onYandexPressed,
backgroundColor: Color(0xFFFFCC00),
tooltip: 'Яндекс ID',
child: Text(
'Yandex',
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700),
),
),
],
),
const Spacer(),
Center(
child: RichText(
@@ -367,7 +359,6 @@ class _AuthScreenState extends State<AuthScreen> {
}
}
// ignore: unused_element
class _SocialButton extends StatelessWidget {
const _SocialButton({required this.onTap, required this.child, required this.backgroundColor, required this.tooltip});
@@ -381,12 +372,16 @@ class _SocialButton extends StatelessWidget {
return Tooltip(
message: tooltip,
child: InkWell(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(20),
onTap: onTap,
child: Ink(
height: 48,
width: (MediaQuery.of(context).size.width - 16 * 2 - 12 * 3) / 4,
decoration: BoxDecoration(color: backgroundColor, borderRadius: BorderRadius.circular(12)),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFFDBDBDB), width: 1),
),
child: Center(child: child),
),
),
@@ -394,45 +389,42 @@ class _SocialButton extends StatelessWidget {
}
}
// ignore: unused_element
void _onVkPressed(BuildContext context) async {
final accessToken = await SocialAuthFacade.signInWithVk(context: context);
if (accessToken == null) return;
final isAuthenticated = await sl<AuthCubit>().getTokenBySocial(provider: 'vk', accessToken: accessToken);
if (isAuthenticated) {
if (AppRouter.navigatorKey.currentContext != null) {
// Reuse success flow
// ignore: use_build_context_synchronously
await _reconnectAfterAuth();
void _onVkPressed() async {
try {
final accessToken = await SocialAuthFacade.signInWithVk();
if (accessToken == null) {
return;
}
} catch (e) {
log(e.toString());
}
}
void _onOkPressed() async {
final accessToken = await SocialAuthFacade.signInWithOk();
if (accessToken == null) return;
final isAuthenticated = await sl<AuthCubit>().getTokenBySocial(provider: 'ok', accessToken: accessToken);
if (isAuthenticated) {
await _reconnectAfterAuth();
try {
final accessToken = await SocialAuthFacade.signInWithOk();
if (accessToken == null) {
return;
}
} catch (e) {
log(e.toString());
}
}
void _onMailRuPressed() async {
final accessToken = await SocialAuthFacade.signInWithMailRu();
if (accessToken == null) return;
final isAuthenticated = await sl<AuthCubit>().getTokenBySocial(provider: 'mailru', accessToken: accessToken);
if (isAuthenticated) {
await _reconnectAfterAuth();
try {
final accessToken = await SocialAuthFacade.signInWithMailRu();
if (accessToken == null) {
return;
}
} catch (e) {
log(e.toString());
}
}
void _onYandexPressed() async {
final accessToken = await SocialAuthFacade.signInWithYandex();
if (accessToken == null) return;
final isAuthenticated = await sl<AuthCubit>().getTokenBySocial(provider: 'yandex', accessToken: accessToken);
if (isAuthenticated) {
await _reconnectAfterAuth();
}
}
Future<void> _reconnectAfterAuth() async {

View File

@@ -1,125 +1,226 @@
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'dart:developer';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:eatvio_app/features/auth/models/yandex_profile_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_login_yandex/flutter_login_yandex.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/env_config.dart';
import 'package:http/http.dart' as http;
import 'package:vk_id/vk_id.dart';
import 'package:webview_flutter/webview_flutter.dart';
class VkAuthResult {
VkAuthResult({required this.accessToken, this.refreshToken, this.userId, this.firstName, this.lastName, this.email, this.phone, this.gender, this.birthDate});
final String accessToken;
final String? refreshToken;
final String? userId;
final String? firstName;
final String? lastName;
final String? email;
final String? phone;
final String? gender;
final String? birthDate;
}
class SocialAuthFacade {
static Future<String?> signInWithVk({required BuildContext context}) async {
static Future<String?> signInWithVk() async {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
final result = await signInWithVkAndFetchProfile(context: context);
return result?.accessToken;
}
static Future<VkAuthResult?> signInWithVkAndFetchProfile({required BuildContext context}) async {
final clientIdStr = dotenv.maybeGet('VK_CLIENT_ID')?.trim() ?? Platform.environment['VK_CLIENT_ID'];
if (clientIdStr == null || clientIdStr.isEmpty) {
static Future<AuthResultEntity?> signInWithVkAndFetchProfile({required BuildContext context}) async {
final clientIdStr = EnvConfig.vkClientId.trim();
if (clientIdStr.isEmpty) {
return null;
}
try {
dynamic controller = VkIDController(clID: int.parse(clientIdStr));
final controller = VkIDController(clID: int.parse(clientIdStr));
final authUri = controller.generateAuthorizeLinkWithCodeVerifier();
final redirectScheme = 'vk$clientIdStr://';
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUri.toString(), redirectSchemePrefix: redirectScheme);
if (redirectParams == null) return null;
final code = redirectParams['code'];
final deviceId = redirectParams['device_id'];
if (code == null || deviceId == null) return null;
final tokenResponse = await controller.exchangeAuthorizationCodeForToken(code: code, deviceId: deviceId);
final accessToken = _extractAccessToken(tokenResponse);
if (accessToken == null) return null;
return VkAuthResult(accessToken: accessToken);
} catch (_) {
final authUrl = authUri.value.toString();
final codeVerifier = authUri.key;
final redirectScheme = 'vk$clientIdStr://';
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUrl, redirectSchemePrefix: redirectScheme);
if (redirectParams == null) {
return null;
}
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier);
} catch (e) {
return null;
}
}
static Future<AuthResultEntity?> _parseVKAuthResult({
required Map<String, String> redirectParams,
required VkIDController controller,
required String codeVerifier,
}) async {
final code = redirectParams['code'];
final deviceId = redirectParams['device_id'];
final state = redirectParams['state'];
if (code == null || deviceId == null || state == null) {
return null;
}
final authRes = await controller.retrieveOAuthToken(authorizationCode: code, deviceId: deviceId, codeVerifier: codeVerifier, state: state);
final auth = authRes.result;
if (auth == null) {
var msg = "Retrieve OAuth token error";
final err = authRes.error?.vkErr;
if (err != null) {
msg += ": ${err.error} - ${err.description}";
}
log(msg);
return null;
}
final profileRes = await controller.getProfileInfo();
final profile = profileRes.result;
if (profile == null) {
var msg = "Profile info load error";
final err = profileRes.error?.vkErr;
if (err != null) {
msg += ": ${err.error} - ${err.description}";
}
log(msg);
return null;
}
return AuthResultEntity(
accessToken: auth.accessToken,
refreshToken: auth.refreshToken,
userId: profile.userId.toString(),
firstName: profile.firstName,
lastName: profile.lastName,
email: profile.email,
phone: profile.phone,
gender: GenderEnum.fromVkGender(profile.parsedSex),
birthDate: profile.bDayDt?.toString(),
);
}
Future<Map<String, dynamic>?> exchangeCodeForToken(String code, String clientId, String clientSecret) async {
final response = await http.post(
Uri.parse('https://oauth.yandex.ru/token'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {'grant_type': 'authorization_code', 'code': code, 'client_id': clientId, 'client_secret': clientSecret},
);
if (response.statusCode == 200) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
log('Error exchanging code: ${response.body}');
}
return null;
}
static Future<String?> signInWithOk() async {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
return _signInWithVkIdProvider(context: context, provider: 'ok');
final vkAuthRes = await _signInWithVkIdProvider(context: context, provider: VkProvider.okRu);
return vkAuthRes?.accessToken;
}
static Future<String?> signInWithMailRu() async {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
return _signInWithVkIdProvider(context: context, provider: 'mail');
final vkAuthRes = await _signInWithVkIdProvider(context: context, provider: VkProvider.mailRu);
return vkAuthRes?.accessToken;
}
static Future<String?> signInWithYandex() async {
final clientId = dotenv.maybeGet('YANDEX_CLIENT_ID')?.trim() ?? Platform.environment['YANDEX_CLIENT_ID'];
final redirectUri = dotenv.maybeGet('YANDEX_REDIRECT_URI')?.trim() ?? Platform.environment['YANDEX_REDIRECT_URI'];
static Future<AuthResultEntity?> signInWithYandex() async {
final clientId = EnvConfig.yandexClientId.trim();
final redirectUri = EnvConfig.yandexRedirectUri.trim();
if (clientId == null || clientId.isEmpty || redirectUri == null || redirectUri.isEmpty) {
if (clientId.isEmpty || redirectUri.isEmpty) {
return null;
}
try {
final context = AppRouter.navigatorKey.currentContext;
if (context == null) return null;
final authorizeUrl = Uri.https('oauth.yandex.ru', '/authorize', {
'response_type': 'code',
'client_id': clientId,
'redirect_uri': redirectUri,
'force_confirm': 'yes',
}).toString();
final redirectSchemePrefix = redirectUri;
final params = await _presentOAuthWebView(context: context, initialUrl: authorizeUrl, redirectSchemePrefix: redirectSchemePrefix);
if (params == null) return null;
final code = params['code'];
if (code == null || code.isEmpty) {
return null;
}
final tokens = await _getTokensFomYandex(code: code, clientId: clientId, clientSecret: EnvConfig.yandexClientSecret);
if (tokens == null) {
return null;
}
final response = await http.get(Uri.parse('https://login.yandex.ru/info?format=json'), headers: {'Authorization': 'OAuth ${tokens.accessToken}'});
final Map<String, dynamic> json = jsonDecode(response.body);
final yandexProfileData = YandexProfileModel.fromJson(json);
return AuthResultEntity(
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
userId: yandexProfileData.id,
firstName: yandexProfileData.firstName,
lastName: yandexProfileData.lastName,
email: yandexProfileData.defaultEmail,
phone: yandexProfileData.defaultPhone.number,
gender: GenderEnum.fromString(yandexProfileData.sex),
birthDate: yandexProfileData.birthday,
);
} catch (e) {
log(e.toString());
}
return null;
}
static Future<({String accessToken, String refreshToken})?> _getTokensFomYandex({
required String code,
required String clientId,
required String clientSecret,
}) async {
final response = await http.post(
Uri.parse('https://oauth.yandex.ru/token'),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {'grant_type': 'authorization_code', 'code': code, 'client_id': clientId, 'client_secret': clientSecret},
);
if (response.statusCode != 200) {
log('Yandex token exchange failed: ${response.body}');
return null;
}
final tokens = jsonDecode(response.body);
final accessToken = tokens['access_token'] as String;
final refreshToken = tokens['refresh_token'] as String;
return (accessToken: accessToken, refreshToken: refreshToken);
}
static Future<AuthResultEntity?> _signInWithVkIdProvider({required BuildContext context, required VkProvider provider}) async {
final clientIdStr = EnvConfig.vkClientId.trim();
if (clientIdStr.isEmpty) {
return null;
}
try {
final plugin = FlutterLoginYandex();
final response = await plugin.signIn();
final token = (response is Map) ? response['token'] as String? : null;
return (token != null && token.isNotEmpty) ? token : null;
} catch (_) {
return null;
}
}
final controller = VkIDController(clID: int.parse(clientIdStr));
static Future<String?> _signInWithVkIdProvider({required BuildContext context, required String provider}) async {
final clientIdStr = dotenv.maybeGet('VK_CLIENT_ID')?.trim() ?? Platform.environment['VK_CLIENT_ID'];
if (clientIdStr == null || clientIdStr.isEmpty) {
return null;
}
try {
dynamic controller = VkIDController(clID: int.parse(clientIdStr));
final authUri = controller.generateAuthorizeLinkWithCodeVerifier(provider: provider);
final codeVerifier = authUri.key;
final authUrl = authUri.value.toString();
final redirectScheme = 'vk$clientIdStr://';
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUri.toString(), redirectSchemePrefix: redirectScheme);
final redirectParams = await _presentOAuthWebView(context: context, initialUrl: authUrl, redirectSchemePrefix: redirectScheme);
if (redirectParams == null) return null;
final code = redirectParams['code'];
final deviceId = redirectParams['device_id'];
if (code == null || deviceId == null) return null;
final tokenResponse = await controller.exchangeAuthorizationCodeForToken(code: code, deviceId: deviceId, provider: provider);
final accessToken = _extractAccessToken(tokenResponse);
return accessToken;
} catch (_) {
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier);
} catch (e) {
return null;
}
}
static String? _extractAccessToken(dynamic tokenResponse) {
if (tokenResponse == null) return null;
if (tokenResponse is Map) {
return (tokenResponse['access_token'] as String?) ?? (tokenResponse['token'] as String?);
}
try {
final token = (tokenResponse as dynamic).accessToken as String?;
if (token != null && token.isNotEmpty) return token;
} catch (_) {}
try {
final token = (tokenResponse as dynamic).token as String?;
if (token != null && token.isNotEmpty) return token;
} catch (_) {}
return null;
}
static Future<Map<String, String>?> _presentOAuthWebView({
required BuildContext context,
required String initialUrl,
@@ -134,22 +235,47 @@ class SocialAuthFacade {
if (url.startsWith(redirectSchemePrefix)) {
final uri = Uri.parse(url);
final params = <String, String>{}..addAll(uri.queryParameters);
if (uri.fragment.isNotEmpty) {
final fragPairs = uri.fragment.split('&');
for (final pair in fragPairs) {
final eq = pair.indexOf('=');
if (eq > 0 && eq < pair.length - 1) {
final key = Uri.decodeComponent(pair.substring(0, eq));
final value = Uri.decodeComponent(pair.substring(eq + 1));
params[key] = value;
}
}
}
Navigator.of(context, rootNavigator: true).pop(params);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
onPageStarted: (url) {},
onPageFinished: (url) {},
onWebResourceError: (error) {},
),
)
..loadRequest(Uri.parse(initialUrl));
);
try {
await controller.loadRequest(Uri.parse(initialUrl));
} catch (e) {
return null;
}
if (!context.mounted) return null;
return showDialog<Map<String, String>>(
context: context,
barrierDismissible: false,
builder: (_) {
const borderRadius = BorderRadius.all(Radius.circular(16));
return Dialog(
insetPadding: const EdgeInsets.all(16),
child: SizedBox(height: 560, child: WebViewWidget(controller: controller)),
shape: const RoundedRectangleBorder(borderRadius: borderRadius),
elevation: 0,
child: ClipRRect(
borderRadius: borderRadius,
child: SizedBox(height: 560, child: WebViewWidget(controller: controller)),
),
);
},
);

View File

@@ -1,61 +1,34 @@
import 'dart:developer';
import 'package:eatvio_app/core/cubit/base_cubit.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
final BlogPostRepository blogPostRepository;
BlogPostCreateCubit({
required this.blogPostRepository,
}) : super(const BlogPostCreateState());
BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState());
Future<bool> createBlogPost({
String? title,
String? videoUrl,
String? description,
XFile? image,
}) async {
Future<bool> createBlogPost({String? title, String? videoUrl, String? description, XFile? image}) async {
try {
emit(const BlogPostCreateState.loading());
await blogPostRepository.createBlogPost(
title,
videoUrl,
description,
image,
);
await blogPostRepository.createBlogPost(title, videoUrl, description, image);
emit(const BlogPostCreateState.loaded());
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
} catch (error, stackTrace) {
handleError(error, stackTrace);
emit(const BlogPostCreateState.loaded());
return false;
}
}
Future<bool> updateBlogPost({
required int id,
String? title,
String? videoUrl,
String? description,
XFile? image,
String? oldImageUrl,
}) async {
Future<bool> updateBlogPost({required int id, String? title, String? videoUrl, String? description, XFile? image, String? oldImageUrl}) async {
try {
emit(const BlogPostCreateState.loading());
await blogPostRepository.updateBlogPost(
id,
title,
videoUrl,
description,
image,
oldImageUrl,
);
await blogPostRepository.updateBlogPost(id, title, videoUrl, description, image, oldImageUrl);
emit(const BlogPostCreateState.loaded());
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
} catch (error, stackTrace) {
handleError(error, stackTrace);
emit(const BlogPostCreateState.loaded());
return false;
}
@@ -65,8 +38,8 @@ class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
try {
await blogPostRepository.deleteBlogPost(id);
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
} catch (error, stackTrace) {
handleError(error, stackTrace);
return false;
}
}

View File

@@ -13,6 +13,7 @@ abstract class StatisticsModel with _$StatisticsModel {
@JsonKey(name: 'count_diets') int? dietsCount,
@JsonKey(name: 'count_consultants') int? consultationsCount,
@JsonKey(name: 'count_products_nutritional_values') int? qwe,
@JsonKey(name: 'referral_link') String? refLink,
}) = _StatisticsModel;
factory StatisticsModel.fromJson(Map<String, dynamic> json) => _$StatisticsModelFromJson(json);

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart';
@@ -10,6 +11,7 @@ import 'package:eatvio_app/features/consultant_rating/presentation/widgets/ratin
import 'package:eatvio_app/features/consultant_rating/presentation/widgets/statistic_widget.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultantRatingScreen extends StatelessWidget {
@@ -53,8 +55,8 @@ class ConsultantRatingPageContent extends StatelessWidget {
child: Column(
children: [
const SizedBox(height: 16),
const RatingWidget(
rating: 1058,
RatingWidget(
rating: state.absoluteRating!,
),
const SizedBox(height: 16),
Padding(
@@ -67,7 +69,10 @@ class ConsultantRatingPageContent extends StatelessWidget {
side: const WidgetStatePropertyAll(BorderSide.none),
backgroundColor: WidgetStatePropertyAll(appTheme.blueA400),
),
onPressed: () {},
onPressed: () {
Clipboard.setData(ClipboardData(text: state.statistics?.refLink ?? ''));
showInfoToast(s.link_was_copied);
},
),
),
const SizedBox(height: 16),

View File

@@ -2,17 +2,23 @@ import 'dart:developer';
import 'package:eatvio_app/features/consultant_rating/presentation/cubit/consultant_rating_state.dart';
import 'package:eatvio_app/features/consultant_rating/repositories/consultant_rating_repository.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
final ConsultantRatingRepository repository;
ConsultantRatingCubit(this.repository) : super(ConsultantRatingState());
final FeedRepository feedRepository;
final ProfileRepository profileRepository;
ConsultantRatingCubit({required this.repository, required this.feedRepository, required this.profileRepository}) : super(ConsultantRatingState());
Future<void> getConsultantStatistics({String? date}) async {
try {
emit(state.copyWith(isStatisticsLoading: true));
final statistics = await repository.getConsultantStatistics(date: date);
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null));
final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name);
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores));
} catch (e, st) {
emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
log(e.toString(), stackTrace: st);

View File

@@ -12,5 +12,6 @@ abstract class ConsultantRatingState with _$ConsultantRatingState {
@Default(null) StatisticsModel? statistics,
@Default(null) String? error,
@Default(<RatingModel>[]) List<RatingModel> ratings,
@Default(0) int? absoluteRating,
}) = _ConsultantRatingState;
}

View File

@@ -71,7 +71,7 @@ class StatisticWidget extends StatelessWidget {
children: [
Expanded(child: StatisticBouble(number: statistics?.consultationsCount ?? 0, title: s.consultations_count)),
const SizedBox(width: 8),
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.active_consultations_count)),
Expanded(child: StatisticBouble(number: statistics?.qwe ?? 0, title: s.added_products_in_glossary)),
],
),
const SizedBox(

View File

@@ -133,9 +133,9 @@ class FeedRepository {
}
}
Future<List<FeedConsultantModel>> getConsultants() async {
Future<List<FeedConsultantModel>> getConsultants([String? name]) async {
try {
return (await feedService.getConsultants()).data;
return (await feedService.getConsultants(name: name)).data;
} catch (e) {
rethrow;
}

View File

@@ -16,15 +16,10 @@ abstract class FeedService {
factory FeedService(Dio dio, {String baseUrl}) = _FeedService;
@GET('/api/blogPosts/index')
Future<FeedBlogPostSettingsModel> getBlogPostList({
@Query('page') required int page,
@Query('query_search') String? query,
});
Future<FeedBlogPostSettingsModel> getBlogPostList({@Query('page') required int page, @Query('query_search') String? query});
@GET('/api/blogPosts/show/{postId}')
Future<FeedBlogPostModel> getBlogPost({
@Path('postId') required int postId,
});
Future<FeedBlogPostModel> getBlogPost({@Path('postId') required int postId});
@GET('/api/recipes/index')
Future<RecipesPagination> getRecipes({
@@ -53,88 +48,50 @@ abstract class FeedService {
});
@GET('/api/blogPosts/{id}/comments/show')
Future<CommentModel> getBlogPostComments({
@Path() required String id,
});
Future<CommentModel> getBlogPostComments({@Path() required String id});
@POST('/api/blogPosts/{id}/comment/create')
Future<void> sendBlogPostComment({
@Path() required String id,
@Part() required String body,
@Part() int? parentCommentId,
});
Future<void> sendBlogPostComment({@Path() required String id, @Part() required String body, @Part() int? parentCommentId});
@GET('/api/diets/{id}/comments/show')
Future<CommentModel> getDietsComments({
@Path() required String id,
});
Future<CommentModel> getDietsComments({@Path() required String id});
@POST('/api/diets/{id}/comment/create')
Future<void> sendDietsComment({
@Path() required String id,
@Part() required String body,
@Part() int? parentCommentId,
});
Future<void> sendDietsComment({@Path() required String id, @Part() required String body, @Part() int? parentCommentId});
@GET('/api/plans/{slug}/comments/show')
Future<CommentModel> getPlansComments({
@Path() required String slug,
});
Future<CommentModel> getPlansComments({@Path() required String slug});
@POST('/api/plans/{slug}/comment/create')
Future<void> sendPlansComment({
@Path() required String slug,
@Part() required String body,
@Part() int? parentCommentId,
});
Future<void> sendPlansComment({@Path() required String slug, @Part() required String body, @Part() int? parentCommentId});
@GET('/api/recipes/{id}/comments/show')
Future<CommentModel> getRecipesComments({
@Path() required String id,
});
Future<CommentModel> getRecipesComments({@Path() required String id});
@POST('/api/recipes/{id}/comment/create')
Future<void> sendRecipesComment({
@Path() required String id,
@Part() required String body,
@Part() int? parentCommentId,
});
Future<void> sendRecipesComment({@Path() required String id, @Part() required String body, @Part() int? parentCommentId});
@POST('/api/blogPosts/{id}/like')
Future<void> likeBlogPost({
@Path() required int id,
});
Future<void> likeBlogPost({@Path() required int id});
@POST('/api/blogPosts/{id}/unliked')
Future<void> unlikedBlogPost({
@Path() required int id,
});
Future<void> unlikedBlogPost({@Path() required int id});
@GET('/api/consultants/index')
Future<FeedConsultantPaginationModel> getConsultants();
Future<FeedConsultantPaginationModel> getConsultants({@Query('filters[name][\$startsWith]') String? name});
@POST('/api/blogPosts/{id}/view')
Future<void> viewPost({
@Path() required int id,
});
Future<void> viewPost({@Path() required int id});
@POST('/api/recipes/{slug}/view')
Future<void> viewRecipe({
@Path() required String slug,
});
Future<void> viewRecipe({@Path() required String slug});
@POST('/api/diets/{id}/view')
Future<void> viewDiet({
@Path() required int id,
});
Future<void> viewDiet({@Path() required int id});
@POST('/api/plans/{slug}/view')
Future<void> viewPlan({
@Path() required String slug,
});
Future<void> viewPlan({@Path() required String slug});
@POST('/api/views')
Future<void> viewContent({
@Body() required ViewModel viewModel,
});
Future<void> viewContent({@Body() required ViewModel viewModel});
}

View File

@@ -58,7 +58,7 @@ class MenuScreen extends StatelessWidget {
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/finance.svg',
leading: 'assets/icons/consultant_rating_icon.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);

View File

@@ -66,6 +66,9 @@ class MessageLookup extends MessageLookupByLibrary {
"add_card": MessageLookupByLibrary.simpleMessage("Привязать карту"),
"add_cover": MessageLookupByLibrary.simpleMessage("Добавьте обложку"),
"add_measure": MessageLookupByLibrary.simpleMessage("Добавить замер"),
"added_products_in_glossary": MessageLookupByLibrary.simpleMessage(
"Кол-во продуктов добавленных в глоссарий",
),
"adding_plan": MessageLookupByLibrary.simpleMessage("Добавление плана"),
"additional_information": MessageLookupByLibrary.simpleMessage(
"Дополнительная информация",

View File

@@ -2399,6 +2399,16 @@ class S {
);
}
/// `Кол-во продуктов добавленных в глоссарий`
String get added_products_in_glossary {
return Intl.message(
'Кол-во продуктов добавленных в глоссарий',
name: 'added_products_in_glossary',
desc: '',
args: [],
);
}
/// `Кол-во активных консультаций`
String get active_consultations_count {
return Intl.message(

View File

@@ -300,6 +300,7 @@
"published_recipes_count": "Кол-во опубликованных рецептов",
"published_diets_count": "Кол-во опубликованных диет",
"consultations_count": "Кол-во проведенных консулдьтаций",
"added_products_in_glossary": "Кол-во продуктов добавленных в глоссарий",
"active_consultations_count": "Кол-во активных консультаций",
"rating_history": "Начисление рейтинга",
"something_went_wrong": "Упс! Что-то пошло не так",

View File

@@ -1,53 +1,15 @@
import 'dart:io' as io;
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart' as di;
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/views/cubit/views_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/firebase_options.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/core/env_config.dart';
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
Future<void> main() async {
WidgetsBinding widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await dotenv.load(fileName: ".env");
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await FirebaseMessaging.instance.requestPermission();
await di.init();
if (kDebugMode) {
io.HttpClient.enableTimelineLogging = true;
}
AppMetrica.activate(AppMetricaConfig(EnvConfig.appMetricaApiKey));
AppMetrica.reportEvent('Application started');
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.tracesSampleRate = 1.0;
options.debug = false;
}, appRunner: () => runApp(const MyApp()));
await AppInitializer.initialize();
await AppInitializer.runAppWithSentry(() => runApp(const MyApp()));
}
class MyApp extends StatelessWidget {
@@ -56,28 +18,15 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
BlocProvider<ViewsCubit>(create: (context) => sl<ViewsCubit>()),
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()..getNotifications()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
BlocProvider<ProfileAvatarCubit>(create: (context) => sl<ProfileAvatarCubit>()..getMyProfile()),
BlocProvider<ChatsCubit>(create: (context) => sl<ChatsCubit>()..getChatRoomsWithLoading()),
BlocProvider<InternetConnectionCubit>(create: (context) => sl<InternetConnectionCubit>(), lazy: false),
],
providers: AppConfig.globalBlocProviders,
child: Portal(
child: KeyboardDismisser(
child: MaterialApp.router(
theme: ThemeHelper().themeData(),
routerConfig: AppRouter.appRouter,
routerConfig: AppRouter.appRouter,
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale.fromSubtags(languageCode: 'ru')],
localizationsDelegates: AppConfig.localizationDelegates,
supportedLocales: AppConfig.supportedLocales,
),
),
),