import 'dart:math'; import 'package:ernaehrung/android/components/meal_page_text/days_component.dart'; import 'package:ernaehrung/android/config/cast_helper.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:hive/hive.dart'; import '../models/food.dart'; import '../models/user.dart'; import 'format_helper.dart'; class StatisticsService { static final StatisticsService _instance = StatisticsService._internal(); factory StatisticsService() => _instance; static StatisticsService get instance => _instance; StatisticsService._internal() { //initBoxes(); } String todayStatisticsBoxName = dotenv.env['STATISTICS_TODAY_BOX'] ?? 'STATISTICS_TODAY_BOX'; String mainStatisticsBoxName = dotenv.env['STATISTICS_MAIN_BOX'] ?? 'STATISTICS_MAIN_BOX'; String progressStatisticsBoxName = dotenv.env['STATISTICS_PROGRESS_BOX'] ?? 'STATISTICS_PROGRESS_BOX'; ValueNotifier eatenCalories = ValueNotifier(0); /* * fat 0 * protein 1 * carbon 2 * fat for one day, week, month, 3 * protein for one day, week, month 4 * carbon for one day, week, month 5 * calories for one day, week, month 6 * */ ValueNotifier> ingredients = ValueNotifier>([0,0,0,0,0,0,0]); ValueNotifier dailyAverageForCurrentWeek = ValueNotifier(0); ValueNotifier> weeklyCaloryRanking = ValueNotifier>([]); ValueNotifier> barChartData = ValueNotifier>([]); initBoxes()async{ Box reducedBox = Hive.box(todayStatisticsBoxName); Box progressBox = Hive.box(progressStatisticsBoxName); putIfKeyNotExists([reducedBox,progressBox], 'FRÜHSTÜCK', []); putIfKeyNotExists([reducedBox,progressBox], 'MITTAGESSEN', []); putIfKeyNotExists([reducedBox,progressBox], 'ABENDESSEN', []); updateStatisticsTodayBoxByTimespan(TimeSpan.day); } void putIfKeyNotExists(List boxes, String key, dynamic value) { for(int i = 0; i < boxes.length;i++){ if (!boxes[i].containsKey(key)) { boxes[i].put(key, value); } } } updateStatisticsTodayBoxByTimespan(TimeSpan timespan){ clearBoxByBox(Hive.box(todayStatisticsBoxName)); int timestamp = getTimestampFromNow(); int dayLen = 1; switch(timespan){ case TimeSpan.day: getNewFoodAndUpdateBoxByTimestampAndBox(timestamp, Hive.box(todayStatisticsBoxName)); break; case TimeSpan.week: List currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp); for(int i = 0;i < currentWeek.length;i++){ getNewFoodAndUpdateBoxByTimestampAndBox(currentWeek[i],Hive.box(todayStatisticsBoxName)); } dayLen = currentWeek.length; break; case TimeSpan.month: List currentMonth = getTimestampsByTimestampAndTimespan(TimeSpan.month,timestamp); for(int i = 0;i < currentMonth.length;i++){ getNewFoodAndUpdateBoxByTimestampAndBox(currentMonth[i],Hive.box(todayStatisticsBoxName)); } dayLen = currentMonth.length; break; } updateCalculationsAndNotfiyListenersForPorgressStatistics(dayLen); } void updateCalculationsAndNotfiyListenersForPorgressStatistics(int dayLen){ eatenCalories.value = getAllEatenCaloriesByBox(Hive.box(todayStatisticsBoxName)); ingredients.value = getAllEatenIngredientsForTodayStatistics(dayLen.roundToDouble()); } void getNewFoodAndUpdateBoxByTimestampAndBox(int timestamp, Box box){ Map> newFood = getFoodMapForGivenTimestampFromMainBox(timestamp); if(newFood.keys.isNotEmpty){ setElementsOfBoxByBox(newFood, box); } } bool timestampIsValid(int timestamp){ try{ DateTime.fromMillisecondsSinceEpoch(timestamp*1000); return true; }catch(e){ return false; } } List getTimestampsByTimestampAndTimespan(TimeSpan timespan, int timestamp) { if(!timestampIsValid(timestamp)){ return []; } int range = timespan == TimeSpan.week ? 7 : 31; int targetWeekday = DateTime.monday; // Example target weekday (Monday) DateTime currentDateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); int currentWeekday = currentDateTime.weekday; int daysToAdd = targetWeekday - currentWeekday; DateTime targetDateTime = currentDateTime.add(Duration(days: daysToAdd)); List timestampsForWeekdays = []; for (int i = 0; i < range; i++) { timestampsForWeekdays.add(targetDateTime.millisecondsSinceEpoch ~/ 1000); targetDateTime = targetDateTime.add(const Duration(days: 1)); } return timestampsForWeekdays; } clearBoxByBox(Box box){ for(int i = 0; i < box.keys.length;i++){ box.put(box.keys.elementAt(i), []); } } setElementsOfBoxByBox(Map> newFood,Box box){ Iterable keys = newFood.keys; for(int i = 0; i < keys.length;i++){ List alreadyExisting = castDynamicToListFood(box.get(keys.elementAt(i))); alreadyExisting.addAll(newFood[keys.elementAt(i)] as Iterable); box.put(keys.elementAt(i), alreadyExisting); } } getDayAsIntFromTimestamp(int timestamp){ DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); return dateTime.day; } Map> getFoodMapForGivenTimestampFromMainBox(int timestamp){ Box box = Hive.box(mainStatisticsBoxName); dynamic matchingTimestamp = getMatchingTimestamp(box, timestamp); if(matchingTimestamp != null){ return castDynamicMap(box.get(matchingTimestamp)); } return >{}; } getMatchingTimestamp(Box box,int newTimestamp){ if(box.keys.isNotEmpty){ for(int i = 0; i < box.keys.length;i++){ int timestamp = box.keys.elementAt(i); if(isDateEqual(newTimestamp, timestamp)){ return timestamp; } } return null; } } getMonthAsIntFromTimestamp(int timestamp){ DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); return dateTime.month; } getYearAsIntFromTimestamp(int timestamp){ DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); return dateTime.year; } isDateEqual(int timestamp1, int timestamp2){ if(!timestampIsValid(timestamp1) || !timestampIsValid(timestamp2)){ return false; } return getDayAsIntFromTimestamp(timestamp1) == getDayAsIntFromTimestamp(timestamp2) && getMonthAsIntFromTimestamp(timestamp1) == getMonthAsIntFromTimestamp(timestamp2) && getYearAsIntFromTimestamp(timestamp1) == getYearAsIntFromTimestamp(timestamp2); } addItemToMainBox(Food value, String mealType){ // Hive.deleteFromDisk(); Box box = Hive.box(mainStatisticsBoxName); DateTime dateTime = DateTime.now(); // DEBUG //DateTime dateTime = getRandomTimestampForTesting(); int newTimestamp = dateTime.millisecondsSinceEpoch.toInt() ~/ 1000; dynamic matchingTimestamp = getMatchingTimestamp(box, newTimestamp); if(matchingTimestamp != null){ newTimestamp = matchingTimestamp; } Map> valueMap = castDynamicMap(box.get(newTimestamp)); List values = []; if(valueMap.containsKey(mealType)){ values = valueMap[mealType]!; } values.add(value); valueMap[mealType] = values; box.put(newTimestamp, valueMap); } getRandomTimestampForTesting(){ DateTime now = DateTime.now(); DateTime startOfWeek = now.subtract(Duration(days: now.weekday - 1)); DateTime endOfWeek = startOfWeek.add(const Duration(days: 6)); Random random = Random(); int randomMilliseconds = random.nextInt(endOfWeek.millisecondsSinceEpoch - startOfWeek.millisecondsSinceEpoch); DateTime randomTimestamp = startOfWeek.add(Duration(milliseconds: randomMilliseconds)); return randomTimestamp; } int getAllEatenCaloriesByBox(Box box){ num sum = 0; for(int i = 0; i < box.keys.length;i++){ for(Food food in box.get(box.keys.elementAt(i))){ sum += food.calories; } } return sum as int; } double getCaloryTargetForOneDay(){ final Box userBox = Hive.box("USER_BOX"); return userBox.get("USER").kalorien.toDouble(); } List getAllEatenIngredientsForTodayStatistics(double dayLen){ Box box = Hive.box(todayStatisticsBoxName); final Box userBox = Hive.box("USER_BOX"); final calorieSummary = userBox.get("USER").kalorien; const double fatPercentPerThousandCalorie = 3.7; const double proteinPercentPerThousandCalorie = 4.5; const double carbsPercentPerThousandCalorie = 12.8; double fatSummary = (calorieSummary / 100) * fatPercentPerThousandCalorie; double carbSummary = (calorieSummary / 100) * carbsPercentPerThousandCalorie; double proteinSummary = (calorieSummary / 100) * proteinPercentPerThousandCalorie; num fat = 0; num protein = 0; num carbs = 0; for(int i = 0; i < box.keys.length;i++){ for(Food food in box.get(box.keys.elementAt(i))){ fat += food.fatg; protein += food.proteing; carbs += food.carbohydrateg; } } return [ fat.roundToDouble(), protein.roundToDouble(), carbs.roundToDouble(), fatSummary * dayLen, proteinSummary * dayLen, carbSummary * dayLen, calorieSummary * dayLen ]; } void updateCalculationsAndNotfiyListenersForTodayStatistics(){ dailyAverageForCurrentWeek.value = getAverageCaloriesForCurrentWeekOnDailyBasis(); weeklyCaloryRanking.value = getWeeklyCaloryRanking(); barChartData.value = getBarChartData(); } void updateProgressBoxValues(){ Box box = Hive.box(progressStatisticsBoxName); box.clear(); int timestamp = getTimestampFromNow(); List currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp); for(int i = 0;i < currentWeek.length;i++){ getNewFoodAndUpdateBoxByTimestampAndBox(currentWeek[i],box); } updateCalculationsAndNotfiyListenersForTodayStatistics(); } int getAverageCaloriesForCurrentWeekOnDailyBasis(){ Box box = Hive.box(progressStatisticsBoxName); return getAllEatenCaloriesByBox(box)~/7; } List getWeeklyCaloryRanking(){ int timestamp = getTimestampFromNow(); List currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp); List allFoodsOfWeek = []; for(int i = 0;i < currentWeek.length;i++){ Map> foodFromMainBox = getFoodMapForGivenTimestampFromMainBox(currentWeek[i]); Iterable keys = foodFromMainBox.keys; if(keys.isNotEmpty){ for(int i = 0; i < keys.length;i++ ){ allFoodsOfWeek.addAll(foodFromMainBox[keys.elementAt(i)] as Iterable); } } } allFoodsOfWeek.sort((a, b) => b.calories - a.calories); return getListOfDistinctElements(allFoodsOfWeek); } int getSumOfCaloriesByFoodList(List foods){ int sum = 0; for(int i = 0; i < foods.length;i++){ try{ sum += foods[i].calories as int; }catch(e){ throw const FormatException('please check formats'); } } return sum; } List getBarChartData(){ int timestamp = getTimestampFromNow(); List currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp); int maxValue = 0; List> result = [ [0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0], [0,0,0], ]; //[[breakfast,lunch,dinner],[breakfast,lunch,dinner] ...] for(int i = 0;i < currentWeek.length;i++){ Map> foodMapFromMainBoxByTimestamp = getFoodMapForGivenTimestampFromMainBox(currentWeek[i]); Iterable keys = foodMapFromMainBoxByTimestamp.keys; if(keys.isNotEmpty){ for(int j = 0; j < keys.length;j++){ int value = getSumOfCaloriesByFoodList(foodMapFromMainBoxByTimestamp[keys.elementAt(j)] ?? []); if(value > maxValue){ maxValue = value; } result[i][j] = value; } } } return [ BarChartGroupData( x: 0, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[0][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[0][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[0][2].toDouble(), // Second segment color ),], ), BarChartGroupData( x: 1, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[1][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[1][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[1][2].toDouble(), // Second segment color ),], ), BarChartGroupData( x: 2, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[2][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[2][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[2][2].toDouble(), // Second segment color ),], ), BarChartGroupData( x: 3, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[3][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[3][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[3][2].toDouble(), // Second segment color ),], showingTooltipIndicators: [0], ), BarChartGroupData( x: 4, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[4][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[4][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[4][2].toDouble(), // Second segment color ),], ), BarChartGroupData( x: 5, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[5][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[5][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[5][2].toDouble(), // Second segment color ),], ), BarChartGroupData( x: 6, barRods: [BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.yellow, toY: result[6][0].toDouble(), // First segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.red, toY: result[6][1].toDouble(), // Second segment color ), BarChartRodData( width: 7.5, // Adjust the width of the bar if needed color: Colors.green, toY: result[6][2].toDouble(), // Second segment color ),], showingTooltipIndicators: [0], ), ]; } getTimestampFromNow(){ DateTime now = DateTime.now(); return now.millisecondsSinceEpoch.toInt() ~/ 1000; } List getMealsOfTodayByMealtype(String mealtype){ int timestamp = getTimestampFromNow(); return getFoodMapForGivenTimestampFromMainBox(timestamp)[mealtype] ?? []; } }