import 'dart:async'; import 'package:eatvio_app/features/diary/models/diary_model.dart'; import 'package:eatvio_app/features/diary/models/meal.dart'; import 'package:eatvio_app/features/diary/models/product.dart'; import 'package:eatvio_app/features/diary/repositories/diary_repository.dart'; import 'package:intl/intl.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '/core/app_export.dart'; import 'package:image_picker/image_picker.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:flutter_dotenv/flutter_dotenv.dart'; part 'diary_event.dart'; /// A bloc that manages the state of a Diary according to the event that is dispatched to it. class DiaryBloc extends Bloc { final DiaryRepository diaryRepository; DiaryBloc({required this.diaryRepository}) : super(const DiaryState.loading()) { on(loadDiary); on(_changeSatiety); // пользователь нажимает на шкалу сытости on(_changeMood); // пользователь нажимает на шкалу настроения on(_changeMealTime); // пользовательяет времяа пиши on(_changeMeal); on(_changePortion); on(_changeWakeup); // пользователь выбирает время пробуждения on(_changeRetire); // пользователь выбирает время отхода ко сну on(_toggleMood); on(_toggleWakeup); on(_addPortion); //пользователь нажимает "Добавить продукт" on(_removePortion); on( _removeImage, ); // пользователь нажимает на крестик около продукта on(_uploadImages); //пользователь нажимает "Добавить фото" on(_addComment); //пользователь нажимает "Добавить комментарий" on( _inputProduct, ); on(_finishDay); on(_sendModeration); on( reloadScreen, ); on(_clearInput); } loadDiary( LoadDiary event, Emitter emit, ) async { emit(const DiaryState.loading()); try { Map data = await diaryRepository.getDiaryID(); final int? diaryID = data['id']; final DiaryModel diary = await diaryRepository.getDiary(diaryID ?? 45); final Meal currentMeal = diary.meals[0]; List portionList = [ SelectionPopupModel(title: "gr"), SelectionPopupModel(title: "pcs") ]; List mealList = [ SelectionPopupModel(title: "Прием пищи 1", id: 1), SelectionPopupModel(title: "Прием пищи 2", id: 2), SelectionPopupModel(title: "Прием пищи 3", id: 3), SelectionPopupModel(title: "Прием пищи 4", id: 4), SelectionPopupModel(title: "Прием пищи 5", id: 5), SelectionPopupModel(title: "Прием пищи 6", id: 6), ]; emit(DiaryState.loaded( diary: diary, currentMeal: currentMeal, portions: currentMeal.portions, photos: currentMeal.photos, portionList: portionList, portionController: portionList[0], mealList: mealList, selectedMood: currentMeal.mood, selectedSatiety: currentMeal.satiety, commentController: TextEditingController(), productNameController: TextEditingController(), productQuantityController: TextEditingController(), caloriesController: TextEditingController(), fatsController: TextEditingController(), proteinsController: TextEditingController(), carbsController: TextEditingController(), wakeupExpanded: false, moderationExpanded: false, moodExpanded: false, isLoading: false, isAdded: false, autocompleteList: [], recommendedChartValues: [1, 1, 1, 1], consumedChartValues: [1, 1, 1, 1])); } catch (e) { emit(DiaryState.error(errorMessage: e.toString())); } } fetchChartValues( String token, int diaryID, Emitter emit, ) async { final responseChart = await http.get( Uri.parse( dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/dailyNutritionData/', ), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $token', }, ); if (responseChart.statusCode == 200) { final responseBody = jsonDecode(responseChart.body); final recommendedChartValues = responseBody['dailyNutritionRecommendation'] .values .map((value) => convertToInt(value)) .toList(); final consumedChartValues = responseBody['dailyNutritionIntake'] .values .map((value) => convertToInt(value)) .toList(); print([recommendedChartValues, consumedChartValues]); return [recommendedChartValues, consumedChartValues]; } else { emit(DiaryStateError( errorMessage: 'Ошибка при декодировании данных КБЖУ')); print('fail'); return [[], []]; // Return empty lists instead of -1 } } refreshScreen( int mealID, DiaryEvent event, Emitter emit, ) async { await reloadScreen(event, emit); } reloadScreen( DiaryEvent event, Emitter emit, ) async { print("Reloading..."); } Future _changeSatiety( ChangeSatiety event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith(selectedSatiety: event.selectedElement); diaryRepository.changeSatiety(event.selectedElement, currentState.currentMeal.id, currentState.diary.id); emit(updatedState); } } Future _changeMood( ChangeMood event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith(selectedMood: event.selectedElement); diaryRepository.changeMood(event.selectedElement, currentState.currentMeal.id, currentState.diary.id); emit(updatedState); // Передаем обновленное состояние в emit } } FutureOr _changeWakeup( WakeupEvent event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith(wakeupController: event.selectedTime); final DateFormat formatter = DateFormat('HH:mm'); final String wakeupString = formatter.format(DateTime( 2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); await diaryRepository.changeWakeup( currentState.diary.id, wakeupString, ); emit(updatedState); } } FutureOr _changeRetire( RetireEvent event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith(retireController: event.selectedTime); final DateFormat formatter = DateFormat('HH:mm'); final String retireString = formatter.format(DateTime( 2022, 1, 1, event.selectedTime.hour, event.selectedTime.minute)); await diaryRepository.changeRetire( currentState.diary.id, retireString, ); emit(updatedState); } } FutureOr _changeMealTime( ChangeMealTime event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith(mealTimeController: event.time); emit(updatedState); } } FutureOr _changeMeal( ChangeMeal event, Emitter emit, ) async { if (state is DiaryStateLoaded) { final currentState = state as DiaryStateLoaded; final Meal meal = currentState.diary.meals .firstWhere((meal) => meal.type == event.value.id); DiaryStateLoaded updatedState = currentState.copyWith( currentMeal: meal, portions: meal.portions, photos: meal.photos, mealTimeController: toTime(meal.time), selectedMood: meal.mood, selectedSatiety: meal.satiety, ); emit(updatedState); } } Future _inputProduct( ProductInput event, Emitter emit, ) async { if (state is DiaryStateLoaded) { //var currentState = state as DiaryStateLoaded; print(await diaryRepository.inputProduct(event.input)); final Map> productNutritionalValues = await diaryRepository.inputProduct(event.input); // Create a new controller with the input text and update the autocomplete list /*var updatedState = currentState.copyWith( productNameController: TextEditingController(text: event.input), autocompleteList: productNutritionalValues.map((product) => product['name']).toList(), ); if (event.input.length > 3) { updatedState = updatedState.copyWith(moderationExpanded: true); } else { updatedState = updatedState.copyWith(moderationExpanded: false); } emit(updatedState); */ } } FutureOr _clearInput( ClearInput event, Emitter emit, ) { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; DiaryStateLoaded updatedState = currentState.copyWith(); emit(updatedState); } } FutureOr _changePortion( ChangePortion event, Emitter emit, ) { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; DiaryStateLoaded updatedState = currentState.copyWith( portionController: event.value, ); emit(updatedState); } } FutureOr _toggleMood( ToggleMoodExpandedEvent event, Emitter emit, ) { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; emit(currentState.copyWith(moodExpanded: !currentState.moodExpanded)); } } FutureOr _toggleWakeup( ToggleWakeupExpandedEvent event, Emitter emit, ) { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; emit(currentState.copyWith(wakeupExpanded: !currentState.wakeupExpanded)); } } FutureOr _addPortion( AddPortionEvent event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; await diaryRepository.addPortion( currentState.diary.id, currentState.currentMeal.id, currentState.productNameController.text, currentState.portionController.value, int.parse(currentState.productQuantityController.text), ); emit(currentState.copyWith(isAdded: true)); } } Future _removePortion( RemovePortionEvent event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; int? mealId = currentState.currentMeal.id; // Rename id to mealId for clarity var response = await sendRemovePortion( event.portionId, mealId, currentState.diary.id, ); if (response.statusCode == 204) { await reloadScreen(event, emit); } else { print( 'Failed to add portion. Status Code: ${response.statusCode} ${response.body}', ); } } } Future sendRemovePortion( int? portionID, int? mealID, int diaryID, ) async { String url = '${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealID/portions/$portionID/delete'; var response = await http.delete( Uri.parse(url), headers: { 'Content-Type': 'application/json', }, encoding: utf8, ); return response; } FutureOr _removeImage( RemoveImageEvent event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var response = await sendRemovePhoto( event.path, event.photoID, currentState.currentMeal.id, currentState.diary.id, ); print(response.statusCode); if (response.statusCode == 204) { await reloadScreen(event, emit); } else { print( 'Failed to remove photo. Status Code: ${response.statusCode} ${response.body}', ); } } } Future sendRemovePhoto( String? path, String? photoID, int? mealID, int diaryID, ) async { String url = '${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealID/photo/delete'; final Map credentials = { 'id': photoID, 'path': path, }; var response = await http.delete( Uri.parse(url), headers: { 'Content-Type': 'application/json', }, body: json.encode(credentials), encoding: utf8, ); return response; } Future _finishDay(FinishDay event, Emitter emit) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var response = await sendFinishDay( currentState.diary.id, currentState.wakeupController, currentState.retireController, currentState.commentController.text, ); if (response.statusCode == 200) { await reloadScreen(event, emit); } } } Future sendFinishDay( int diaryID, TimeOfDay? wakeup, TimeOfDay? retire, String? comment, ) async { final formatter = DateFormat('HH:mm'); final wakeupString = formatter.format(DateTime(2022, 1, 1, wakeup!.hour, wakeup.minute)); final retireString = formatter.format(DateTime(2022, 1, 1, retire!.hour, retire.minute)); final Map credentials = { 'wakeup': wakeupString, 'retire': retireString, 'comment': comment ?? " ", }; String url = '${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/finishDay'; var response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json', }, body: json.encode(credentials), encoding: utf8, ); return response; } Future _uploadImages( UploadImages event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; emit(currentState.copyWith(isLoading: true)); await uploadImageToServer( event.image.path, currentState.diary.id, currentState.currentMeal.id, ); await reloadScreen(event, emit); } } Future uploadImageToServer( String imagePath, int diaryID, int mealId, ) async { String url = '${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealId/photo/upload'; var request = http.MultipartRequest('POST', Uri.parse(url)); request.files.add(await http.MultipartFile.fromPath('photo', imagePath)); var client = http.Client(); var streamedResponse = await client.send(request); if (streamedResponse.statusCode == 200) { var response = await http.Response.fromStream(streamedResponse); var imageUrl = response.body; print('Image successfully uploaded'); return imageUrl; } else { throw Exception( 'Failed to upload image. Status code: ${streamedResponse.statusCode}}', ); } } FutureOr _sendModeration( SendModeration event, Emitter emit, ) async { if (state is DiaryStateLoaded) { final response = await _sendModerationData( event.name, event.energyOn100gr, event.proteinsOn100gr, event.fatsOn100gr, event.carbohydratesOn100gr, ); if (response.statusCode == 200) print("Moderation success"); else print("Moderation error"); } } Future _sendModerationData( String name, String energy_on_100gr, String proteins_on_100gr, String fats_on_100gr, String carbohydrates_on_100gr, ) async { final Uri url = Uri.parse( '${dotenv.env['SERVER_ROOT']!}/diary/product_nutritional_values/sendToModeration', ); final Uri serverURL = Uri.parse('${dotenv.env['SERVER_ROOT']!}'); String host = serverURL.host; final Map credentials = { 'name': name, 'energy_on_100gr': energy_on_100gr, 'proteins_on_100gr': proteins_on_100gr, 'fats_on_100gr': fats_on_100gr, 'carbohydrates_on_100gr': carbohydrates_on_100gr, }; var headers = { //'Content-Length': bodyBytes, 'Host': host, }; var response = await http.post( url, headers: headers, body: credentials, encoding: utf8, ); if (response.statusCode == 200) { return response; } else { throw Exception( 'Failed to send a product for moderation ${response.statusCode}', ); } } FutureOr _addComment( AddComment event, Emitter emit, ) async { if (state is DiaryStateLoaded) { var currentState = state as DiaryStateLoaded; var updatedState = currentState.copyWith( commentController: TextEditingController(text: event.comment), ); // Отправляем данные по HTTP маршруту и ждем ответ var response = await sendCommentData( event.comment, currentState.diary.id, ); if (response.statusCode == 200) { emit(updatedState); // Передаем обновленное состояние в emit } } } Future sendCommentData( String comment, int diaryID, ) async { String url = dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/retire/fill'; final Map credentials = { 'comment': comment, }; var response = await http.post( Uri.parse(url), headers: { 'Content-Type': 'application/json', }, body: json.encode(credentials), encoding: utf8, ); return response; } TimeOfDay toTime(String timeStr) { List timeParts = timeStr.split(":"); int hour = int.parse(timeParts[0]); int minute = int.parse(timeParts[1]); TimeOfDay timeOfDay = TimeOfDay(hour: hour, minute: minute); return timeOfDay; } List fillMealList(List meals) { meals.sort(compareTypes); return meals.map((meal) { String title = "Прием пищи ${meal['type']}"; return SelectionPopupModel(id: meal['id'], title: title); }).toList(); } int compareTypes(a, b) { return a['type'].compareTo(b['type']); } List fillPortion() { return [ SelectionPopupModel(id: 0, title: "порций", isSelected: true), SelectionPopupModel(id: 1, title: "грамм"), ]; } } int convertToInt(dynamic value) { if (value is int) { return value; } else if (value is double) { return value.toInt(); } else { throw FormatException('Unexpected value type: ${value.runtimeType}'); } }