455 lines
16 KiB
Dart
455 lines
16 KiB
Dart
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();
|
|
}
|
|
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<int> eatenCalories = ValueNotifier<int>(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<List<double>> ingredients = ValueNotifier<List<double>>([0,0,0,0,0,0,0]);
|
|
ValueNotifier<int> dailyAverageForCurrentWeek = ValueNotifier<int>(0);
|
|
ValueNotifier<List<Food>> weeklyCaloryRanking = ValueNotifier<List<Food>>([]);
|
|
ValueNotifier<List<BarChartGroupData>> barChartData = ValueNotifier<List<BarChartGroupData>>([]);
|
|
|
|
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<Box> 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<int> 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<int> 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<String,List<Food>> newFood = getFoodMapForGivenTimestampFromMainBox(timestamp);
|
|
if(newFood.keys.isNotEmpty){
|
|
setElementsOfBoxByBox(newFood, box);
|
|
}
|
|
}
|
|
|
|
List<int> 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<int> 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<String,List<Food>> newFood,Box box){
|
|
Iterable<String> keys = newFood.keys;
|
|
for(int i = 0; i < keys.length;i++){
|
|
List<Food> alreadyExisting = castDynamicToListFood(box.get(keys.elementAt(i)));
|
|
alreadyExisting.addAll(newFood[keys.elementAt(i)] as Iterable<Food>);
|
|
box.put(keys.elementAt(i), alreadyExisting);
|
|
}
|
|
}
|
|
|
|
getDayAsIntFromTimestamp(int timestamp){
|
|
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
|
return dateTime.day;
|
|
}
|
|
|
|
Map<String,List<Food>> getFoodMapForGivenTimestampFromMainBox(int timestamp){
|
|
Box box = Hive.box(mainStatisticsBoxName);
|
|
dynamic matchingTimestamp = getMatchingTimestamp(box, timestamp);
|
|
if(matchingTimestamp != null){
|
|
return castDynamicMap(box.get(matchingTimestamp));
|
|
}
|
|
return <String,List<Food>>{};
|
|
}
|
|
|
|
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<String, List<Food>> valueMap = castDynamicMap(box.get(newTimestamp));
|
|
List<Food> 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<double> getAllEatenIngredientsForTodayStatistics(double dayLen){
|
|
Box box = Hive.box(todayStatisticsBoxName);
|
|
final Box userBox = Hive.box<User>("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<int> 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<Food> getWeeklyCaloryRanking(){
|
|
int timestamp = getTimestampFromNow();
|
|
List<int> currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp);
|
|
List<Food> allFoodsOfWeek = [];
|
|
for(int i = 0;i < currentWeek.length;i++){
|
|
Map<String,List<Food>> foodFromMainBox = getFoodMapForGivenTimestampFromMainBox(currentWeek[i]);
|
|
Iterable<String> keys = foodFromMainBox.keys;
|
|
if(keys.isNotEmpty){
|
|
for(int i = 0; i < keys.length;i++ ){
|
|
allFoodsOfWeek.addAll(foodFromMainBox[keys.elementAt(i)] as Iterable<Food>);
|
|
}
|
|
}
|
|
}
|
|
allFoodsOfWeek.sort((a, b) => b.calories - a.calories);
|
|
return getListOfDistinctElements(allFoodsOfWeek);
|
|
}
|
|
|
|
int getSumOfCaloriesByFoodList(List<Food> foods){
|
|
int sum = 0;
|
|
for(int i = 0; i < foods.length;i++){
|
|
sum += foods[i].calories as int;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
List<BarChartGroupData> getBarChartData(){
|
|
int timestamp = getTimestampFromNow();
|
|
List<int> currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp);
|
|
int maxValue = 0;
|
|
List<List<int>> 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<String,List<Food>> foodMapFromMainBoxByTimestamp = getFoodMapForGivenTimestampFromMainBox(currentWeek[i]);
|
|
Iterable<String> 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<Food> getMealsOfTodayByMealtype(String mealtype){
|
|
int timestamp = getTimestampFromNow();
|
|
return getFoodMapForGivenTimestampFromMainBox(timestamp)[mealtype] ?? [];
|
|
|
|
}
|
|
|
|
} |