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 'format_helper.dart'; class StatisticsService { static final StatisticsService _instance = StatisticsService._internal(); factory StatisticsService() => _instance; static StatisticsService get instance => _instance; StatisticsService._internal() { initBoxes(); } final String todayStatisticsBoxName = dotenv.env['STATISTICS_TODAY_BOX'] ?? 'STATISTICS_TODAY_BOX'; final String mainStatisticsBoxName = dotenv.env['STATISTICS_MAIN_BOX'] ?? 'STATISTICS_MAIN_BOX'; final String progressStatisticsBoxName = dotenv.env['STATISTICS_PROGRESS_BOX'] ?? 'STATISTICS_PROGRESS_BOX'; ValueNotifier eatenCalories = ValueNotifier(0); ValueNotifier> ingredients = ValueNotifier>([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', []); updateReducedBoxByTimespan(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); } } } updateReducedBoxByTimespan(TimeSpan timeSpan){ clearReducedBoxBeforeUpdate(); int timestamp = getTimestampFromNow(); 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)); } break; case TimeSpan.month: List currentMonth = getTimestampsByTimestampAndTimespan(TimeSpan.month,timestamp); for(int i = 0;i < currentMonth.length;i++){ getNewFoodAndUpdateBoxByTimestampAndBox(currentMonth[i],Hive.box(todayStatisticsBoxName)); } break; } updateCalculationsAndNotfiyListenersForPorgressStatistics(); } void updateCalculationsAndNotfiyListenersForPorgressStatistics(){ eatenCalories.value = getAllEatenCaloriesByBox(Hive.box(todayStatisticsBoxName)); eatenCalories.notifyListeners(); ingredients.value = getAllEatenIngredientsForTodayStatistics(); ingredients.notifyListeners(); } void getNewFoodAndUpdateBoxByTimestampAndBox(int timestamp, Box box){ Map> newFood = getFoodMapForGivenTimestampFromMainBox(timestamp); if(newFood.keys.isNotEmpty){ setElementsOfBoxByBox(newFood, box); } } List getTimestampsByTimestampAndTimespan(TimeSpan timespan, int timestamp) { 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; } clearReducedBoxBeforeUpdate(){ Box box = Hive.box(todayStatisticsBoxName); 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){ 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; } List getAllEatenIngredientsForTodayStatistics(){ Box box = Hive.box(todayStatisticsBoxName); 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()]; } void updateCalculationsAndNotfiyListenersForTodayStatistics(){ dailyAverageForCurrentWeek.value = getAverageCaloriesForCurrentWeekOnDailyBasis(); dailyAverageForCurrentWeek.notifyListeners(); weeklyCaloryRanking.value = getWeeklyCaloryRanking(); weeklyCaloryRanking.notifyListeners(); barChartData.value = getBarChartData(); barChartData.notifyListeners(); } 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++){ sum += foods[i].calories as int; } 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 * 175) ~/ 100; } } } 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 ),], showingTooltipIndicators: [0], groupVertically: true ), 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 ),], showingTooltipIndicators: [0], ), 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 ),], showingTooltipIndicators: [0], ), 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 ),], showingTooltipIndicators: [0], ), 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 ),], showingTooltipIndicators: [0], ), 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; } showItems(){ print("Statistics.dart - showItems() - ITEMS"); //Hive.box(boxName).clear(); print(Hive.box(mainStatisticsBoxName).keys.length); for(int i = 0; i < Hive.box(mainStatisticsBoxName).keys.length; i++){ print(Hive.box(mainStatisticsBoxName).keys.elementAt(i)); print(Hive.box(mainStatisticsBoxName).values.elementAt(i)); //print(Hive.box(boxName).keys.elementAt(i) + " " + Hive.box(boxName).values.elementAt(i)); } } }