2024-12-07 12:29:34 +00:00
|
|
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
2024-12-07 12:39:11 +00:00
|
|
|
/* Copyright (C) 2024 Marco Groß <mgross@sw-gross.de> */
|
2024-06-09 17:06:10 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
2024-09-06 17:00:25 +00:00
|
|
|
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
2024-10-06 23:44:19 +00:00
|
|
|
import 'package:calorimeter/utils/date_time_helper.dart';
|
2024-06-09 17:06:10 +00:00
|
|
|
import 'package:path_provider/path_provider.dart';
|
2024-06-12 12:42:29 +00:00
|
|
|
import 'package:universal_platform/universal_platform.dart';
|
2024-06-09 17:06:10 +00:00
|
|
|
|
2024-09-04 20:47:32 +00:00
|
|
|
class FoodStorage {
|
|
|
|
static late FoodStorage _instance;
|
2024-06-09 17:06:10 +00:00
|
|
|
late String path;
|
2024-09-09 20:41:48 +00:00
|
|
|
final Map<String, int> _foodLookupDatabase = {};
|
2024-06-09 17:06:10 +00:00
|
|
|
|
2024-09-04 20:47:32 +00:00
|
|
|
FoodStorage._create();
|
|
|
|
|
|
|
|
static Future<FoodStorage> create() async {
|
|
|
|
var storage = FoodStorage._create();
|
2024-06-12 12:42:29 +00:00
|
|
|
|
|
|
|
Directory dir = Directory('');
|
|
|
|
|
|
|
|
if (UniversalPlatform.isDesktop) {
|
|
|
|
dir = await getApplicationCacheDirectory();
|
|
|
|
} else if (UniversalPlatform.isAndroid) {
|
|
|
|
dir = await getApplicationDocumentsDirectory();
|
|
|
|
}
|
|
|
|
|
|
|
|
storage.path = dir.path;
|
2024-06-11 17:05:42 +00:00
|
|
|
_instance = storage;
|
2024-06-09 17:06:10 +00:00
|
|
|
|
2024-06-11 17:05:42 +00:00
|
|
|
return _instance;
|
2024-06-09 17:06:10 +00:00
|
|
|
}
|
|
|
|
|
2024-09-04 20:47:32 +00:00
|
|
|
static FoodStorage getInstance() => _instance;
|
2024-06-11 17:05:42 +00:00
|
|
|
|
2024-09-09 20:41:48 +00:00
|
|
|
Future<List<FoodEntryState>> getEntriesForDate(DateTime date) async {
|
|
|
|
List<FoodEntryState> entries = [];
|
2024-06-09 17:06:10 +00:00
|
|
|
var filePath = '$path/${date.year}/${date.month}/${date.day}';
|
|
|
|
|
|
|
|
var file = File(filePath);
|
|
|
|
var exists = await file.exists();
|
|
|
|
|
|
|
|
if (!exists) return [];
|
|
|
|
|
|
|
|
var lines = await file.readAsLines();
|
|
|
|
|
|
|
|
for (var line in lines) {
|
2024-09-13 20:35:32 +00:00
|
|
|
var fields = line.splitWithIgnore(',', ignoreIn: '"');
|
2024-09-09 20:41:48 +00:00
|
|
|
int mass = 0;
|
|
|
|
int kcalPerMass = 0;
|
|
|
|
|
|
|
|
try {
|
|
|
|
mass = int.parse(fields[2]);
|
|
|
|
} catch (e) {
|
|
|
|
mass = double.parse(fields[2]).toInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
kcalPerMass = int.parse(fields[3]);
|
|
|
|
} catch (e) {
|
|
|
|
kcalPerMass = double.parse(fields[3]).toInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
var entry = FoodEntryState(
|
|
|
|
name: fields[1].replaceAll('"', ""),
|
|
|
|
mass: mass,
|
2024-09-24 15:23:01 +00:00
|
|
|
kcalPer100: kcalPerMass,
|
2024-09-09 20:41:48 +00:00
|
|
|
waitingForNetwork: false,
|
2024-09-25 15:33:40 +00:00
|
|
|
isSelected: false,
|
2024-09-09 20:41:48 +00:00
|
|
|
);
|
2024-06-09 17:06:10 +00:00
|
|
|
entries.add(entry);
|
|
|
|
}
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> writeEntriesForDate(
|
2024-09-09 20:41:48 +00:00
|
|
|
DateTime date, List<FoodEntryState> foodEntries) async {
|
2024-06-09 17:06:10 +00:00
|
|
|
var filePath = '$path/${date.year}/${date.month}/${date.day}';
|
|
|
|
var file = File(filePath);
|
|
|
|
|
|
|
|
var exists = await file.exists();
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
await file.create(recursive: true);
|
|
|
|
}
|
|
|
|
|
|
|
|
String fullString = '';
|
|
|
|
for (var entry in foodEntries) {
|
|
|
|
fullString += '${entry.toString()}\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
await file.writeAsString(fullString);
|
|
|
|
}
|
2024-06-10 01:06:56 +00:00
|
|
|
|
|
|
|
Future<void> updateLimit(double limit) async {
|
|
|
|
var filePath = '$path/limit';
|
|
|
|
var file = File(filePath);
|
|
|
|
|
|
|
|
var exists = await file.exists();
|
|
|
|
if (!exists) {
|
|
|
|
await file.create();
|
|
|
|
}
|
|
|
|
|
|
|
|
await file.writeAsString(limit.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<double> readLimit() async {
|
|
|
|
var filePath = '$path/limit';
|
|
|
|
var file = File(filePath);
|
|
|
|
var exists = await file.exists();
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
return 2000;
|
|
|
|
}
|
|
|
|
|
|
|
|
var line = await file.readAsLines();
|
|
|
|
|
|
|
|
double limit;
|
|
|
|
try {
|
|
|
|
limit = double.parse(line[0]);
|
|
|
|
} catch (e) {
|
|
|
|
limit = 2000;
|
|
|
|
}
|
|
|
|
|
|
|
|
return limit;
|
|
|
|
}
|
2024-06-12 12:42:29 +00:00
|
|
|
|
|
|
|
Future<String> readBrightness() async {
|
|
|
|
var filePath = '$path/brightness';
|
|
|
|
var file = File(filePath);
|
|
|
|
var exists = await file.exists();
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
return 'dark';
|
|
|
|
}
|
|
|
|
|
|
|
|
var line = await file.readAsLines();
|
|
|
|
|
|
|
|
if (line.isEmpty || (line[0] != 'dark' && line[0] != 'light')) {
|
|
|
|
return 'dark';
|
|
|
|
}
|
|
|
|
|
|
|
|
return line[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> writeBrightness(String brightness) async {
|
|
|
|
var filePath = '$path/brightness';
|
|
|
|
var file = File(filePath);
|
|
|
|
var exists = await file.exists();
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
file.create();
|
|
|
|
}
|
|
|
|
|
|
|
|
await file.writeAsString(brightness);
|
|
|
|
}
|
2024-09-04 20:47:32 +00:00
|
|
|
|
|
|
|
Future<void> buildFoodLookupDatabase() async {
|
|
|
|
// get a list of dates of the last 365 days
|
|
|
|
var dates = List<DateTime>.generate(365, (idx) {
|
2024-09-13 20:50:08 +00:00
|
|
|
var durationToPast = Duration(days: idx);
|
2024-10-06 23:44:19 +00:00
|
|
|
return DateTimeHelper.now().subtract(durationToPast);
|
2024-09-04 20:47:32 +00:00
|
|
|
});
|
|
|
|
|
2024-09-13 20:50:08 +00:00
|
|
|
for (var date in dates.reversed) {
|
2024-09-04 20:47:32 +00:00
|
|
|
addFoodEntryToLookupDatabaseFor(date);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> addFoodEntryToLookupDatabaseFor(DateTime date) async {
|
|
|
|
var entriesForDate = await getEntriesForDate(date);
|
|
|
|
|
|
|
|
for (var entry in entriesForDate) {
|
2024-09-24 15:23:01 +00:00
|
|
|
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
2024-09-04 20:47:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 20:41:48 +00:00
|
|
|
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
2024-09-24 15:23:01 +00:00
|
|
|
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
2024-09-04 20:47:32 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 20:41:48 +00:00
|
|
|
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
2024-06-09 17:06:10 +00:00
|
|
|
}
|
2024-09-13 20:35:32 +00:00
|
|
|
|
|
|
|
extension SplitWithIgnore on String {
|
|
|
|
List<String> splitWithIgnore(String delimiter, {String? ignoreIn}) {
|
|
|
|
List<String> parts = [];
|
|
|
|
|
|
|
|
if (ignoreIn == null) {
|
|
|
|
return split(delimiter);
|
|
|
|
}
|
|
|
|
|
|
|
|
int index = -1;
|
|
|
|
int indexCharAfterDelimiter = 0;
|
|
|
|
bool inIgnore = false;
|
|
|
|
for (var rune in runes) {
|
|
|
|
var char = String.fromCharCode(rune);
|
|
|
|
|
|
|
|
index += 1;
|
|
|
|
|
|
|
|
if (char == ignoreIn) {
|
|
|
|
inIgnore = !inIgnore;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inIgnore) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (char == delimiter) {
|
|
|
|
parts.add(substring(indexCharAfterDelimiter, index));
|
|
|
|
indexCharAfterDelimiter = index + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index + 1 == length) {
|
|
|
|
parts.add(substring(indexCharAfterDelimiter, length));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return parts;
|
|
|
|
}
|
|
|
|
}
|