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

423 lines
16 KiB
Dart

import 'dart:developer';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
import 'package:eatvio_app/features/diary/entities/diary_future_diet_day_entity.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/diary_day_model.dart';
import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
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/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DiaryCubit extends Cubit<DiaryState> {
late final DateTime today;
late DateTime selectedDay;
int selectedMeal = 1;
final DiaryRepository diaryRepository;
final GlossaryRepository glossaryRepository;
final ProfileRepository profileRepository;
final DietRepository dietRepository;
final ChatRepository chatRepository;
final SettingsRepository settingsRepository;
int? diaryId;
DiaryCubit({
required this.chatRepository,
required this.diaryRepository,
required this.glossaryRepository,
required this.profileRepository,
required this.dietRepository,
required this.settingsRepository,
}) : super(const DiaryState.loading()) {
today = DateTime.now();
selectedDay = today;
diaryRepository.forceFetchStream.listen((_) {
fetchDiaryV2(needLoading: true, diaryId: diaryId);
});
}
String comment = '';
Future<bool> setSelectedDay(DateTime day) async {
final prevDay = selectedDay;
final prevMeal = selectedMeal;
selectedDay = day;
selectedMeal = 1;
final canSelect = await fetchDiaryV2(needLoading: true);
if (!canSelect) {
selectedDay = prevDay;
selectedMeal = prevMeal;
}
return canSelect;
}
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
final currentState = state;
try {
if (needLoading) {
emit(const DiaryState.loading());
}
if (diaryId != null) {
final diaryDayById = await diaryRepository.getDiaryDay(diaryId);
final diaryEntity = diaryDayById.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
return true;
}
}
final diaryDay = await getDiary();
if (diaryDay == null) {
emit(const DiaryState.error(errorMessage: 'Diary not found'));
return true;
}
final diaryEntity = diaryDay.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
return true;
}
if (diaryDay.typeOfDiary.hasChosenDiet == true && diaryDay.typeOfDiary.diaryState == null) {
setDiaryForDietOnly(diaryDay.toDiaryFutureDayEntity());
return true;
}
emit(currentState);
return false;
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(DiaryState.error(errorMessage: e.toString()));
return false;
}
}
Future<void> updateCurrentDiary() async {
try {
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
final diaryEntity = diaryDay.toEntity();
if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId);
}
} catch (e) {
log(e.toString());
}
}
Future<DiaryDayModel?> getDiary() async {
try {
await diaryRepository.createDiaryDay(day: selectedDay.toISO8601WithTimeZoneOffset());
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
return diaryDay;
} catch (e, st) {
log(e.toString(), stackTrace: st);
if (e is DioException) {
if (e.response?.statusCode == 422) {
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
return diaryDay;
}
}
return null;
}
}
void setSelectedMeal(int meal) {
selectedMeal = meal;
final currentState = state;
if (currentState is DiaryStateLoaded) {
emit(currentState.copyWith(selectedMeal: selectedMeal));
} else if (currentState is DiaryStateLoadedTomorrowWithDiet) {
emit(currentState.copyWith(selectedMeal: selectedMeal));
}
}
Future<bool> setDiaryForDietOnly(DiaryFutureDietDayEntity diaryDay) async {
if (!diaryDay.typeOfDiary.hasChosenDiet) {
return false;
}
emit(const DiaryState.loading());
if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) {
final diet = await dietRepository.getDiet((diaryDay.diet?.id)!);
final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet];
final firstMealPortions = meals?[1]?.values.map((elem) {
return PortionModel(
id: 0,
productName: elem.name,
quantity: elem.quantity,
portion: elem.portion.getValue(),
fromDiet: true,
portionState: PortionState.filledDiet,
);
}).toList();
final secondMealPortions = meals?[2]?.values.map((elem) {
return PortionModel(
id: 0,
productName: elem.name,
quantity: elem.quantity,
portion: elem.portion.getValue(),
fromDiet: true,
portionState: PortionState.filledDiet,
);
}).toList();
final thirdMealPortions = meals?[3]?.values.map((elem) {
return PortionModel(
id: 0,
productName: elem.name,
quantity: elem.quantity,
portion: elem.portion.getValue(),
fromDiet: true,
portionState: PortionState.filledDiet,
);
}).toList();
final fourthMealPortions = meals?[4]?.values.map((elem) {
return PortionModel(
id: 0,
productName: elem.name,
quantity: elem.quantity,
portion: elem.portion.getValue(),
fromDiet: true,
portionState: PortionState.filledDiet,
);
}).toList();
final fifthMealPortions = meals?[5]?.values.map((elem) {
return PortionModel(
id: 0,
productName: elem.name,
quantity: elem.quantity,
portion: elem.portion.getValue(),
fromDiet: true,
portionState: PortionState.filledDiet,
);
}).toList();
final firstMeal = MealModel(id: 0, time: '0', typeNumber: 1, satiety: 0, mood: 0, portions: firstMealPortions ?? [], photos: []);
final secondMeal = MealModel(id: 0, time: '0', typeNumber: 2, satiety: 0, mood: 0, portions: secondMealPortions ?? [], photos: []);
final thirdMeal = MealModel(id: 0, time: '0', typeNumber: 3, satiety: 0, mood: 0, portions: thirdMealPortions ?? [], photos: []);
final fourthMeal = MealModel(id: 0, time: '0', typeNumber: 4, satiety: 0, mood: 0, portions: fourthMealPortions ?? [], photos: []);
final fifthMeal = MealModel(id: 0, time: '0', typeNumber: 5, satiety: 0, mood: 0, portions: fifthMealPortions ?? [], photos: []);
emit(
DiaryState.loadedTomorrowWithDiet(
diary: diaryDay.copyWith(meals: [firstMeal, secondMeal, thirdMeal, fourthMeal, fifthMeal]),
selectedDay: selectedDay,
selectedMeal: selectedMeal,
),
);
return true;
}
return false;
}
Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async {
this.diaryId ??= diaryId;
final isConsultantView = this.diaryId != null;
try {
bool isMoodEnabled = false;
bool isWakeUpAndRetireTimeEnabled = false;
if (diaryId != null) {
} else {
final profileSettings = await settingsRepository.getProfileSettings();
isMoodEnabled = profileSettings.isMoodFillable;
isWakeUpAndRetireTimeEnabled = profileSettings.isBedtimeFillable;
}
ConsultantReviewModel? consultantReview;
int? planDuration;
int? currentPlanDay;
String? planName;
if (diaryDay.chosenPlanId != null) {
final chosenPlanModel = await chatRepository.getChosenPlan(id: diaryDay.chosenPlanId!);
if (!isConsultantView &&
(diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented)) {
await diaryRepository.viewCommentOfDiaryDayByConsultant(diaryId: diaryDay.id);
}
consultantReview = await diaryRepository.getDiaryComments(planId: diaryDay.chosenPlanId!, diaryId: diaryDay.id);
planDuration = chosenPlanModel.plan.duration;
planName = chosenPlanModel.plan.name;
currentPlanDay = consultantReview.currentPlanDay;
}
final nutrition = await getNutrition(diaryDay.id);
final isDiaryFinished = diaryDay.typeOfDiary.diaryState == DiaryStateEnum.finished || diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayFinished;
final isDiaryCommented =
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.commented ||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.lastDayCommented ||
diaryDay.typeOfDiary.diaryState == DiaryStateEnum.reviewedByUser;
emit(
DiaryState.loaded(
diary: diaryDay,
nutrition: nutrition,
selectedDay: selectedDay,
selectedMeal: selectedMeal,
isConsultantView: isConsultantView,
consultantReview: consultantReview,
isDiaryFinished: isDiaryFinished,
isDiaryCommented: isDiaryCommented,
chosenPlanCurrentDay: currentPlanDay,
chosenPlanDuration: planDuration,
chosenPlanName: planName,
isMoodEnabled: isMoodEnabled,
isWakeUpAndRetireEnabled: isWakeUpAndRetireTimeEnabled,
),
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
emit(DiaryState.error(errorMessage: e.toString()));
}
}
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
await updateCurrentDiary();
}
Future<NutritionDataModel> getNutrition(int index) async {
return (await diaryRepository.getNutritionData(index));
}
Future<void> addProductToMeal({
required int diaryId,
required int mealId,
required String productName,
required String portion,
required int quantity,
int? productNutritionalValueId,
}) async {
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
await diaryRepository.addPortionToMeal(
diaryId: diaryId,
mealId: mealId,
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
await updateCurrentDiary();
}
Future<void> updateProductInMeal({
required int diaryId,
required int mealId,
required int portionId,
required String productName,
required String portion,
required int quantity,
int? productNutritionalValueId,
required PortionState portionState,
}) async {
if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) {
await diaryRepository.addPortionToMeal(
diaryId: diaryId,
mealId: mealId,
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
);
} else {
await diaryRepository.updateProductInMeal(
diaryId: diaryId,
mealId: mealId,
portionId: portionId,
productName: productName,
portion: portion,
quantity: quantity,
productNutritionalValueId: productNutritionalValueId,
portionState: portionState,
);
}
await updateCurrentDiary();
}
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
}
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
}
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
}
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
}
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
return await glossaryRepository.getGlossaryListByQuery(query);
}
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
await updateCurrentDiary();
}
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
await updateCurrentDiary();
}
Future<GlossaryModerationModel?> sendProductToModeration({
required String name,
required double energy,
required double proteins,
required double fats,
required double carbohydrates,
}) async {
try {
return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates);
} catch (e, stackTrace) {
log(e.toString(), stackTrace: stackTrace);
}
return null;
}
Future<void> fillTime({required int diaryId, required String time}) async {
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
await updateCurrentDiary();
}
Future<void> fillWakeup({required int diaryId, required String time}) async {
await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
await updateCurrentDiary();
}
Future<void> fillRetire({required int diaryId, required String time}) async {
await diaryRepository.fillRetire(diaryId: diaryId, time: time);
await updateCurrentDiary();
}
Future<void> fillSatiety({required int diaryId, required int satiety}) async {
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
await updateCurrentDiary();
}
Future<void> fillMood({required int diaryId, required int mood}) async {
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
await updateCurrentDiary();
}
void fillComment(String comment) {
this.comment = comment;
}
}
enum DiaryFetchingErrors { canNotCreateFutureDays, dayAlreadyExists }