From 42b443c3babbeaf642f88e26598c43dae076ed2d Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 22 Dec 2025 16:48:13 +0300 Subject: [PATCH 1/2] fix: ui overflow nav-bar, name files in chat, qr-code scanner in dev-tools, message field size, keyboard type --- lib/core/di/repository_registrations.dart | 2 + lib/core/di/service_registrations.dart | 2 + .../oauth_verification_confirm_response.dart | 2 + .../models/oauth_verification_request.dart | 2 + .../oauth_verification_request_response.dart | 2 + .../pages/register_via_social_screen.dart | 16 +- .../barcode/models/barcode_product_model.dart | 29 +++ .../repositories/barcode_repository.dart | 108 +++++++++ .../barcode/services/barcode_service.dart | 17 ++ .../presentation/pages/blog_post_screen.dart | 8 +- .../chat/models/chat_message_model.dart | 1 + .../chat/presentation/pages/chat_screen.dart | 54 +++-- .../presentation/pages/empty_chat_screen.dart | 52 ++--- .../widgets/chat_bubble_widget.dart | 54 ++++- .../presentation/dev_tools_screen.dart | 207 ++++++++++++++++++ .../pages/barcode_scanner_screen.dart | 102 ++++----- .../widgets/add_product_modal.dart | 24 +- .../presentation/pages/about_diet_screen.dart | 202 +++++------------ .../presentation/pages/about_plan_screen.dart | 124 +++-------- .../pages/about_recipe_screen.dart | 22 +- .../pages/general_info_screen.dart | 10 +- .../presentation/pages/other_info_screen.dart | 10 +- .../pages/physical_activity_screen.dart | 10 +- .../pages/physical_form_screen.dart | 10 +- .../presentation/pages/pressure_screen.dart | 10 +- pubspec.yaml | 2 +- test/core/constants/app_constants_test.dart | 2 + test/core/cubit/state_utils_test.dart | 2 + .../core/data/selection_popup_model_test.dart | 2 + test/core/enums/prefs_keys_test.dart | 2 + .../internet_connection_state_test.dart | 2 + test/core/prefs_keys_test.dart | 2 + .../routing/main_navigation_routes_test.dart | 2 + test/core/routing/other_routes_test.dart | 2 + test/core/routing/route_constants_test.dart | 2 + test/core/routing/settings_routes_test.dart | 2 + test/core/views/cubit/views_state_test.dart | 2 + test/core/widgets/add_item_button_test.dart | 2 + .../widgets/animated_option_button_test.dart | 2 + test/core/widgets/base_button_test.dart | 2 + .../widgets/bottom_sheet_with_blur_test.dart | 2 + test/core/widgets/cpfc_container_test.dart | 2 + test/core/widgets/custom_search_bar_test.dart | 2 + test/core/widgets/item_widget_test.dart | 2 + test/core/widgets/loader_test.dart | 2 + .../widgets/main_background_bouble_test.dart | 2 + .../widgets/marketing_bottom_sheet_test.dart | 2 + test/core/widgets/option_widget_test.dart | 2 + .../widgets/error_widget_test.dart | 2 + .../chosen_diet_widget_content_test.dart | 2 + .../widgets/comment_container_test.dart | 2 + .../finish_day_button_widget_test.dart | 2 + .../widgets/meal_summary_widget_test.dart | 2 + .../widgets/product_loader_widget_test.dart | 2 + .../widgets/info_bouble_test.dart | 2 + .../widgets/choice_chip_widget_test.dart | 2 + .../widgets/filter_badge_test.dart | 2 + .../widgets/status_widget_test.dart | 2 + .../glossary_repository_test.dart | 2 + .../widgets/notification_widget_test.dart | 2 + .../service/notifications_service_test.dart | 2 + 61 files changed, 759 insertions(+), 393 deletions(-) create mode 100644 lib/features/barcode/models/barcode_product_model.dart create mode 100644 lib/features/barcode/repositories/barcode_repository.dart create mode 100644 lib/features/barcode/services/barcode_service.dart diff --git a/lib/core/di/repository_registrations.dart b/lib/core/di/repository_registrations.dart index fbbb4498..341691a5 100644 --- a/lib/core/di/repository_registrations.dart +++ b/lib/core/di/repository_registrations.dart @@ -17,6 +17,7 @@ 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/barcode/repositories/barcode_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'; @@ -55,6 +56,7 @@ class RepositoryRegistrations { sl.registerLazySingleton(() => ConsultantRepository(consultantService: sl())); sl.registerLazySingleton(() => DiaryRepository(diaryService: sl())); sl.registerLazySingleton(() => GlossaryRepository(glossaryService: sl())); + sl.registerLazySingleton(() => BarcodeRepository(barcodeService: sl(), dio: sl())); sl.registerLazySingleton(() => DietRepository(dietsService: sl())); sl.registerLazySingleton(() => PlansRepository(plansService: sl())); sl.registerLazySingleton(() => PlanRepository(planService: sl())); diff --git a/lib/core/di/service_registrations.dart b/lib/core/di/service_registrations.dart index f70e28ce..62439f4a 100644 --- a/lib/core/di/service_registrations.dart +++ b/lib/core/di/service_registrations.dart @@ -19,6 +19,7 @@ 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/barcode/services/barcode_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'; @@ -56,6 +57,7 @@ class ServiceRegistrations { sl.registerLazySingleton(() => ConsultantService(sl())); sl.registerLazySingleton(() => DiaryService(sl())); sl.registerLazySingleton(() => GlossaryService(sl())); + sl.registerLazySingleton(() => BarcodeService(sl())); sl.registerLazySingleton(() => DietService(sl())); sl.registerLazySingleton(() => PlansService(sl())); sl.registerLazySingleton(() => PlanService(sl())); diff --git a/lib/features/auth/models/oauth_verification_confirm_response.dart b/lib/features/auth/models/oauth_verification_confirm_response.dart index 6e7eda52..f079f11c 100644 --- a/lib/features/auth/models/oauth_verification_confirm_response.dart +++ b/lib/features/auth/models/oauth_verification_confirm_response.dart @@ -25,3 +25,5 @@ abstract class OAuthVerificationConfirmResponse with _$OAuthVerificationConfirmR + + diff --git a/lib/features/auth/models/oauth_verification_request.dart b/lib/features/auth/models/oauth_verification_request.dart index 018809aa..041791d6 100644 --- a/lib/features/auth/models/oauth_verification_request.dart +++ b/lib/features/auth/models/oauth_verification_request.dart @@ -19,3 +19,5 @@ abstract class OAuthVerificationRequest with _$OAuthVerificationRequest { + + diff --git a/lib/features/auth/models/oauth_verification_request_response.dart b/lib/features/auth/models/oauth_verification_request_response.dart index a9f814de..262d400d 100644 --- a/lib/features/auth/models/oauth_verification_request_response.dart +++ b/lib/features/auth/models/oauth_verification_request_response.dart @@ -19,3 +19,5 @@ abstract class OAuthVerificationRequestResponse with _$OAuthVerificationRequestR + + diff --git a/lib/features/auth/presentation/pages/register_via_social_screen.dart b/lib/features/auth/presentation/pages/register_via_social_screen.dart index 2663dac2..a2f5e65b 100644 --- a/lib/features/auth/presentation/pages/register_via_social_screen.dart +++ b/lib/features/auth/presentation/pages/register_via_social_screen.dart @@ -338,7 +338,13 @@ class _RegisterViaSocialScreenState extends State { children: [ const Text('Вес'), const SizedBox(height: 4), - CustomTextFormField(controller: weightController, hintText: 'Введите вес', onChanged: (newValue) {}, hasError: weightError), + CustomTextFormField( + controller: weightController, + hintText: 'Введите вес', + keyboardType: TextInputType.number, + onChanged: (newValue) {}, + hasError: weightError, + ), if (weightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Вес должен быть от 30 до 300 кг')], ], ), @@ -351,7 +357,13 @@ class _RegisterViaSocialScreenState extends State { children: [ const Text('Рост'), const SizedBox(height: 4), - CustomTextFormField(controller: heightController, hintText: 'Введите рост', onChanged: (newValue) {}, hasError: heightError), + CustomTextFormField( + controller: heightController, + hintText: 'Введите рост', + keyboardType: TextInputType.number, + onChanged: (newValue) {}, + hasError: heightError, + ), if (heightError) ...[const SizedBox(height: 4), const ValidationErrorWidget(error: 'Рост должен быть от 100 до 250 см')], ], ), diff --git a/lib/features/barcode/models/barcode_product_model.dart b/lib/features/barcode/models/barcode_product_model.dart new file mode 100644 index 00000000..99fd8cd7 --- /dev/null +++ b/lib/features/barcode/models/barcode_product_model.dart @@ -0,0 +1,29 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'barcode_product_model.freezed.dart'; +part 'barcode_product_model.g.dart'; + +@freezed +abstract class BarcodeProductModel with _$BarcodeProductModel { + const factory BarcodeProductModel({ + // ignore: non_constant_identifier_names + required String GTIN, + @JsonKey(name: 'Товарный знак (ru)') String? brandName, + @JsonKey(name: 'Описание (ru)') String? description, + @JsonKey(name: 'Количество/вес') String? quantity, + @JsonKey(name: 'Рынок сбыта') String? market, + @JsonKey(name: 'Калорийность') double? calories, + @JsonKey(name: 'Белки') double? proteins, + @JsonKey(name: 'Жиры') double? fats, + @JsonKey(name: 'Углеводы') double? carbohydrates, + }) = _BarcodeProductModel; + + factory BarcodeProductModel.fromJson(Map json) => _$BarcodeProductModelFromJson(json); +} + +@freezed +abstract class BarcodeTaskStatusModel with _$BarcodeTaskStatusModel { + const factory BarcodeTaskStatusModel({String? status, @JsonKey(name: 'task_id') String? taskId, BarcodeProductModel? data}) = _BarcodeTaskStatusModel; + + factory BarcodeTaskStatusModel.fromJson(Map json) => _$BarcodeTaskStatusModelFromJson(json); +} diff --git a/lib/features/barcode/repositories/barcode_repository.dart b/lib/features/barcode/repositories/barcode_repository.dart new file mode 100644 index 00000000..b83ace5c --- /dev/null +++ b/lib/features/barcode/repositories/barcode_repository.dart @@ -0,0 +1,108 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart'; +import 'package:eatvio_app/features/barcode/services/barcode_service.dart'; + +class BarcodeRepository { + final BarcodeService barcodeService; + final Dio dio; + + BarcodeRepository({ + required this.barcodeService, + required this.dio, + }); + + /// Получить информацию о продукте по штрихкоду + /// Возвращает либо данные продукта, либо статус задачи обработки + Future getProductByBarcode(String barcode) async { + try { + final response = await dio.get('/api/gsone/barcode/$barcode'); + + if (response.statusCode == 200) { + // Данные найдены в БД + final product = BarcodeProductModel.fromJson(response.data as Map); + return BarcodeResult.success(product); + } else if (response.statusCode == 202) { + // Запущена задача обработки + final taskStatus = BarcodeTaskStatusModel.fromJson(response.data as Map); + return BarcodeResult.processing(taskStatus.taskId ?? ''); + } + + throw Exception('Unexpected status code: ${response.statusCode}'); + } on DioException catch (e) { + if (e.response?.statusCode == 202) { + // Если получен статус 202, значит запущена задача обработки + final taskStatus = BarcodeTaskStatusModel.fromJson(e.response!.data as Map); + return BarcodeResult.processing(taskStatus.taskId ?? ''); + } + rethrow; + } catch (e) { + rethrow; + } + } + + /// Получить статус задачи обработки + Future getTaskStatus(String taskId) async { + try { + final statusModel = await barcodeService.getTaskStatus(taskId: taskId); + + if (statusModel.status == 'completed' && statusModel.data != null) { + return BarcodeTaskStatus.completed(statusModel.data!); + } else if (statusModel.status == 'failed') { + return BarcodeTaskStatus.failed(); + } else { + return BarcodeTaskStatus.processing(); + } + } catch (e) { + rethrow; + } + } +} + +/// Результат запроса информации о продукте по штрихкоду +class BarcodeResult { + final BarcodeResultType type; + final BarcodeProductModel? product; + final String? taskId; + + BarcodeResult._({required this.type, this.product, this.taskId}); + + factory BarcodeResult.success(BarcodeProductModel product) { + return BarcodeResult._(type: BarcodeResultType.success, product: product); + } + + factory BarcodeResult.processing(String taskId) { + return BarcodeResult._(type: BarcodeResultType.processing, taskId: taskId); + } +} + +enum BarcodeResultType { + success, + processing, +} + +/// Статус задачи обработки +class BarcodeTaskStatus { + final BarcodeTaskStatusType type; + final BarcodeProductModel? product; + + BarcodeTaskStatus._({required this.type, this.product}); + + factory BarcodeTaskStatus.completed(BarcodeProductModel product) { + return BarcodeTaskStatus._(type: BarcodeTaskStatusType.completed, product: product); + } + + factory BarcodeTaskStatus.processing() { + return BarcodeTaskStatus._(type: BarcodeTaskStatusType.processing); + } + + factory BarcodeTaskStatus.failed() { + return BarcodeTaskStatus._(type: BarcodeTaskStatusType.failed); + } +} + +enum BarcodeTaskStatusType { + completed, + processing, + failed, +} + diff --git a/lib/features/barcode/services/barcode_service.dart b/lib/features/barcode/services/barcode_service.dart new file mode 100644 index 00000000..56b41eae --- /dev/null +++ b/lib/features/barcode/services/barcode_service.dart @@ -0,0 +1,17 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'barcode_service.g.dart'; + +@RestApi() +abstract class BarcodeService { + factory BarcodeService(Dio dio, {String baseUrl}) = _BarcodeService; + + @GET('/api/gsone/barcode/{barcode}') + Future getProductByBarcode({@Path('barcode') required String barcode}); + + @GET('/api/gsone/barcode/status/{task_id}') + Future getTaskStatus({@Path('task_id') required String taskId}); +} + diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index 08312aeb..139f9d10 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; @@ -79,7 +78,10 @@ class _BlogPostScreenState extends State { return Scaffold( resizeToAvoidBottomInset: true, bottomSheet: Container( - padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), + padding: EdgeInsets.only( + top: 12, + bottom: 12 + MediaQuery.of(context).padding.bottom + 56, // 56 - высота bottomNavigationBar + ), decoration: BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), @@ -195,7 +197,7 @@ class _BlogPostScreenState extends State { ], ), body: SingleChildScrollView( - padding: const EdgeInsets.only(bottom: 80), + padding: EdgeInsets.only(bottom: 160 + MediaQuery.of(context).padding.bottom), // поле комментария (52-200) + padding (12+12+56) + safe area physics: const BouncingScrollPhysics(), child: Column( children: [ diff --git a/lib/features/chat/models/chat_message_model.dart b/lib/features/chat/models/chat_message_model.dart index 45b6c6ac..c50a68f8 100644 --- a/lib/features/chat/models/chat_message_model.dart +++ b/lib/features/chat/models/chat_message_model.dart @@ -27,6 +27,7 @@ abstract class ChatMessageModel with _$ChatMessageModel { required int id, @JsonKey(name: 'data', fromJson: _parseData) required String data, @JsonKey(name: 'media_data') required List? mediaData, + @JsonKey(name: 'urls_media_info') List>? urlsMediaInfo, @JsonKey(name: 'author_id', fromJson: _parseAuthorId) required int authorId, @JsonKey(name: 'updated_at', fromJson: _parseUpdatedAt) required DateTime updatedAt, @JsonKey(name: 'read_by_to_user', defaultValue: false) required bool isRead, diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 8e185096..8f5e49d2 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -275,6 +275,7 @@ class _ChatScreenState extends State { userSlug: state.chatRoom.users.receiver.slug, messageType: message.type, isConsultant: amIConsultant, + urlsMediaInfo: message.urlsMediaInfo, ), ), ], @@ -330,6 +331,7 @@ class _ChatScreenState extends State { userSlug: state.chatRoom.users.receiver.slug, messageType: message.type, isConsultant: amIConsultant, + urlsMediaInfo: message.urlsMediaInfo, ), ), ], @@ -460,34 +462,30 @@ class _ChatScreenState extends State { ), const SizedBox(width: 8), Expanded( - child: SizedBox( - height: 40, - child: TextFormField( - expands: true, - textCapitalization: TextCapitalization.sentences, - controller: textEditingController, - maxLines: null, - minLines: null, - keyboardType: TextInputType.multiline, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1), - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - hintText: 'Сообщение', - hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), + child: TextFormField( + textCapitalization: TextCapitalization.sentences, + controller: textEditingController, + maxLines: 3, + minLines: 1, + keyboardType: TextInputType.multiline, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1), + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + hintText: 'Сообщение', + hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), ), ), ), diff --git a/lib/features/chat/presentation/pages/empty_chat_screen.dart b/lib/features/chat/presentation/pages/empty_chat_screen.dart index cd37535f..a0ddf60a 100644 --- a/lib/features/chat/presentation/pages/empty_chat_screen.dart +++ b/lib/features/chat/presentation/pages/empty_chat_screen.dart @@ -79,34 +79,30 @@ class _EmptyChatScreenState extends State { ), const SizedBox(width: 8), Expanded( - child: SizedBox( - height: 40, - child: TextFormField( - expands: true, - textCapitalization: TextCapitalization.sentences, - controller: textEditingController, - maxLines: null, - minLines: null, - keyboardType: TextInputType.multiline, - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1), - decoration: InputDecoration( - isDense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - hintText: 'Сообщение', - hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(100), - borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), - ), + child: TextFormField( + textCapitalization: TextCapitalization.sentences, + controller: textEditingController, + maxLines: 3, + minLines: 1, + keyboardType: TextInputType.multiline, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1), + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + hintText: 'Сообщение', + hintStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, height: 1, color: Color(0xFFAEAEB2)), + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(100), + borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1), ), ), ), diff --git a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart index 2e0d3e13..5b634d9d 100644 --- a/lib/features/chat/presentation/widgets/chat_bubble_widget.dart +++ b/lib/features/chat/presentation/widgets/chat_bubble_widget.dart @@ -28,6 +28,7 @@ class ChatBubbleWidget extends StatelessWidget { required this.userSlug, this.messageType, this.isConsultant, + this.urlsMediaInfo, }); final DateTime sendTime; @@ -40,6 +41,7 @@ class ChatBubbleWidget extends StatelessWidget { final String userSlug; final String? messageType; final bool? isConsultant; + final List>? urlsMediaInfo; @override Widget build(BuildContext context) { final size = MediaQuery.sizeOf(context); @@ -127,7 +129,57 @@ class ChatBubbleWidget extends StatelessWidget { fileName = mediaItem.path.split('/').last; } else { final urlString = mediaItem.toString(); - fileName = urlString.contains('/') ? urlString.split('/').last.split('?').first : urlString.replaceAll('chat_files/', ''); + // Получаем расширение файла из URL + String? fileExtension; + final urlParts = urlString.split('/'); + if (urlParts.isNotEmpty) { + final lastPart = urlParts.last.split('?').first; + final dotIndex = lastPart.lastIndexOf('.'); + if (dotIndex != -1 && dotIndex < lastPart.length - 1) { + fileExtension = lastPart.substring(dotIndex); + } + } + + // Пытаемся найти имя файла в urls_media_info + String? fileNameFromInfo; + final mediaInfo = urlsMediaInfo; + if (mediaInfo != null && mediaInfo.isNotEmpty) { + for (final mediaInfoMap in mediaInfo) { + // Каждый элемент urls_media_info - это Map, где ключ - это URL + if (mediaInfoMap.isNotEmpty) { + for (final entry in mediaInfoMap.entries) { + final infoUrl = entry.key.toString(); + // Сравниваем полные URL или только имя файла + if (infoUrl == urlString || + urlString.contains(infoUrl) || + infoUrl.contains(urlString) || + urlString.split('/').last == infoUrl.split('/').last) { + final fileInfo = entry.value; + if (fileInfo is Map && fileInfo.containsKey('name')) { + final nameValue = fileInfo['name']; + if (nameValue != null) { + fileNameFromInfo = nameValue.toString(); + break; + } + } + } + } + if (fileNameFromInfo != null && fileNameFromInfo.isNotEmpty) break; + } + } + } + + if (fileNameFromInfo != null && fileNameFromInfo.isNotEmpty) { + // Добавляем расширение к названию файла, если его там нет + if (fileExtension != null && !fileNameFromInfo.endsWith(fileExtension)) { + fileName = '$fileNameFromInfo$fileExtension'; + } else { + fileName = fileNameFromInfo; + } + } else { + // Fallback к старой логике, если информации нет + fileName = urlString.contains('/') ? urlString.split('/').last.split('?').first : urlString.replaceAll('chat_files/', ''); + } } return ListTile( diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index 0be58bee..37c85b79 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -10,8 +10,12 @@ import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/dev_tools/test_cases.dart'; import 'package:eatvio_app/core/env_config.dart'; +import 'package:eatvio_app/features/diary/presentation/pages/barcode_scanner_screen.dart'; +import 'package:eatvio_app/features/barcode/repositories/barcode_repository.dart'; +import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'dart:async'; class DevToolsScreen extends StatelessWidget { const DevToolsScreen({super.key}); @@ -296,6 +300,20 @@ class _DevToolsScreenContentState extends State { } }, ), + const SizedBox(height: 10), + CustomOutlinedButton( + text: 'Сканирование QR/штрихкода', + buttonStyle: const ButtonStyle(), + onPressed: () async { + if (!context.mounted) return; + final barcode = await Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const BarcodeScannerScreen()), + ); + if (barcode != null && barcode.isNotEmpty && context.mounted) { + await _handleBarcodeScanned(barcode, context); + } + }, + ), const SizedBox(height: 300), ], ), @@ -303,6 +321,195 @@ class _DevToolsScreenContentState extends State { ), ); } + + Future _handleBarcodeScanned(String barcode, BuildContext context) async { + try { + final barcodeRepository = sl(); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const AlertDialog( + content: Row( + children: [ + CircularProgressIndicator(), + SizedBox(width: 16), + Text('Загрузка данных...'), + ], + ), + ), + ); + + final result = await barcodeRepository.getProductByBarcode(barcode); + + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки + + if (result.type == BarcodeResultType.success && result.product != null) { + // Данные получены сразу + _showProductDialog(context, result.product!, barcode); + } else if (result.type == BarcodeResultType.processing && result.taskId != null) { + // Запущена задача обработки, нужно опрашивать статус + await _pollTaskStatus(context, result.taskId!, barcode); + } + } catch (e) { + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки если открыт + showErrorToast('Ошибка: $e'); + _showBarcodeDialog(context, barcode, null); + } + } + + Future _pollTaskStatus(BuildContext context, String taskId, String barcode) async { + final barcodeRepository = sl(); + + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + const SizedBox(height: 16), + const Text('Обработка штрихкода...'), + const SizedBox(height: 8), + Text('Это может занять некоторое время', style: TextStyle(fontSize: 12, color: Colors.grey[600])), + ], + ), + ), + ); + + final completer = Completer(); + int attempts = 0; + const maxAttempts = 30; // Максимум 30 попыток (примерно 5 минут при интервале 10 секунд) + + // ignore: unused_local_variable + final timer = Timer.periodic(const Duration(seconds: 10), (timer) async { + attempts++; + try { + final status = await barcodeRepository.getTaskStatus(taskId); + + if (status.type == BarcodeTaskStatusType.completed && status.product != null) { + timer.cancel(); + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки + _showProductDialog(context, status.product!, barcode); + completer.complete(); + } else if (status.type == BarcodeTaskStatusType.failed) { + timer.cancel(); + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки + showErrorToast('Не удалось получить данные о продукте'); + _showBarcodeDialog(context, barcode, null); + completer.complete(); + } else if (attempts >= maxAttempts) { + timer.cancel(); + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки + showErrorToast('Превышено время ожидания'); + _showBarcodeDialog(context, barcode, null); + completer.complete(); + } + } catch (e) { + timer.cancel(); + if (!context.mounted) return; + Navigator.of(context).pop(); // Закрываем индикатор загрузки + showErrorToast('Ошибка при проверке статуса: $e'); + _showBarcodeDialog(context, barcode, null); + completer.complete(); + } + }); + + await completer.future; + } + + void _showProductDialog(BuildContext context, BarcodeProductModel product, String barcode) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Информация о продукте'), + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildInfoRow('Штрихкод:', barcode), + if (product.brandName != null) _buildInfoRow('Бренд:', product.brandName!), + if (product.description != null) _buildInfoRow('Описание:', product.description!), + if (product.quantity != null) _buildInfoRow('Количество:', product.quantity!), + if (product.market != null) _buildInfoRow('Рынок сбыта:', product.market!), + if (product.calories != null) _buildInfoRow('Калорийность:', '${product.calories} ккал'), + if (product.proteins != null) _buildInfoRow('Белки:', '${product.proteins} г'), + if (product.fats != null) _buildInfoRow('Жиры:', '${product.fats} г'), + if (product.carbohydrates != null) _buildInfoRow('Углеводы:', '${product.carbohydrates} г'), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: barcode)); + showInfoToast('Штрихкод скопирован в буфер обмена'); + }, + child: const Text('Скопировать код'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Закрыть'), + ), + ], + ), + ); + } + + void _showBarcodeDialog(BuildContext context, String barcode, BarcodeProductModel? product) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Отсканированный код'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(barcode), + if (product == null) ...[ + const SizedBox(height: 16), + const Text('Данные о продукте не найдены', style: TextStyle(color: Colors.grey)), + ], + ], + ), + actions: [ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: barcode)); + Navigator.of(context).pop(); + showInfoToast('Код скопирован в буфер обмена'); + }, + child: const Text('Скопировать'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Закрыть'), + ), + ], + ), + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: RichText( + text: TextSpan( + style: const TextStyle(color: Colors.black, fontSize: 14), + children: [ + TextSpan(text: '$label ', style: const TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: value), + ], + ), + ), + ); + } } enum TestState { success, fail, notRun, loading } diff --git a/lib/features/diary/presentation/pages/barcode_scanner_screen.dart b/lib/features/diary/presentation/pages/barcode_scanner_screen.dart index 19994568..dde46894 100644 --- a/lib/features/diary/presentation/pages/barcode_scanner_screen.dart +++ b/lib/features/diary/presentation/pages/barcode_scanner_screen.dart @@ -25,61 +25,63 @@ class _BarcodeScannerScreenState extends State { title: const Text('Сканирование штрихкода'), centerTitle: true, ), - body: Column( - children: [ - Expanded( - child: MobileScanner( - controller: _controller, - onDetect: (capture) { - final List barcodes = capture.barcodes; - if (barcodes.isEmpty) return; - final rawValue = barcodes.first.rawValue; - if (rawValue == null || rawValue == _detectedBarcode) return; - setState(() { - _detectedBarcode = rawValue; - }); - }, + body: SafeArea( + child: Column( + children: [ + Expanded( + child: MobileScanner( + controller: _controller, + onDetect: (capture) { + final List barcodes = capture.barcodes; + if (barcodes.isEmpty) return; + final rawValue = barcodes.first.rawValue; + if (rawValue == null || rawValue == _detectedBarcode) return; + setState(() { + _detectedBarcode = rawValue; + }); + }, + ), ), - ), - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration( - color: Color(0xFFF6F6F6), - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Найденный штрихкод', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - const SizedBox(height: 8), - Text( - _detectedBarcode ?? 'Наведите камеру на штрихкод', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: _detectedBarcode == null ? Colors.grey : Colors.black, + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Color(0xFFF6F6F6), + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Найденный штрихкод', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), - ), - const SizedBox(height: 16), - if (_detectedBarcode != null) - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.of(context).pop(_detectedBarcode); - }, - child: const Text('Использовать штрихкод'), + const SizedBox(height: 8), + Text( + _detectedBarcode ?? 'Наведите камеру на штрихкод', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: _detectedBarcode == null ? Colors.grey : Colors.black, ), ), - ], + const SizedBox(height: 16), + if (_detectedBarcode != null) + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(_detectedBarcode); + }, + child: const Text('Использовать штрихкод'), + ), + ), + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart index 9d77249e..4b0a3806 100644 --- a/lib/features/diary/presentation/widgets/add_product_modal.dart +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -5,11 +5,9 @@ import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; -import 'package:eatvio_app/features/diary/presentation/pages/barcode_scanner_screen.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/glossary_moderation_request_modal_widget.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:flutter/material.dart'; -import 'package:material_symbols_icons/material_symbols_icons.dart'; class AddProductModal extends StatefulWidget { final PortionModel? portion; @@ -64,25 +62,9 @@ class _AddProductModalState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Добавление блюда', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)), - ), - IconButton( - onPressed: () async { - final barcode = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => const BarcodeScannerScreen())); - if (barcode != null && barcode.isNotEmpty) { - productNameController.text = barcode; - setState(() {}); - } - }, - splashRadius: 20, - icon: const Icon(Symbols.barcode_scanner, color: Color(0xFF323F4B)), - ), - ], + const Text( + 'Добавление блюда', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF323F4B)), ), const SizedBox(height: 16), Row( diff --git a/lib/features/diets/presentation/pages/about_diet_screen.dart b/lib/features/diets/presentation/pages/about_diet_screen.dart index 9057c59b..82ab3144 100644 --- a/lib/features/diets/presentation/pages/about_diet_screen.dart +++ b/lib/features/diets/presentation/pages/about_diet_screen.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; @@ -70,9 +68,7 @@ class _AboutDietScreenState extends State { void _scrollToBottom() { _jumped = true; - scrollController.jumpTo( - scrollController.position.maxScrollExtent, - ); + scrollController.jumpTo(scrollController.position.maxScrollExtent); } @override @@ -97,11 +93,7 @@ class _AboutDietScreenState extends State { if (dietState is DietStateLoaded) { if (_isFirstLaunch) { context.read().viewDiet(dietState.diet.id); - context.read().getComments( - dietState.diet.id.toString(), - CommentSourceType.diet, - true, - ); + context.read().getComments(dietState.diet.id.toString(), CommentSourceType.diet, true); _isFirstLaunch = false; } Future.delayed(const Duration(milliseconds: 500), () { @@ -113,14 +105,13 @@ class _AboutDietScreenState extends State { backgroundColor: appTheme.gray100, resizeToAvoidBottomInset: true, bottomSheet: Container( - padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), + padding: EdgeInsets.only( + top: 12, + bottom: 12 + MediaQuery.of(context).padding.bottom + 56, // 56 - высота bottomNavigationBar + ), decoration: BoxDecoration( color: Colors.white, - border: Border( - top: BorderSide( - color: Colors.black.withValues(alpha: 0.1), - ), - ), + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -136,9 +127,7 @@ class _AboutDietScreenState extends State { highlightElevation: 0, elevation: 0, focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), backgroundColor: dietState.isStarted ? appTheme.transparentRed : appTheme.greenA700, onPressed: () { final cubit = context.read(); @@ -146,10 +135,7 @@ class _AboutDietScreenState extends State { if (dietState.isStarted) { showModalBottomSheet( context: context, - builder: (context) => CancelDietBottomSheet( - aboutDietCubit: cubit, - dietName: dietState.diet.name, - ), + builder: (context) => CancelDietBottomSheet(aboutDietCubit: cubit, dietName: dietState.diet.name), ); } else if (dietState.hasStartedDiet) { showModalBottomSheet( @@ -162,9 +148,7 @@ class _AboutDietScreenState extends State { } else { showModalBottomSheet( context: context, - builder: (context) => StartDietBottomSheet( - aboutDietCubit: cubit, - ), + builder: (context) => StartDietBottomSheet(aboutDietCubit: cubit), ); } }, @@ -209,17 +193,10 @@ class _AboutDietScreenState extends State { FocusManager.instance.primaryFocus?.unfocus(); }, child: Container( - decoration: BoxDecoration( - color: appTheme.blueA400, - shape: BoxShape.circle, - ), + decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle), height: 32, width: 32, - child: const Icon( - Icons.close_rounded, - size: 18, - color: Colors.white, - ), + child: const Icon(Icons.close_rounded, size: 18, color: Colors.white), ), ), if (replyId != null) const SizedBox(width: 8), @@ -235,22 +212,12 @@ class _AboutDietScreenState extends State { maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.black, - height: 1.2, - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2), decoration: InputDecoration( isDense: false, // убираем true, чтобы padding учитывался корректно contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен hintText: 'Комментарий', - hintStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - height: 1.2, - color: Color(0xFFAEAEB2), - ), + hintStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), @@ -312,14 +279,14 @@ class _AboutDietScreenState extends State { appBar: AppBar( centerTitle: true, leading: BackButton( - onPressed: () { - if (context.canPop()) { - GoRouter.of(context).pop(); - return; - } - GoRouter.of(context).go('/feed-screen'); - }, - ), + onPressed: () { + if (context.canPop()) { + GoRouter.of(context).pop(); + return; + } + GoRouter.of(context).go('/feed-screen'); + }, + ), title: Text( dietState.diet.name, maxLines: 2, @@ -342,7 +309,9 @@ class _AboutDietScreenState extends State { ), body: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only(bottom: 160), + padding: EdgeInsets.only( + bottom: 220 + MediaQuery.of(context).padding.bottom, + ), // кнопка (52) + отступ (8) + поле комментария (52-200) + padding (12+12+56) + safe area child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -371,10 +340,7 @@ class _AboutDietScreenState extends State { children: [ SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), const SizedBox(width: 8), - Text( - dietState.diet.totalViews.toString(), - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), + Text(dietState.diet.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), const SizedBox(width: 12), @@ -392,10 +358,7 @@ class _AboutDietScreenState extends State { colorFilter: dietState.diet.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, ), const SizedBox(width: 8), - Text( - dietState.diet.totalLikes.toString(), - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), + Text(dietState.diet.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), ), @@ -405,10 +368,7 @@ class _AboutDietScreenState extends State { children: [ SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12), const SizedBox(width: 8), - Text( - dietState.diet.totalComments.toString(), - style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), - ), + Text(dietState.diet.totalComments.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], ), ], @@ -419,26 +379,15 @@ class _AboutDietScreenState extends State { const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - s.about_diet, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), + child: Text(s.about_diet, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), ), const SizedBox(height: 12), - InfoRow( - diet: dietState.diet, - type: ProductType.diet, - ), - ExpandableText( - text: dietState.diet.details, - ), + InfoRow(diet: dietState.diet, type: ProductType.diet), + ExpandableText(text: dietState.diet.details), const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: AuthorCard( - author: dietState.diet.author, - title: s.diet_author, - ), + child: AuthorCard(author: dietState.diet.author, title: s.diet_author), ), const SizedBox(height: 16), ], @@ -451,9 +400,7 @@ class _AboutDietScreenState extends State { child: Center( child: Text( '${s.diet_plan}(${dietState.diet.duration.toString()} ${getDayForm(dietState.diet.duration, s)})', - style: TextStyles.headingXLSemibold.copyWith( - color: appTheme.black, - ), + style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), ), ), @@ -463,13 +410,7 @@ class _AboutDietScreenState extends State { physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemBuilder: (context, index) { - return _dayOfPlan( - dietState.diet.dailySchedule[index + 1] ?? {}, - index, - s, - dietState.selectedIndexes, - dietState.diet, - ); + return _dayOfPlan(dietState.diet.dailySchedule[index + 1] ?? {}, index, s, dietState.selectedIndexes, dietState.diet); }, itemCount: dietState.diet.dailySchedule.length, ), @@ -482,9 +423,7 @@ class _AboutDietScreenState extends State { alignment: Alignment.centerLeft, child: Text( '${dietState.diet.totalComments} ${getCommentForm(dietState.diet.totalComments, s)}', - style: TextStyles.textMMedium.copyWith( - color: appTheme.gray500, - ), + style: TextStyles.textMMedium.copyWith(color: appTheme.gray500), ), ), const SizedBox(height: 16), @@ -509,14 +448,14 @@ class _AboutDietScreenState extends State { appBar: AppBar( centerTitle: true, leading: BackButton( - onPressed: () { - if (context.canPop()) { - GoRouter.of(context).pop(); - return; - } - GoRouter.of(context).go('/feed-screen'); - }, - ), + onPressed: () { + if (context.canPop()) { + GoRouter.of(context).pop(); + return; + } + GoRouter.of(context).go('/feed-screen'); + }, + ), title: Text( '', maxLines: 2, @@ -569,18 +508,14 @@ class _AboutDietScreenState extends State { pressElevation: 0, showCheckmark: false, side: BorderSide.none, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), label: Text((index + 1).toString()), - labelStyle: TextStyles.textXSSemibold.copyWith( - color: index == selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600, - ), + labelStyle: TextStyles.textXSSemibold.copyWith(color: index == selectedIndexes[dayNumber] ? Colors.white : appTheme.gray600), selected: selectedIndexes[dayNumber] == index, backgroundColor: appTheme.gray100, onSelected: (bool selected) { - dietsCubit.setSelectedIndex(dayNumber: dayNumber, index: index); + dietsCubit.setSelectedIndex(dayNumber: dayNumber, index: index); }, ); } @@ -588,9 +523,7 @@ class _AboutDietScreenState extends State { return Padding( padding: const EdgeInsets.only(bottom: 8), child: MainBackgroundBouble( - padding: const EdgeInsets.only( - top: 16, - ), + padding: const EdgeInsets.only(top: 16), child: Column( children: [ Padding( @@ -598,31 +531,12 @@ class _AboutDietScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '${s.day} ${dayNumber + 1}', - style: TextStyles.headingXLSemibold.copyWith( - color: appTheme.black, - ), - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 12), - child: CustomDivider(), - ), - Text( - s.meal, - style: TextStyles.textMMedium.copyWith( - color: appTheme.black, - ), - ), + Text('${s.day} ${dayNumber + 1}', style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), + const Padding(padding: EdgeInsets.symmetric(vertical: 12), child: CustomDivider()), + Text(s.meal, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - choiceChipWidget(0), - choiceChipWidget(1), - choiceChipWidget(2), - choiceChipWidget(3), - choiceChipWidget(4), - ], + children: [choiceChipWidget(0), choiceChipWidget(1), choiceChipWidget(2), choiceChipWidget(3), choiceChipWidget(4)], ), const SizedBox(height: 8), ListView.builder( @@ -655,20 +569,10 @@ class _AboutDietScreenState extends State { children: [ SizedBox( width: MediaQuery.sizeOf(context).width * 0.6, - child: Text( - daily.name, - style: TextStyles.textMMedium.copyWith( - color: appTheme.black, - ), - ), + child: Text(daily.name, style: TextStyles.textMMedium.copyWith(color: appTheme.black)), ), const Spacer(), - Text( - '${daily.quantity} ${getLocalizedMeasurementUnits(s, daily.portion)}', - style: TextStyles.textMSemibold.copyWith( - color: appTheme.black, - ), - ), + Text('${daily.quantity} ${getLocalizedMeasurementUnits(s, daily.portion)}', style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), ], ), ); diff --git a/lib/features/plans/presentation/pages/about_plan_screen.dart b/lib/features/plans/presentation/pages/about_plan_screen.dart index 4ab8dd24..1fc6dba2 100644 --- a/lib/features/plans/presentation/pages/about_plan_screen.dart +++ b/lib/features/plans/presentation/pages/about_plan_screen.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; @@ -64,9 +62,7 @@ class _AboutPlanScreenState extends State { void _scrollToBottom() { _jumped = true; - scrollController.jumpTo( - scrollController.position.maxScrollExtent, - ); + scrollController.jumpTo(scrollController.position.maxScrollExtent); } @override @@ -75,17 +71,8 @@ class _AboutPlanScreenState extends State { return PopScope( child: MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => commentsCubit - ..getComments( - widget.slug, - CommentSourceType.plan, - true, - ), - ), - BlocProvider( - create: (context) => sl()..getPlanWithLoading(widget.slug), - ), + BlocProvider(create: (context) => commentsCubit..getComments(widget.slug, CommentSourceType.plan, true)), + BlocProvider(create: (context) => sl()..getPlanWithLoading(widget.slug)), ], child: BlocConsumer( listener: (BuildContext context, CommentsState commentsState) { @@ -166,14 +153,13 @@ class _AboutPlanScreenState extends State { backgroundColor: appTheme.gray100, resizeToAvoidBottomInset: true, bottomSheet: Container( - padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), + padding: EdgeInsets.only( + top: 12, + bottom: 12 + MediaQuery.of(context).padding.bottom + 56, // 56 - высота bottomNavigationBar + ), decoration: BoxDecoration( color: Colors.white, - border: Border( - top: BorderSide( - color: Colors.black.withValues(alpha: 0.1), - ), - ), + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), @@ -189,9 +175,7 @@ class _AboutPlanScreenState extends State { highlightElevation: 0, elevation: 0, focusElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: appTheme.greenA700, onPressed: () async { final (chosenPlan, error) = await context.read().chosePlan(); @@ -205,18 +189,9 @@ class _AboutPlanScreenState extends State { return; } if (!context.mounted) return; - GoRouter.of(context).pushReplacementNamed( - 'chatScreen', - extra: { - 'uuid': chosenPlan.chatId, - 'sendInitialMessage': true, - }, - ); + GoRouter.of(context).pushReplacementNamed('chatScreen', extra: {'uuid': chosenPlan.chatId, 'sendInitialMessage': true}); }, - label: Text( - s.to_order, - style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), - ), + label: Text(s.to_order, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700)), ), ), ), @@ -255,17 +230,10 @@ class _AboutPlanScreenState extends State { FocusManager.instance.primaryFocus?.unfocus(); }, child: Container( - decoration: BoxDecoration( - color: appTheme.blueA400, - shape: BoxShape.circle, - ), + decoration: BoxDecoration(color: appTheme.blueA400, shape: BoxShape.circle), height: 32, width: 32, - child: const Icon( - Icons.close_rounded, - size: 18, - color: Colors.white, - ), + child: const Icon(Icons.close_rounded, size: 18, color: Colors.white), ), ), if (replyId != null) const SizedBox(width: 8), @@ -281,22 +249,12 @@ class _AboutPlanScreenState extends State { maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.black, - height: 1.2, - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2), decoration: InputDecoration( isDense: false, // убираем true, чтобы padding учитывался корректно contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен hintText: 'Комментарий', - hintStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - height: 1.2, - color: Color(0xFFAEAEB2), - ), + hintStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), @@ -345,11 +303,7 @@ class _AboutPlanScreenState extends State { ), height: 32, width: 32, - child: Icon( - Icons.send_rounded, - size: 18, - color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black, - ), + child: Icon(Icons.send_rounded, size: 18, color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black), ), ), ], @@ -389,7 +343,9 @@ class _AboutPlanScreenState extends State { ), body: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only(bottom: 160), + padding: EdgeInsets.only( + bottom: 220 + MediaQuery.of(context).padding.bottom, + ), // кнопка (52) + отступ (8) + поле комментария (52-200) + padding (12+12+56) + safe area child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -436,8 +392,9 @@ class _AboutPlanScreenState extends State { 'assets/icons/heart.svg', width: 14, height: 12, - colorFilter: - state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, + colorFilter: state.planModel?.wasLikedByMe == true + ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) + : null, ), const SizedBox(width: 8), Text( @@ -467,24 +424,16 @@ class _AboutPlanScreenState extends State { const SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - s.about_plan, - style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), - ), + child: Text(s.about_plan, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), ), const SizedBox(height: 12), InfoRow(plan: state.planModel, type: ProductType.plan), - ExpandableText( - text: state.planModel?.details ?? '', - ), + ExpandableText(text: state.planModel?.details ?? ''), if (state.planModel != null) ...[ const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: AuthorCard( - author: (state.planModel?.author)!, - title: s.plan_author, - ), + child: AuthorCard(author: (state.planModel?.author)!, title: s.plan_author), ), ], const SizedBox(height: 16), @@ -503,22 +452,11 @@ class _AboutPlanScreenState extends State { const SizedBox(height: 12), ], switch (state.chosenOptionIndex) { - 1 => OptionWidget( - option: state.planModel?.packet2Options, - duration: state.planModel?.duration ?? 0, - ), - 2 => OptionWidget( - option: state.planModel?.packet3Options, - duration: state.planModel?.duration ?? 0, - ), - _ => OptionWidget( - option: state.planModel?.packet1Options, - duration: state.planModel?.duration ?? 0, - ), + 1 => OptionWidget(option: state.planModel?.packet2Options, duration: state.planModel?.duration ?? 0), + 2 => OptionWidget(option: state.planModel?.packet3Options, duration: state.planModel?.duration ?? 0), + _ => OptionWidget(option: state.planModel?.packet1Options, duration: state.planModel?.duration ?? 0), }, - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), MainBackgroundBouble( padding: const EdgeInsets.symmetric(vertical: 16), child: Column( @@ -528,9 +466,7 @@ class _AboutPlanScreenState extends State { alignment: Alignment.centerLeft, child: Text( '${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}', - style: TextStyles.textMMedium.copyWith( - color: appTheme.gray500, - ), + style: TextStyles.textMMedium.copyWith(color: appTheme.gray500), ), ), const SizedBox(height: 16), diff --git a/lib/features/recipe/presentation/pages/about_recipe_screen.dart b/lib/features/recipe/presentation/pages/about_recipe_screen.dart index efc19a12..209cb873 100644 --- a/lib/features/recipe/presentation/pages/about_recipe_screen.dart +++ b/lib/features/recipe/presentation/pages/about_recipe_screen.dart @@ -1,6 +1,4 @@ import 'dart:developer'; -import 'dart:io'; - import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; @@ -100,8 +98,14 @@ class _AboutRecipeScreenState extends State { backgroundColor: appTheme.gray100, resizeToAvoidBottomInset: true, bottomSheet: Container( - padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), - decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1)))), + padding: EdgeInsets.only( + top: 12, + bottom: 12 + MediaQuery.of(context).padding.bottom + 56, // 56 - высота bottomNavigationBar + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), + ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( @@ -235,7 +239,9 @@ class _AboutRecipeScreenState extends State { ), body: SingleChildScrollView( controller: scrollController, - padding: const EdgeInsets.only(bottom: 160), + padding: EdgeInsets.only( + bottom: 220 + MediaQuery.of(context).padding.bottom, + ), // кнопка (52) + отступ (8) + поле комментария (52-200) + padding (12+12+56) + safe area child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -387,7 +393,11 @@ class _AboutRecipeScreenState extends State { if (state is CommentsStateLoading) const Loader(), if (state is CommentsStateLoaded) CommentsWidget( - id: recipeState.recipe.slug, commentSourceType: CommentSourceType.recipe, comment: state.comment, replyId: replyId), + id: recipeState.recipe.slug, + commentSourceType: CommentSourceType.recipe, + comment: state.comment, + replyId: replyId, + ), ], ), ), diff --git a/lib/features/settings/presentation/pages/general_info_screen.dart b/lib/features/settings/presentation/pages/general_info_screen.dart index 0e7600ce..7ea472ab 100644 --- a/lib/features/settings/presentation/pages/general_info_screen.dart +++ b/lib/features/settings/presentation/pages/general_info_screen.dart @@ -65,10 +65,16 @@ class _GeneralInfoScreenState extends State { appBar: AppBar( title: const Text('Общая информация'), ), - bottomSheet: _saveButton(), + bottomSheet: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 56), // высота bottomNavigationBar + child: _saveButton(), + ), + ), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 64), + padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 120), // 52 (кнопка) + 56 (навигация) + 12 (отступ) child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/features/settings/presentation/pages/other_info_screen.dart b/lib/features/settings/presentation/pages/other_info_screen.dart index 840d8bbe..839c440d 100644 --- a/lib/features/settings/presentation/pages/other_info_screen.dart +++ b/lib/features/settings/presentation/pages/other_info_screen.dart @@ -45,9 +45,15 @@ class _OtherInfoScreenState extends State { appBar: AppBar( title: const Text('Прочая информаиця'), ), - bottomSheet: _saveButton(), + bottomSheet: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 56), // высота bottomNavigationBar + child: _saveButton(), + ), + ), body: SingleChildScrollView( - padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 64), + padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 120), // 52 (кнопка) + 56 (навигация) + 12 (отступ) child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/features/settings/presentation/pages/physical_activity_screen.dart b/lib/features/settings/presentation/pages/physical_activity_screen.dart index 1aa9e1bb..74b6f971 100644 --- a/lib/features/settings/presentation/pages/physical_activity_screen.dart +++ b/lib/features/settings/presentation/pages/physical_activity_screen.dart @@ -75,10 +75,16 @@ class _PhysicalActivityScreenState extends State { builder: (context, state) { return Scaffold( appBar: AppBar(title: const Text('Физическая нагрузка')), - bottomSheet: _saveButton(), + bottomSheet: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 56), // высота bottomNavigationBar + child: _saveButton(), + ), + ), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.only(top: 16, bottom: 80), + padding: const EdgeInsets.only(top: 16, bottom: 120), // 52 (кнопка) + 56 (навигация) + 12 (отступ) child: Column( children: [ Row( diff --git a/lib/features/settings/presentation/pages/physical_form_screen.dart b/lib/features/settings/presentation/pages/physical_form_screen.dart index 0c22cf85..f806b086 100644 --- a/lib/features/settings/presentation/pages/physical_form_screen.dart +++ b/lib/features/settings/presentation/pages/physical_form_screen.dart @@ -45,10 +45,16 @@ class _PhysicalFormScreenState extends State { appBar: AppBar( title: const Text('Физическая форма'), ), - bottomSheet: _saveButton(), + bottomSheet: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 56), // высота bottomNavigationBar + child: _saveButton(), + ), + ), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 64), + padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 120), // 52 (кнопка) + 56 (навигация) + 12 (отступ) child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/features/settings/presentation/pages/pressure_screen.dart b/lib/features/settings/presentation/pages/pressure_screen.dart index 287744a9..b50d9a42 100644 --- a/lib/features/settings/presentation/pages/pressure_screen.dart +++ b/lib/features/settings/presentation/pages/pressure_screen.dart @@ -50,10 +50,16 @@ class _PressureScreenState extends State { appBar: AppBar( title: const Text('Давление'), ), - bottomSheet: _saveButton(), + bottomSheet: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.only(bottom: 56), // высота bottomNavigationBar + child: _saveButton(), + ), + ), body: SingleChildScrollView( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 64), + padding: const EdgeInsets.all(16) + const EdgeInsets.only(bottom: 120), // 52 (кнопка) + 56 (навигация) + 12 (отступ) child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/pubspec.yaml b/pubspec.yaml index 96bab2f8..b861f083 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.8+88 +version: 1.3.10+90 publish_to: 'none' environment: diff --git a/test/core/constants/app_constants_test.dart b/test/core/constants/app_constants_test.dart index 2b4d8d80..85f13a4e 100644 --- a/test/core/constants/app_constants_test.dart +++ b/test/core/constants/app_constants_test.dart @@ -146,6 +146,8 @@ void main() { + + diff --git a/test/core/cubit/state_utils_test.dart b/test/core/cubit/state_utils_test.dart index e945e87b..3021d44a 100644 --- a/test/core/cubit/state_utils_test.dart +++ b/test/core/cubit/state_utils_test.dart @@ -81,6 +81,8 @@ void main() { + + diff --git a/test/core/data/selection_popup_model_test.dart b/test/core/data/selection_popup_model_test.dart index 17ed81e2..1e2914f7 100644 --- a/test/core/data/selection_popup_model_test.dart +++ b/test/core/data/selection_popup_model_test.dart @@ -95,6 +95,8 @@ void main() { + + diff --git a/test/core/enums/prefs_keys_test.dart b/test/core/enums/prefs_keys_test.dart index ae964223..066d5f5a 100644 --- a/test/core/enums/prefs_keys_test.dart +++ b/test/core/enums/prefs_keys_test.dart @@ -44,6 +44,8 @@ void main() { + + diff --git a/test/core/internet_connection_cubit/internet_connection_state_test.dart b/test/core/internet_connection_cubit/internet_connection_state_test.dart index 60aa037d..4fe26817 100644 --- a/test/core/internet_connection_cubit/internet_connection_state_test.dart +++ b/test/core/internet_connection_cubit/internet_connection_state_test.dart @@ -70,6 +70,8 @@ void main() { + + diff --git a/test/core/prefs_keys_test.dart b/test/core/prefs_keys_test.dart index 60331f71..433b1c75 100644 --- a/test/core/prefs_keys_test.dart +++ b/test/core/prefs_keys_test.dart @@ -40,6 +40,8 @@ void main() { + + diff --git a/test/core/routing/main_navigation_routes_test.dart b/test/core/routing/main_navigation_routes_test.dart index 71f34975..c6fe27ea 100644 --- a/test/core/routing/main_navigation_routes_test.dart +++ b/test/core/routing/main_navigation_routes_test.dart @@ -37,6 +37,8 @@ void main() { + + diff --git a/test/core/routing/other_routes_test.dart b/test/core/routing/other_routes_test.dart index c2640d00..09ae2261 100644 --- a/test/core/routing/other_routes_test.dart +++ b/test/core/routing/other_routes_test.dart @@ -41,6 +41,8 @@ void main() { + + diff --git a/test/core/routing/route_constants_test.dart b/test/core/routing/route_constants_test.dart index 35a510b3..99076a7d 100644 --- a/test/core/routing/route_constants_test.dart +++ b/test/core/routing/route_constants_test.dart @@ -120,6 +120,8 @@ void main() { + + diff --git a/test/core/routing/settings_routes_test.dart b/test/core/routing/settings_routes_test.dart index 639613e5..6d96f91d 100644 --- a/test/core/routing/settings_routes_test.dart +++ b/test/core/routing/settings_routes_test.dart @@ -44,6 +44,8 @@ void main() { + + diff --git a/test/core/views/cubit/views_state_test.dart b/test/core/views/cubit/views_state_test.dart index f1c6533f..80837cb7 100644 --- a/test/core/views/cubit/views_state_test.dart +++ b/test/core/views/cubit/views_state_test.dart @@ -38,6 +38,8 @@ void main() { + + diff --git a/test/core/widgets/add_item_button_test.dart b/test/core/widgets/add_item_button_test.dart index 4d35ca93..3bcfbd0b 100644 --- a/test/core/widgets/add_item_button_test.dart +++ b/test/core/widgets/add_item_button_test.dart @@ -74,6 +74,8 @@ void main() { + + diff --git a/test/core/widgets/animated_option_button_test.dart b/test/core/widgets/animated_option_button_test.dart index 0a1736b7..ea7d573a 100644 --- a/test/core/widgets/animated_option_button_test.dart +++ b/test/core/widgets/animated_option_button_test.dart @@ -126,6 +126,8 @@ void main() { + + diff --git a/test/core/widgets/base_button_test.dart b/test/core/widgets/base_button_test.dart index 6454fe07..a8763ccc 100644 --- a/test/core/widgets/base_button_test.dart +++ b/test/core/widgets/base_button_test.dart @@ -63,6 +63,8 @@ void main() { + + diff --git a/test/core/widgets/bottom_sheet_with_blur_test.dart b/test/core/widgets/bottom_sheet_with_blur_test.dart index 0b207a4a..cbec65dd 100644 --- a/test/core/widgets/bottom_sheet_with_blur_test.dart +++ b/test/core/widgets/bottom_sheet_with_blur_test.dart @@ -145,6 +145,8 @@ void main() { + + diff --git a/test/core/widgets/cpfc_container_test.dart b/test/core/widgets/cpfc_container_test.dart index 6692cb04..344acacd 100644 --- a/test/core/widgets/cpfc_container_test.dart +++ b/test/core/widgets/cpfc_container_test.dart @@ -106,6 +106,8 @@ void main() { + + diff --git a/test/core/widgets/custom_search_bar_test.dart b/test/core/widgets/custom_search_bar_test.dart index 0ae6d1d5..ff0b258b 100644 --- a/test/core/widgets/custom_search_bar_test.dart +++ b/test/core/widgets/custom_search_bar_test.dart @@ -174,6 +174,8 @@ void main() { + + diff --git a/test/core/widgets/item_widget_test.dart b/test/core/widgets/item_widget_test.dart index b71a81ff..c2076e46 100644 --- a/test/core/widgets/item_widget_test.dart +++ b/test/core/widgets/item_widget_test.dart @@ -67,6 +67,8 @@ void main() { + + diff --git a/test/core/widgets/loader_test.dart b/test/core/widgets/loader_test.dart index 83c46f35..7ff7ff7c 100644 --- a/test/core/widgets/loader_test.dart +++ b/test/core/widgets/loader_test.dart @@ -52,6 +52,8 @@ void main() { + + diff --git a/test/core/widgets/main_background_bouble_test.dart b/test/core/widgets/main_background_bouble_test.dart index 5589ab7b..e7c0fd78 100644 --- a/test/core/widgets/main_background_bouble_test.dart +++ b/test/core/widgets/main_background_bouble_test.dart @@ -106,6 +106,8 @@ void main() { + + diff --git a/test/core/widgets/marketing_bottom_sheet_test.dart b/test/core/widgets/marketing_bottom_sheet_test.dart index 568dfed7..bda701ed 100644 --- a/test/core/widgets/marketing_bottom_sheet_test.dart +++ b/test/core/widgets/marketing_bottom_sheet_test.dart @@ -86,6 +86,8 @@ void main() { + + diff --git a/test/core/widgets/option_widget_test.dart b/test/core/widgets/option_widget_test.dart index b03bea45..0d932449 100644 --- a/test/core/widgets/option_widget_test.dart +++ b/test/core/widgets/option_widget_test.dart @@ -75,6 +75,8 @@ void main() { + + diff --git a/test/features/create_diet/presentation/widgets/error_widget_test.dart b/test/features/create_diet/presentation/widgets/error_widget_test.dart index 9640413e..e918ee44 100644 --- a/test/features/create_diet/presentation/widgets/error_widget_test.dart +++ b/test/features/create_diet/presentation/widgets/error_widget_test.dart @@ -81,6 +81,8 @@ void main() { + + diff --git a/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart b/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart index 5d19c5ac..410c00c8 100644 --- a/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart +++ b/test/features/diary/presentation/widgets/chosen_diet_widget_content_test.dart @@ -90,6 +90,8 @@ void main() { + + diff --git a/test/features/diary/presentation/widgets/comment_container_test.dart b/test/features/diary/presentation/widgets/comment_container_test.dart index 7f753e68..3a9d8e92 100644 --- a/test/features/diary/presentation/widgets/comment_container_test.dart +++ b/test/features/diary/presentation/widgets/comment_container_test.dart @@ -68,6 +68,8 @@ void main() { + + diff --git a/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart b/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart index 128d354f..d4fda847 100644 --- a/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart +++ b/test/features/diary/presentation/widgets/finish_day_button_widget_test.dart @@ -63,6 +63,8 @@ void main() { + + diff --git a/test/features/diary/presentation/widgets/meal_summary_widget_test.dart b/test/features/diary/presentation/widgets/meal_summary_widget_test.dart index 5a9ec47f..c5fed4a8 100644 --- a/test/features/diary/presentation/widgets/meal_summary_widget_test.dart +++ b/test/features/diary/presentation/widgets/meal_summary_widget_test.dart @@ -100,6 +100,8 @@ void main() { + + diff --git a/test/features/diary/presentation/widgets/product_loader_widget_test.dart b/test/features/diary/presentation/widgets/product_loader_widget_test.dart index 5e23e62a..fdb02efd 100644 --- a/test/features/diary/presentation/widgets/product_loader_widget_test.dart +++ b/test/features/diary/presentation/widgets/product_loader_widget_test.dart @@ -52,6 +52,8 @@ void main() { + + diff --git a/test/features/diets/presentation/widgets/info_bouble_test.dart b/test/features/diets/presentation/widgets/info_bouble_test.dart index 499172d3..692e2d8c 100644 --- a/test/features/diets/presentation/widgets/info_bouble_test.dart +++ b/test/features/diets/presentation/widgets/info_bouble_test.dart @@ -118,6 +118,8 @@ void main() { + + diff --git a/test/features/feed/presentation/widgets/choice_chip_widget_test.dart b/test/features/feed/presentation/widgets/choice_chip_widget_test.dart index 6b61de56..ed8ef25e 100644 --- a/test/features/feed/presentation/widgets/choice_chip_widget_test.dart +++ b/test/features/feed/presentation/widgets/choice_chip_widget_test.dart @@ -148,6 +148,8 @@ void main() { + + diff --git a/test/features/feed/presentation/widgets/filter_badge_test.dart b/test/features/feed/presentation/widgets/filter_badge_test.dart index f78e9253..fd204766 100644 --- a/test/features/feed/presentation/widgets/filter_badge_test.dart +++ b/test/features/feed/presentation/widgets/filter_badge_test.dart @@ -60,6 +60,8 @@ void main() { + + diff --git a/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart b/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart index 7a6125af..af9099fc 100644 --- a/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart +++ b/test/features/finances_and_consultations_archive/presentation/widgets/status_widget_test.dart @@ -68,6 +68,8 @@ void main() { + + diff --git a/test/features/glossary/repositories/glossary_repository_test.dart b/test/features/glossary/repositories/glossary_repository_test.dart index 5e08f826..67514a78 100644 --- a/test/features/glossary/repositories/glossary_repository_test.dart +++ b/test/features/glossary/repositories/glossary_repository_test.dart @@ -61,6 +61,8 @@ void main() { + + diff --git a/test/features/notifications/presentation/widgets/notification_widget_test.dart b/test/features/notifications/presentation/widgets/notification_widget_test.dart index eba78121..6094e398 100644 --- a/test/features/notifications/presentation/widgets/notification_widget_test.dart +++ b/test/features/notifications/presentation/widgets/notification_widget_test.dart @@ -1038,6 +1038,8 @@ void main() { + + diff --git a/test/features/notifications/service/notifications_service_test.dart b/test/features/notifications/service/notifications_service_test.dart index a1583d0d..e644bd23 100644 --- a/test/features/notifications/service/notifications_service_test.dart +++ b/test/features/notifications/service/notifications_service_test.dart @@ -73,3 +73,5 @@ void main() { + + From 5d6e25858bf6f30a64f60e089498c06e283e7616 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 22 Dec 2025 16:52:40 +0300 Subject: [PATCH 2/2] test(chat): update chat service test to validate message response and ensure correct message model handling --- test/features/chat/services/chat_service_test.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/features/chat/services/chat_service_test.dart b/test/features/chat/services/chat_service_test.dart index 103c4944..3e4a924c 100644 --- a/test/features/chat/services/chat_service_test.dart +++ b/test/features/chat/services/chat_service_test.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/chat/models/chat_message_model.dart'; import 'package:eatvio_app/features/chat/models/chat_messages_model.dart'; import 'package:eatvio_app/features/chat/models/chat_room_model.dart' as chat_room; import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart'; @@ -66,12 +67,16 @@ void main() { const slug = 'user-slug'; const data = 'Test message'; const socketId = 'socket-id-123'; + final messageFixture = FixtureReader.readAsMap('chat/chat_message_response.json'); + final expectedMessage = ChatMessageModel.fromJson(messageFixture); // ignore: null_argument_to_non_null_type - when(mockChatService.sendMessage(slug: slug, data: data, mediaData: null, socketId: socketId)).thenAnswer((_) async => Future.value()); + when(mockChatService.sendMessage(slug: slug, data: data, mediaData: null, socketId: socketId)).thenAnswer((_) async => expectedMessage); - await mockChatService.sendMessage(slug: slug, data: data, mediaData: null, socketId: socketId); + final result = await mockChatService.sendMessage(slug: slug, data: data, mediaData: null, socketId: socketId); + expect(result, isA()); + expect(result.data, data); verify(mockChatService.sendMessage(slug: slug, data: data, mediaData: null, socketId: socketId)).called(1); });