Flutter-Ernaehrungsapp/lib/services/statistics.dart

480 lines
17 KiB
Dart

import 'dart:math';
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 '../pages/nav_pages/subpages/progress_page/sub_components/charts/sub_components/tabs/timespan_tabs.dart';
import '../models/food.dart';
import '../models/user.dart';
import '../helper/cast_helper.dart';
import '../helper/format_helper.dart';
class DataService {
static final DataService _instance = DataService._internal();
factory DataService() => _instance;
static DataService get instance => _instance;
DataService._internal() {
// TO RUN TESTS -> COMMENT initBoxes() out -> HIVE behavior is tested in other tests
initBoxes();
}
String progressBoxName = dotenv.env['PROGRESS_BOX'] ?? 'PROGRESS_BOX';
String mainBoxName = dotenv.env['MAIN_BOX'] ?? 'MAIN_BOX';
String statisticsBoxName = dotenv.env['STATISTICS_BOX'] ?? 'STATISTICS_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(progressBoxName);
Box progressBox = Hive.box(statisticsBoxName);
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(progressBoxName));
int timestamp = getTimestampFromNow();
int dayLen = 1;
switch(timespan){
case TimeSpan.day:
getNewFoodAndUpdateBoxByTimestampAndBox(timestamp, Hive.box(progressBoxName));
break;
case TimeSpan.week:
List<int> currentWeek = getTimestampsByTimestampAndTimespan(TimeSpan.week,timestamp);
for(int i = 0;i < currentWeek.length;i++){
getNewFoodAndUpdateBoxByTimestampAndBox(currentWeek[i],Hive.box(progressBoxName));
}
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(progressBoxName));
}
dayLen = currentMonth.length;
break;
}
updateCalculationsAndNotfiyListenersForPorgressStatistics(dayLen);
}
void updateCalculationsAndNotfiyListenersForPorgressStatistics(int dayLen){
eatenCalories.value = getAllEatenCaloriesByBox(Hive.box(progressBoxName));
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);
}
}
bool timestampIsValid(int timestamp){
try{
DateTime.fromMillisecondsSinceEpoch(timestamp*1000);
return true;
}catch(e){
return false;
}
}
List<int> 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<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(mainBoxName);
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){
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(mainBoxName);
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;
}
double getCaloryTargetForOneDay(){
final Box userBox = Hive.box<User>(dotenv.env['USER_BOX'] ?? 'USER');
return userBox.get("USER").kalorien.toDouble();
}
List<double> getAllEatenIngredientsForTodayStatistics(double dayLen){
Box box = Hive.box(progressBoxName);
final Box userBox = Hive.box<User>(dotenv.env['USER_BOX'] ?? 'USER');
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(statisticsBoxName);
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(statisticsBoxName);
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++){
try{
sum += foods[i].calories as int;
}catch(e){
throw const FormatException('please check formats');
}
}
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] ?? [];
}
}