diff --git a/android/gradle.properties b/android/gradle.properties index f018a618..475a6280 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/ios/Flutter/ephemeral/flutter_native_integration.env b/ios/Flutter/ephemeral/flutter_native_integration.env new file mode 100644 index 00000000..5da905e4 --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_native_integration.env @@ -0,0 +1,12 @@ +FLUTTER_ROOT=C:\Users\Alexander\fvm\versions\3.44.0 +FLUTTER_APPLICATION_PATH=C:\Users\Alexander\eatvio_app +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=C:\Users\Alexander\eatvio_app\ios\Flutter\ephemeral\Packages\.packages\FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib\main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.3.23 +FLUTTER_BUILD_NUMBER=105 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/lib/core/internet_connection_cubit/internet_connection_cubit.dart b/lib/core/internet_connection_cubit/internet_connection_cubit.dart index 4f87468e..c84d0161 100644 --- a/lib/core/internet_connection_cubit/internet_connection_cubit.dart +++ b/lib/core/internet_connection_cubit/internet_connection_cubit.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:dio/dio.dart'; import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/internet_connection_cubit/internet_connection_state.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; @@ -9,7 +10,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:eatvio_app/core/error_reporting/report_from.dart'; -import 'package:http/http.dart' as http; class InternetConnectionCubit extends Cubit { InternetConnectionCubit() : super(InternetConnectionState()) { @@ -29,6 +29,16 @@ class InternetConnectionCubit extends Cubit { late StreamSubscription> _connectivitySubscription; late final AppLifecycleListener _listener; + /// Отдельный легковесный Dio без авторизационных интерцепторов — + /// используется только для проверки доступности сети. + final Dio _checkDio = Dio(BaseOptions( + connectTimeout: const Duration(seconds: 5), + receiveTimeout: const Duration(seconds: 5), + followRedirects: true, + // Любой HTTP-ответ = интернет доступен + validateStatus: (status) => true, + )); + Future checkConnection() async { late List result; @@ -50,40 +60,61 @@ class InternetConnectionCubit extends Cubit { // Показываем toast только при переходе online → offline, // а не при каждом срабатывании проверки if (wasOnline && !hasInternet) showInternetConnectionToast(); - emit(state.copyWith(isInternetAvailable: hasInternet)); + if (!isClosed) { + emit(state.copyWith(isInternetAvailable: hasInternet)); + } } catch (e, st) { await reportFrom(e, st, this); } } Future _checkInternetAccess(List result) async { - // connectivity_plus может ошибочно возвращать 'none' на эмуляторах - // и некоторых устройствах, поэтому ВСЕГДА проверяем через HTTP. - // Если connectivity говорит 'none' — используем короткий таймаут. - final isLikelyOffline = result.contains(ConnectivityResult.none); - final timeout = Duration(seconds: isLikelyOffline ? 2 : 3); + log('[InternetCheck] connectivity result: $result'); - final uris = [ - Uri.parse(EnvConfig.googleCheckUrl), - Uri.parse(EnvConfig.yandexCheckUrl), - Uri.parse(EnvConfig.baseUrl), + final rawUrls = [ + EnvConfig.googleCheckUrl, + EnvConfig.yandexCheckUrl, + EnvConfig.baseUrl, ]; - final futures = uris.map((uri) => - http.get(uri) - .timeout(timeout) - .then((r) => r.statusCode >= 100 && r.statusCode < 600) - .catchError((_) => false) + final urls = rawUrls + .map((u) => u.trim()) + .where((u) => u.isNotEmpty && Uri.tryParse(u)?.hasScheme == true) + .toList(); + + if (urls.isEmpty) { + log('[InternetCheck] No valid check URLs configured, assuming online'); + return true; + } + + final futures = urls.map((url) => + _checkDio.get(url) + .then((_) { + log('[InternetCheck] $url → OK'); + return true; + }) + .catchError((Object e) { + // DioException с ответом от сервера = интернет есть + if (e is DioException && e.response != null) { + log('[InternetCheck] $url → ${e.response!.statusCode} (has response, internet OK)'); + return true; + } + log('[InternetCheck] $url → FAILED: $e'); + return false; + }) ); final results = await Future.wait(futures); - return results.any((ok) => ok); + final hasInternet = results.any((ok) => ok); + log('[InternetCheck] final result: hasInternet=$hasInternet'); + return hasInternet; } @override Future close() { _connectivitySubscription.cancel(); _listener.dispose(); + _checkDio.close(); return super.close(); } } diff --git a/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart b/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart index 6894c162..3bce7e99 100644 --- a/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart +++ b/lib/features/create_recipe/presentation/widgets/ingredients_widget.dart @@ -47,6 +47,7 @@ class _IngredientsWidgetState extends State { ReorderableListView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + // ignore: deprecated_member_use onReorder: (int oldIndex, int newIndex) { setState(() { if (oldIndex < newIndex) { diff --git a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart index 42266f27..b76df145 100644 --- a/lib/features/diary/presentation/widgets/photo_gallery_widget.dart +++ b/lib/features/diary/presentation/widgets/photo_gallery_widget.dart @@ -116,6 +116,9 @@ class _PhotoGalleryWidgetState extends State { builder: (context, state) { if (state is DiaryStateLoaded) { int mealIndex = state.selectedMeal - 1; + if (mealIndex < 0 || mealIndex >= state.diary.meals.length) { + return const SizedBox.shrink(); + } final photos = state.diary.meals[mealIndex].photos; // BUG-5 fix: сбросить индекс, если он вышел за пределы // (например, после переключения на meal с меньшим числом фото) diff --git a/lib/features/diary/presentation/widgets/products_list_widget.dart b/lib/features/diary/presentation/widgets/products_list_widget.dart index d3d7ac10..e11ab663 100644 --- a/lib/features/diary/presentation/widgets/products_list_widget.dart +++ b/lib/features/diary/presentation/widgets/products_list_widget.dart @@ -20,6 +20,9 @@ class _ProductsListWidgetState extends State { builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { int mealIndex = state.selectedMeal - 1; + if (mealIndex < 0 || mealIndex >= state.diary.meals.length) { + return const SizedBox.shrink(); + } final portions = state.diary.meals[mealIndex].portions; return Column( mainAxisSize: MainAxisSize.min, @@ -35,6 +38,9 @@ class _ProductsListWidgetState extends State { } if (state is DiaryStateLoadedTomorrowWithDiet) { int mealIndex = state.selectedMeal - 1; + if (mealIndex < 0 || mealIndex >= state.diary.meals.length) { + return const SizedBox.shrink(); + } final portions = state.diary.meals[mealIndex].portions; return Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart index afe1bc2a..3f6c1728 100644 --- a/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart +++ b/lib/features/diary/presentation/widgets/time_of_meal_picker_widget.dart @@ -87,6 +87,11 @@ class _TimeOfMealPickerWidgetState extends State { return BlocBuilder( builder: (context, state) { if (state is DiaryStateLoaded) { + final meals = state.diary.meals; + final mealIndex = state.selectedMeal - 1; + if (mealIndex < 0 || mealIndex >= meals.length) { + return const SizedBox.shrink(); + } return Column( children: [ const CustomDivider(), diff --git a/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart index 81c5d7f7..37d6a55c 100644 --- a/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart @@ -12,7 +12,9 @@ class FeedProfileCubit extends Cubit { Future getProfile() async { try { final profile = await profileRepository.getMyProfile(); - emit(state.copyWith(profile: profile)); + if (!isClosed) { + emit(state.copyWith(profile: profile)); + } } catch (e, stackTrace) { await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace);