diff --git a/lib/core/app_config.dart b/lib/core/app_config.dart index afe814e0..df87d259 100644 --- a/lib/core/app_config.dart +++ b/lib/core/app_config.dart @@ -33,15 +33,16 @@ class AppConfig { } /// Список глобальных BlocProvider'ов + /// Используются lazy: true для отложенной инициализации, чтобы не блокировать старт приложения List get globalBlocProviders => [ - BlocProvider(create: (context) => sl()), - BlocProvider(create: (context) => sl()..getNotifications()), - BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), - BlocProvider(create: (context) => sl()..getMyProfile()), - BlocProvider(create: (context) => sl()..getChatRoomsWithLoading()), - BlocProvider(create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), BlocProvider(lazy: false, create: (context) => sl()), - BlocProvider(create: (context) => sl()), + BlocProvider(lazy: true, create: (context) => sl()), ]; /// Делегаты локализации diff --git a/lib/core/app_initializer.dart b/lib/core/app_initializer.dart index ea0b8f5d..193aa7e3 100644 --- a/lib/core/app_initializer.dart +++ b/lib/core/app_initializer.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io' as io; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/env_config.dart'; @@ -37,9 +38,20 @@ class AppInitializer { /// Инициализация Firebase static Future _initializeFirebase() async { - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).timeout( + const Duration(seconds: 10), + onTimeout: () { + throw TimeoutException('Firebase initialization timeout'); + }, + ); if (sl().buildType == AppBuildType.prod) { - await FirebaseMessaging.instance.requestPermission(); + try { + await FirebaseMessaging.instance.requestPermission().timeout( + const Duration(seconds: 5), + ); + } catch (e) { + // Разрешение не критично, продолжаем работу + } } } diff --git a/lib/core/di/external_registrations.dart b/lib/core/di/external_registrations.dart index dcff4441..aecd2707 100644 --- a/lib/core/di/external_registrations.dart +++ b/lib/core/di/external_registrations.dart @@ -20,13 +20,19 @@ class ExternalRegistrations { static void _registerDio() { sl.registerLazySingleton( () => - Dio(BaseOptions(baseUrl: sl().baseUrl, headers: {'accept': 'application/json', 'content-type': 'application/json'})) + Dio(BaseOptions( + baseUrl: sl().baseUrl, + headers: {'accept': 'application/json', 'content-type': 'application/json'}, + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + sendTimeout: const Duration(seconds: 10), + )) ..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]), ); } /// Регистрация Prefs static void _registerPrefs() { - sl.registerSingletonAsync(()async => Prefs(await SharedPreferences.getInstance())); + sl.registerSingletonAsync(() async => Prefs(await SharedPreferences.getInstance())); } } diff --git a/lib/core/firebase_api.dart b/lib/core/firebase_api.dart index 089d7846..887b7137 100644 --- a/lib/core/firebase_api.dart +++ b/lib/core/firebase_api.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:developer'; import 'dart:io'; import 'package:eatvio_app/core/app_config.dart'; @@ -19,12 +20,24 @@ class FirebaseApi { String? token; if (await _isHuaweiDevice()) { - token = await _getHuaweiPushTokenNative(); + token = await _getHuaweiPushTokenNative().timeout( + const Duration(seconds: 5), + onTimeout: () => null, + ); } else { if ((sl().buildType == AppBuildType.prod)) { - await _firebaseMessaging.requestPermission(); + try { + await _firebaseMessaging.requestPermission().timeout( + const Duration(seconds: 5), + ); + } catch (e) { + // Разрешение не критично, продолжаем + } } - token = await _firebaseMessaging.getToken(); + token = await _firebaseMessaging.getToken().timeout( + const Duration(seconds: 5), + onTimeout: () => null, + ); } return token; } @@ -39,9 +52,15 @@ class FirebaseApi { 'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id, 'token': await getToken(), }, + ).timeout( + const Duration(seconds: 10), + onTimeout: () { + throw TimeoutException('registerAnonymousPushToken timeout'); + }, ); } catch (e, st) { log(e.toString(), stackTrace: st); + // Не пробрасываем ошибку дальше, так как это не критичная операция } } @@ -57,9 +76,15 @@ class FirebaseApi { 'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id, 'token': await getToken(), }, + ).timeout( + const Duration(seconds: 10), + onTimeout: () { + throw TimeoutException('registerPushToken timeout'); + }, ); } catch (e, st) { log(e.toString(), stackTrace: st); + // Не пробрасываем ошибку дальше, так как это не критичная операция } } } diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index d3ed9b95..2f457269 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -4,6 +4,7 @@ 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/prefs.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:get_it/get_it.dart'; @@ -18,6 +19,9 @@ Future init({required AppBuildType buildType}) async { // Регистрация внешних зависимостей (Dio, etc.) ExternalRegistrations.registerAll(); + // Ожидание инициализации асинхронных зависимостей + await sl.getAsync(); + // Регистрация сервисов ServiceRegistrations.registerAll(); diff --git a/lib/core/routing/auth_routes.dart b/lib/core/routing/auth_routes.dart index 2ac62618..772cd2eb 100644 --- a/lib/core/routing/auth_routes.dart +++ b/lib/core/routing/auth_routes.dart @@ -1,5 +1,3 @@ -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/features/auth/entities/auth_result_entity.dart'; import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart'; @@ -21,10 +19,8 @@ class AuthRoutes { path: RouteConstants.authScreen, name: 'authScreen', builder: (context, state) => const AuthScreen(), - redirect: (context, state) { - sl().registerAnonymousPushToken(); - return null; - }, + // registerAnonymousPushToken вызывается в AuthCubit.initializeUser() + // и не должен блокировать навигацию routes: [ GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()), GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()), diff --git a/lib/core/routing/main_navigation_routes.dart b/lib/core/routing/main_navigation_routes.dart index 818f625c..d6a4978e 100644 --- a/lib/core/routing/main_navigation_routes.dart +++ b/lib/core/routing/main_navigation_routes.dart @@ -23,6 +23,15 @@ class MainNavigationRoutes { return [ ShellRoute( builder: (context, state, child) { + // Инициализация ProfileAvatarCubit при первом использовании ShellRoute + final profileAvatarCubit = context.read(); + if (profileAvatarCubit.state is ProfileAvatarStateLoading) { + // Вызываем getMyProfile() только если состояние loading (первый запуск) + WidgetsBinding.instance.addPostFrameCallback((_) { + profileAvatarCubit.getMyProfile(); + }); + } + return BlocBuilder( builder: (BuildContext context, ProfileAvatarState state) { if (state is ProfileAvatarStateLoaded) { diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index 28d85d36..9b29ad98 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:dio/dio.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/locator_service.dart'; @@ -56,7 +57,8 @@ class OtherRoutes { builder: (context, state) => const WelcomeScreen(), redirect: (context, state) async { try { - final prefs = sl(); + // Prefs уже инициализирован в locator_service, но на всякий случай проверяем + final prefs = await sl.getAsync(); final welcomeScreenWasShowed = prefs.getBool(PrefsKeys.welcomeScreenWasShowed) ?? false; if (!welcomeScreenWasShowed) { return null; @@ -74,7 +76,27 @@ class OtherRoutes { builder: (context, state) => const UpdateScreen(), redirect: (context, state) async { try { - final versions = (await sl().get('${sl().baseUrl}/api/app_versions')).data; + // Проверка версии с таймаутом, чтобы не блокировать запуск приложения + final versions = await sl() + .get('${sl().baseUrl}/api/app_versions') + .timeout( + const Duration(seconds: 5), + onTimeout: () { + // При таймауте пропускаем проверку и идем дальше + throw TimeoutException('Version check timeout'); + }, + ) + .then((response) => response.data) + .catchError((e) { + // При любой ошибке пропускаем проверку версии + return null; + }); + + if (versions == null) { + // Если проверка не удалась, пропускаем экран обновления + return RouteConstants.welcomeScreen; + } + PackageInfo packageInfo = await PackageInfo.fromPlatform(); String version = packageInfo.version; Version currentVersion = Version.parse(version); @@ -85,6 +107,7 @@ class OtherRoutes { return RouteConstants.welcomeScreen; } } catch (e) { + // При любой ошибке пропускаем экран обновления return RouteConstants.welcomeScreen; } }, diff --git a/lib/core/token_storage.dart b/lib/core/token_storage.dart index f67e919c..871140c7 100644 --- a/lib/core/token_storage.dart +++ b/lib/core/token_storage.dart @@ -1,9 +1,16 @@ import 'package:shared_preferences/shared_preferences.dart'; abstract class TokenStorage { + static SharedPreferences? _cachedPrefs; + + static Future _getPrefs() async { + _cachedPrefs ??= await SharedPreferences.getInstance(); + return _cachedPrefs!; + } + static Future haveTokenInStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); try { + final sharedPreferences = await _getPrefs(); if (sharedPreferences.getString('token') == null) { return false; } else { @@ -15,25 +22,25 @@ abstract class TokenStorage { } static Future getTokenFromStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await _getPrefs(); return sharedPreferences.getString('token'); } static Future saveTokenInStorage(String token) async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await _getPrefs(); await sharedPreferences.setString('token', token); } static Future removeTokenInStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await _getPrefs(); await sharedPreferences.remove('token'); } } abstract class SocketIdStorage { static Future haveSocketIdInStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); try { + final sharedPreferences = await TokenStorage._getPrefs(); if (sharedPreferences.getString('socketId') == null) { return false; } else { @@ -45,17 +52,17 @@ abstract class SocketIdStorage { } static Future getSocketIdFromStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await TokenStorage._getPrefs(); return sharedPreferences.getString('socketId'); } static Future saveSocketIdInStorage(String socketId) async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await TokenStorage._getPrefs(); await sharedPreferences.setString('socketId', socketId); } static Future removeSocketIdInStorage() async { - final sharedPreferences = await SharedPreferences.getInstance(); + final sharedPreferences = await TokenStorage._getPrefs(); await sharedPreferences.remove('socketId'); } } diff --git a/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart b/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart index 4e3c7db9..c80daada 100644 --- a/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart +++ b/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart @@ -38,18 +38,25 @@ class AuthCubit extends Cubit { } Future initializeUser() async { - await registerAnonymousPushToken(); - if (await TokenStorage.haveTokenInStorage()) { - final profile = await profileRepository.getMyProfile(); - if (profile.emailVerifiedAt != null) { - await registerPushToken(); - AppRouter.navigatorKey.currentContext?.go(RouteConstants.feedScreen); - return; - } - AppRouter.navigatorKey.currentContext?.go(RouteConstants.verifyEmailScreen); - } else { + try { await registerAnonymousPushToken(); - FlutterNativeSplash.remove(); + if (await TokenStorage.haveTokenInStorage()) { + final profile = await profileRepository.getMyProfile(); + if (profile.emailVerifiedAt != null) { + await registerPushToken(); + FlutterNativeSplash.remove(); + AppRouter.navigatorKey.currentContext?.go(RouteConstants.feedScreen); + return; + } + FlutterNativeSplash.remove(); + AppRouter.navigatorKey.currentContext?.go(RouteConstants.verifyEmailScreen); + } else { + FlutterNativeSplash.remove(); + } + } catch (e) { + log('Error in initializeUser: $e'); + FlutterNativeSplash.remove(); // Убираем сплэш даже при ошибке + rethrow; } } diff --git a/lib/features/chat/presentation/pages/chats_screen.dart b/lib/features/chat/presentation/pages/chats_screen.dart index befaa00f..539e971c 100644 --- a/lib/features/chat/presentation/pages/chats_screen.dart +++ b/lib/features/chat/presentation/pages/chats_screen.dart @@ -20,6 +20,19 @@ class ChatsScreen extends StatefulWidget { class _ChatsScreenState extends State { final searchController = TextEditingController(); + @override + void initState() { + super.initState(); + // Инициализация ChatsCubit при первом открытии экрана + WidgetsBinding.instance.addPostFrameCallback((_) { + final cubit = context.read(); + final state = cubit.state; + if (state.state == BaseState.initial) { + cubit.getChatRoomsWithLoading(); + } + }); + } + @override Widget build(BuildContext context) { return BlocBuilder( diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 47447e49..0c9dca9d 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -11,6 +11,7 @@ import 'package:eatvio_app/features/create_plan/presentation/create_plan_screen. 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/feed_cubit/feed_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart'; @@ -74,6 +75,14 @@ class _FeedScreenContentState extends State { chipsLabelList = hidePlans ? ['Все', 'Рецепты', 'Диеты', 'Консультанты'] : ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты']; consultantsScrollController.addListener(_onScrollConsultants); _checkRegistrationDate(context); + // Инициализация глобальных кубитов при первом открытии экрана + WidgetsBinding.instance.addPostFrameCallback((_) { + final feedCubit = context.read(); + final state = feedCubit.state; + if (state is! FeedStateLoaded && state is! FeedStateLoading) { + feedCubit.loadFeedBlogPostList(); + } + }); super.initState(); } diff --git a/lib/features/notifications/presentation/notifications_screen.dart b/lib/features/notifications/presentation/notifications_screen.dart index 58e3c65f..afb8e122 100644 --- a/lib/features/notifications/presentation/notifications_screen.dart +++ b/lib/features/notifications/presentation/notifications_screen.dart @@ -24,6 +24,14 @@ class _NotificationsScreenState extends State { void initState() { super.initState(); _scrollController.addListener(_onScroll); + // Инициализация NotificationsCubit при первом открытии экрана + WidgetsBinding.instance.addPostFrameCallback((_) { + final cubit = context.read(); + final state = cubit.state; + if (state is! NotificationsStateLoaded && state is! NotificationsStateError) { + cubit.getNotifications(); + } + }); } @override