Files
eatvio_app/lib/features/diary/presentation/bloc/diary_bloc.dart

675 lines
20 KiB
Dart

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<DiaryEvent, DiaryState> {
final DiaryRepository diaryRepository;
DiaryBloc({required this.diaryRepository})
: super(const DiaryState.loading()) {
on<LoadDiary>(loadDiary);
on<ChangeSatiety>(_changeSatiety); // пользователь нажимает на шкалу сытости
on<ChangeMood>(_changeMood); // пользователь нажимает на шкалу настроения
on<ChangeMealTime>(_changeMealTime); // пользовательяет времяа пиши
on<ChangeMeal>(_changeMeal);
on<ChangePortion>(_changePortion);
on<WakeupEvent>(_changeWakeup); // пользователь выбирает время пробуждения
on<RetireEvent>(_changeRetire); // пользователь выбирает время отхода ко сну
on<ToggleMoodExpandedEvent>(_toggleMood);
on<ToggleWakeupExpandedEvent>(_toggleWakeup);
on<AddPortionEvent>(_addPortion); //пользователь нажимает "Добавить продукт"
on<RemovePortionEvent>(_removePortion);
on<RemoveImageEvent>(
_removeImage,
); // пользователь нажимает на крестик около продукта
on<UploadImages>(_uploadImages); //пользователь нажимает "Добавить фото"
on<AddComment>(_addComment); //пользователь нажимает "Добавить комментарий"
on<ProductInput>(
_inputProduct,
);
on<FinishDay>(_finishDay);
on<SendModeration>(_sendModeration);
on<RefreshEvent>(
reloadScreen,
);
on<ClearInput>(_clearInput);
}
loadDiary(
LoadDiary event,
Emitter<DiaryState> emit,
) async {
emit(const DiaryState.loading());
try {
Map<String, int> data = await diaryRepository.getDiaryID();
final int? diaryID = data['id'];
final DiaryModel diary = await diaryRepository.getDiary(diaryID ?? 45);
final Meal currentMeal = diary.meals[0];
List<SelectionPopupModel> portionList = [
SelectionPopupModel(title: "gr"),
SelectionPopupModel(title: "pcs")
];
List<SelectionPopupModel> 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<DiaryState> 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<int>((value) => convertToInt(value))
.toList();
final consumedChartValues = responseBody['dailyNutritionIntake']
.values
.map<int>((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<DiaryState> emit,
) async {
await reloadScreen(event, emit);
}
reloadScreen(
DiaryEvent event,
Emitter<DiaryState> emit,
) async {
print("Reloading...");
}
Future<void> _changeSatiety(
ChangeSatiety event,
Emitter<DiaryState> 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<void> _changeMood(
ChangeMood event,
Emitter<DiaryState> 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<void> _changeWakeup(
WakeupEvent event,
Emitter<DiaryState> 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<void> _changeRetire(
RetireEvent event,
Emitter<DiaryState> 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<void> _changeMealTime(
ChangeMealTime event,
Emitter<DiaryState> emit,
) async {
if (state is DiaryStateLoaded) {
var currentState = state as DiaryStateLoaded;
var updatedState = currentState.copyWith(mealTimeController: event.time);
emit(updatedState);
}
}
FutureOr<void> _changeMeal(
ChangeMeal event,
Emitter<DiaryState> 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<void> _inputProduct(
ProductInput event,
Emitter<DiaryState> emit,
) async {
if (state is DiaryStateLoaded) {
//var currentState = state as DiaryStateLoaded;
print(await diaryRepository.inputProduct(event.input));
final Map<String, List<Product>> 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<void> _clearInput(
ClearInput event,
Emitter<DiaryState> emit,
) {
if (state is DiaryStateLoaded) {
var currentState = state as DiaryStateLoaded;
DiaryStateLoaded updatedState = currentState.copyWith();
emit(updatedState);
}
}
FutureOr<void> _changePortion(
ChangePortion event,
Emitter<DiaryState> emit,
) {
if (state is DiaryStateLoaded) {
var currentState = state as DiaryStateLoaded;
DiaryStateLoaded updatedState = currentState.copyWith(
portionController: event.value,
);
emit(updatedState);
}
}
FutureOr<void> _toggleMood(
ToggleMoodExpandedEvent event,
Emitter<DiaryState> emit,
) {
if (state is DiaryStateLoaded) {
var currentState = state as DiaryStateLoaded;
emit(currentState.copyWith(moodExpanded: !currentState.moodExpanded));
}
}
FutureOr<void> _toggleWakeup(
ToggleWakeupExpandedEvent event,
Emitter<DiaryState> emit,
) {
if (state is DiaryStateLoaded) {
var currentState = state as DiaryStateLoaded;
emit(currentState.copyWith(wakeupExpanded: !currentState.wakeupExpanded));
}
}
FutureOr<void> _addPortion(
AddPortionEvent event,
Emitter<DiaryState> 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<void> _removePortion(
RemovePortionEvent event,
Emitter<DiaryState> 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<http.Response> 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<void> _removeImage(
RemoveImageEvent event,
Emitter<DiaryState> 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<http.Response> sendRemovePhoto(
String? path,
String? photoID,
int? mealID,
int diaryID,
) async {
String url =
'${dotenv.env['SERVER_ROOT']!}/diary/$diaryID/meal/$mealID/photo/delete';
final Map<String, dynamic> 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<void> _finishDay(FinishDay event, Emitter<DiaryState> 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<http.Response> 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<String, dynamic> 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<void> _uploadImages(
UploadImages event,
Emitter<DiaryState> 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<String> 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<void> _sendModeration(
SendModeration event,
Emitter<DiaryState> 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<http.Response> _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<String, String> 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<void> _addComment(
AddComment event,
Emitter<DiaryState> 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<http.Response> sendCommentData(
String comment,
int diaryID,
) async {
String url = dotenv.env['SERVER_ROOT']! + '/diary/$diaryID/retire/fill';
final Map<String, dynamic> 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<String> 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<SelectionPopupModel> fillMealList(List<dynamic> 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<SelectionPopupModel> 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}');
}
}