adjust older code for 'current plan' and showing weight during plan

This commit is contained in:
Dieter Plaetinck
2025-06-27 22:43:24 +02:00
parent 7986df342a
commit d1cfce7526
2 changed files with 68 additions and 23 deletions

View File

@@ -66,13 +66,30 @@ class NutritionPlansProvider with ChangeNotifier {
ingredients = [];
}
/// Returns the current active nutritional plan. At the moment this is just
/// the latest, but this might change in the future.
/// Returns the current active nutritional plan.
/// A plan is considered active if:
/// - Its start date is before now
/// - Its end date is after now or not set
/// If multiple plans match these criteria, the one with the most recent creation date is returned.
NutritionalPlan? get currentPlan {
if (_plans.isNotEmpty) {
return _plans.first;
if (_plans.isEmpty) {
return null;
}
return null;
final now = DateTime.now();
final activePlans = _plans.where((plan) {
final isAfterStart = plan.startDate.isBefore(now);
final isBeforeEnd = plan.endDate == null || plan.endDate!.isAfter(now);
return isAfterStart && isBeforeEnd;
}).toList();
if (activePlans.isEmpty) {
return null;
}
// Sort by creation date (newest first) and return the first one
activePlans.sort((a, b) => b.creationDate.compareTo(a.creationDate));
return activePlans.first;
}
NutritionalPlan findById(int id) {
@@ -109,7 +126,8 @@ class NutritionPlansProvider with ChangeNotifier {
/// Fetches and sets all plans fully, i.e. with all corresponding child objects
Future<void> fetchAndSetAllPlansFull() async {
final data = await baseProvider.fetchPaginated(baseProvider.makeUrl(_nutritionalPlansPath));
final data = await baseProvider
.fetchPaginated(baseProvider.makeUrl(_nutritionalPlansPath));
await Future.wait(data.map((e) => fetchAndSetPlanFull(e['id'])).toList());
}
@@ -170,7 +188,8 @@ class NutritionPlansProvider with ChangeNotifier {
// Logs
await fetchAndSetLogs(plan);
for (final meal in meals) {
meal.diaryEntries = plan.diaryEntries.where((e) => e.mealId == meal.id).toList();
meal.diaryEntries =
plan.diaryEntries.where((e) => e.mealId == meal.id).toList();
}
// ... and done
@@ -204,7 +223,8 @@ class NutritionPlansProvider with ChangeNotifier {
_plans.removeAt(existingPlanIndex);
notifyListeners();
final response = await baseProvider.deleteRequest(_nutritionalPlansPath, id);
final response =
await baseProvider.deleteRequest(_nutritionalPlansPath, id);
if (response.statusCode >= 400) {
_plans.insert(existingPlanIndex, existingPlan);
@@ -284,7 +304,8 @@ class NutritionPlansProvider with ChangeNotifier {
notifyListeners();
// Try to delete
final response = await baseProvider.deleteRequest(_mealItemPath, mealItem.id!);
final response =
await baseProvider.deleteRequest(_mealItemPath, mealItem.id!);
if (response.statusCode >= 400) {
meal.mealItems.insert(mealItemIndex, existingMealItem);
notifyListeners();
@@ -299,7 +320,8 @@ class NutritionPlansProvider with ChangeNotifier {
/// Fetch and return an ingredient
///
/// If the ingredient is not known locally, it is fetched from the server
Future<Ingredient> fetchIngredient(int ingredientId, {IngredientDatabase? database}) async {
Future<Ingredient> fetchIngredient(int ingredientId,
{IngredientDatabase? database}) async {
database ??= this.database;
Ingredient ingredient;
@@ -317,9 +339,11 @@ class NutritionPlansProvider with ChangeNotifier {
_logger.info("Loaded ingredient '${ingredient.name}' from db cache");
// Prune old entries
if (DateTime.now()
.isAfter(ingredientDb.lastFetched.add(const Duration(days: DAYS_TO_CACHE)))) {
(database.delete(database.ingredients)..where((i) => i.id.equals(ingredientId))).go();
if (DateTime.now().isAfter(ingredientDb.lastFetched
.add(const Duration(days: DAYS_TO_CACHE)))) {
(database.delete(database.ingredients)
..where((i) => i.id.equals(ingredientId)))
.go();
}
} else {
final data = await baseProvider.fetch(
@@ -347,7 +371,9 @@ class NutritionPlansProvider with ChangeNotifier {
final ingredientDb = await database.select(database.ingredients).get();
_logger.info('Read ${ingredientDb.length} ingredients from db cache');
if (ingredientDb.isNotEmpty) {
ingredients = ingredientDb.map((e) => Ingredient.fromJson(jsonDecode(e.data))).toList();
ingredients = ingredientDb
.map((e) => Ingredient.fromJson(jsonDecode(e.data)))
.toList();
}
}

View File

@@ -21,11 +21,16 @@ List<Widget> getOverviewWidgets(
height: 220,
child: MeasurementChartWidgetFl(raw, unit, avgs: avg),
),
if (avg.isNotEmpty) MeasurementOverallChangeWidget(avg.first, avg.last, unit),
if (avg.isNotEmpty)
MeasurementOverallChangeWidget(avg.first, avg.last, unit),
const SizedBox(height: 8),
];
}
// TODO(dieter): i'm not sure if this handles well the case where weights were not logged consistently
// e.g. if the plan runs for a month, but the first point is after 3 weeks.
// and the last (non-included) point was *right* before the startDate.
// wouldn't it be better to interpolate the missing points?
List<Widget> getOverviewWidgetsSeries(
String name,
List<MeasurementChartEntry> entriesAll,
@@ -35,7 +40,10 @@ List<Widget> getOverviewWidgetsSeries(
BuildContext context,
) {
final monthAgo = DateTime.now().subtract(const Duration(days: 30));
final showPlan = plan != null && entriesAll.any((e) => e.date.isAfter(plan.creationDate));
final showPlan = plan != null &&
entriesAll.any((e) =>
e.date.isAfter(plan.startDate) &&
(plan.endDate == null || e.date.isBefore(plan.endDate!)));
return [
...getOverviewWidgets(
@@ -47,9 +55,18 @@ List<Widget> getOverviewWidgetsSeries(
),
if (showPlan)
...getOverviewWidgets(
AppLocalizations.of(context).chartDuringPlanTitle(name, plan.description),
entriesAll.where((e) => e.date.isAfter(plan.creationDate)).toList(),
entries7dAvg.where((e) => e.date.isAfter(plan.creationDate)).toList(),
AppLocalizations.of(context)
.chartDuringPlanTitle(name, plan.description),
entriesAll
.where((e) =>
e.date.isAfter(plan.startDate) &&
(plan.endDate == null || e.date.isBefore(plan.endDate!)))
.toList(),
entries7dAvg
.where((e) =>
e.date.isAfter(plan.startDate) &&
(plan.endDate == null || e.date.isBefore(plan.endDate!)))
.toList(),
unit,
context,
),
@@ -58,13 +75,15 @@ List<Widget> getOverviewWidgetsSeries(
// then let's show a separate chart just focusing on the last 30 days,
// if there is data for it.
if (entriesAll.isNotEmpty &&
entriesAll.first.date.isBefore(entriesAll.last.date.subtract(const Duration(days: 75))) &&
entriesAll.first.date.isBefore(
entriesAll.last.date.subtract(const Duration(days: 75))) &&
(plan == null ||
(showPlan &&
entriesAll
.firstWhere((e) => e.date.isAfter(plan.creationDate))
.firstWhere((e) => e.date.isAfter(plan.startDate))
.date
.isBefore(entriesAll.last.date.subtract(const Duration(days: 30))))) &&
.isBefore(entriesAll.last.date
.subtract(const Duration(days: 30))))) &&
entriesAll.any((e) => e.date.isAfter(monthAgo)))
...getOverviewWidgets(
AppLocalizations.of(context).chart30DaysTitle(name),
@@ -92,7 +111,7 @@ List<Widget> getOverviewWidgetsSeries(
];
}
// return the raw and average meaasurements for a "sensible range"
// return the raw and average measurements for a "sensible range"
// a sensible range is something relatively recent, which is most relevant
// for the user to track their progress, but a range should always include
// at least 5 points, and if not we chose a bigger one.