Extract some logic to their own widgets

This commit is contained in:
Roland Geider
2025-10-06 12:43:05 +02:00
parent 2c963522ad
commit 136d0e0825
5 changed files with 109 additions and 89 deletions

View File

@@ -49,9 +49,11 @@ class NutritionPlansProvider with ChangeNotifier {
List<NutritionalPlan> _plans = [];
List<Ingredient> ingredients = [];
NutritionPlansProvider(this.baseProvider, List<NutritionalPlan> entries,
{IngredientDatabase? database})
: _plans = entries {
NutritionPlansProvider(
this.baseProvider,
List<NutritionalPlan> entries, {
IngredientDatabase? database,
}) : _plans = entries {
this.database = database ?? locator<IngredientDatabase>();
}
@@ -73,8 +75,10 @@ class NutritionPlansProvider with ChangeNotifier {
NutritionalPlan? get currentPlan {
final now = DateTime.now();
return _plans
.where((plan) =>
plan.startDate.isBefore(now) && (plan.endDate == null || plan.endDate!.isAfter(now)))
.where(
(plan) =>
plan.startDate.isBefore(now) && (plan.endDate == null || plan.endDate!.isAfter(now)),
)
.toList()
.sorted((a, b) => b.creationDate.compareTo(a.creationDate))
.firstOrNull;
@@ -114,10 +118,9 @@ 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,
query: {'limit': API_MAX_PAGE_SIZE},
));
final data = await baseProvider.fetchPaginated(
baseProvider.makeUrl(_nutritionalPlansPath, query: {'limit': API_MAX_PAGE_SIZE}),
);
await Future.wait(data.map((e) => fetchAndSetPlanFull(e['id'])).toList());
}
@@ -225,10 +228,7 @@ class NutritionPlansProvider with ChangeNotifier {
/// Adds a meal to a plan
Future<Meal> addMeal(Meal meal, int planId) async {
final plan = findById(planId);
final data = await baseProvider.post(
meal.toJson(),
baseProvider.makeUrl(_mealPath),
);
final data = await baseProvider.post(meal.toJson(), baseProvider.makeUrl(_mealPath));
meal = Meal.fromJson(data);
plan.meals.add(meal);
@@ -269,10 +269,7 @@ class NutritionPlansProvider with ChangeNotifier {
/// Adds a meal item to a meal
Future<MealItem> addMealItem(MealItem mealItem, Meal meal) async {
final data = await baseProvider.post(
mealItem.toJson(),
baseProvider.makeUrl(_mealItemPath),
);
final data = await baseProvider.post(mealItem.toJson(), baseProvider.makeUrl(_mealItemPath));
mealItem = MealItem.fromJson(data);
mealItem.ingredient = await fetchIngredient(mealItem.ingredientId);
@@ -301,6 +298,7 @@ class NutritionPlansProvider with ChangeNotifier {
}
Future<void> clearIngredientCache() async {
ingredients = [];
await database.deleteEverything();
}
@@ -314,9 +312,9 @@ class NutritionPlansProvider with ChangeNotifier {
try {
ingredient = ingredients.firstWhere((e) => e.id == ingredientId);
} on StateError {
final ingredientDb = await (database.select(database.ingredients)
..where((e) => e.id.equals(ingredientId)))
.getSingleOrNull();
final ingredientDb = await (database.select(
database.ingredients,
)..where((e) => e.id.equals(ingredientId))).getSingleOrNull();
// Try to fetch from local db
if (ingredientDb != null) {
@@ -325,8 +323,9 @@ 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)))) {
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 {
@@ -336,7 +335,9 @@ class NutritionPlansProvider with ChangeNotifier {
ingredient = Ingredient.fromJson(data);
ingredients.add(ingredient);
database.into(database.ingredients).insert(
database
.into(database.ingredients)
.insert(
IngredientsCompanion.insert(
id: ingredientId,
data: jsonEncode(data),
@@ -415,10 +416,7 @@ class NutritionPlansProvider with ChangeNotifier {
final plan = findById(meal.planId);
final Log log = Log.fromMealItem(item, plan.id!, meal.id, mealDateTime);
final data = await baseProvider.post(
log.toJson(),
baseProvider.makeUrl(_nutritionDiaryPath),
);
final data = await baseProvider.post(log.toJson(), baseProvider.makeUrl(_nutritionDiaryPath));
log.id = data['id'];
plan.diaryEntries.add(log);
}
@@ -426,19 +424,12 @@ class NutritionPlansProvider with ChangeNotifier {
}
/// Log custom ingredient to nutrition diary
Future<void> logIngredientToDiary(
MealItem mealItem,
int planId, [
DateTime? dateTime,
]) async {
Future<void> logIngredientToDiary(MealItem mealItem, int planId, [DateTime? dateTime]) async {
final plan = findById(planId);
mealItem.ingredient = await fetchIngredient(mealItem.ingredientId);
final log = Log.fromMealItem(mealItem, plan.id!, null, dateTime);
final data = await baseProvider.post(
log.toJson(),
baseProvider.makeUrl(_nutritionDiaryPath),
);
final data = await baseProvider.post(log.toJson(), baseProvider.makeUrl(_nutritionDiaryPath));
log.id = data['id'];
plan.diaryEntries.add(log);
notifyListeners();
@@ -458,11 +449,7 @@ class NutritionPlansProvider with ChangeNotifier {
final data = await baseProvider.fetchPaginated(
baseProvider.makeUrl(
_nutritionDiaryPath,
query: {
'plan': plan.id?.toString(),
'limit': API_MAX_PAGE_SIZE,
'ordering': 'datetime',
},
query: {'plan': plan.id?.toString(), 'limit': API_MAX_PAGE_SIZE, 'ordering': 'datetime'},
),
);

View File

@@ -18,12 +18,11 @@
//import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/screens/configure_plates_screen.dart';
import 'package:wger/widgets/core/settings/exercise_cache.dart';
import 'package:wger/widgets/core/settings/ingredient_cache.dart';
import 'package:wger/widgets/core/settings/theme.dart';
class SettingsPage extends StatelessWidget {
static String routeName = '/SettingsPage';
@@ -33,8 +32,6 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context);
final nutritionProvider = Provider.of<NutritionPlansProvider>(context, listen: false);
final userProvider = Provider.of<UserProvider>(context);
return Scaffold(
appBar: AppBar(title: Text(i18n.settingsTitle)),
@@ -44,50 +41,9 @@ class SettingsPage extends StatelessWidget {
title: Text(i18n.settingsCacheTitle, style: Theme.of(context).textTheme.headlineSmall),
),
const SettingsExerciseCache(),
ListTile(
title: Text(i18n.settingsIngredientCacheDescription),
subtitle: Text('${nutritionProvider.ingredients.length} cached ingredients'),
trailing: IconButton(
key: const ValueKey('cacheIconIngredients'),
icon: const Icon(Icons.delete),
onPressed: () async {
await nutritionProvider.clearIngredientCache();
if (context.mounted) {
final snackBar = SnackBar(content: Text(i18n.settingsCacheDeletedSnackbar));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
},
),
),
const SettingsIngredientCache(),
ListTile(title: Text(i18n.others, style: Theme.of(context).textTheme.headlineSmall)),
ListTile(
title: Text(i18n.themeMode),
trailing: DropdownButton<ThemeMode>(
key: const ValueKey('themeModeDropdown'),
value: userProvider.themeMode,
onChanged: (ThemeMode? newValue) {
if (newValue != null) {
userProvider.setThemeMode(newValue);
}
},
items: ThemeMode.values.map<DropdownMenuItem<ThemeMode>>((ThemeMode value) {
final label = (() {
switch (value) {
case ThemeMode.system:
return i18n.systemMode;
case ThemeMode.light:
return i18n.lightMode;
case ThemeMode.dark:
return i18n.darkMode;
}
})();
return DropdownMenuItem<ThemeMode>(value: value, child: Text(label));
}).toList(),
),
),
const SettingsTheme(),
ListTile(
title: Text(i18n.selectAvailablePlates),
onTap: () {

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/providers/nutrition.dart';
class SettingsIngredientCache extends StatelessWidget {
const SettingsIngredientCache({super.key});
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context);
final nutritionProvider = Provider.of<NutritionPlansProvider>(context, listen: false);
return ListTile(
title: Text(i18n.settingsIngredientCacheDescription),
subtitle: Text('${nutritionProvider.ingredients.length} cached ingredients'),
trailing: IconButton(
key: const ValueKey('cacheIconIngredients'),
icon: const Icon(Icons.delete),
onPressed: () async {
await nutritionProvider.clearIngredientCache();
if (context.mounted) {
final snackBar = SnackBar(content: Text(i18n.settingsCacheDeletedSnackbar));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
},
),
);
}
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/providers/user.dart';
class SettingsTheme extends StatelessWidget {
const SettingsTheme({super.key});
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context);
final userProvider = Provider.of<UserProvider>(context);
return ListTile(
title: Text(i18n.themeMode),
trailing: DropdownButton<ThemeMode>(
key: const ValueKey('themeModeDropdown'),
value: userProvider.themeMode,
onChanged: (ThemeMode? newValue) {
if (newValue != null) {
userProvider.setThemeMode(newValue);
}
},
items: ThemeMode.values.map<DropdownMenuItem<ThemeMode>>((ThemeMode value) {
final label = (() {
switch (value) {
case ThemeMode.system:
return i18n.systemMode;
case ThemeMode.light:
return i18n.lightMode;
case ThemeMode.dark:
return i18n.darkMode;
}
})();
return DropdownMenuItem<ThemeMode>(value: value, child: Text(label));
}).toList(),
),
);
}
}

View File

@@ -30,6 +30,8 @@ import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/widgets/core/settings.dart';
import '../../test_data/exercises.dart';
import '../../test_data/nutritional_plans.dart';
import 'settings_test.mocks.dart';
@GenerateMocks([
@@ -47,6 +49,8 @@ void main() {
setUp(() {
when(mockUserProvider.themeMode).thenReturn(ThemeMode.system);
when(mockExerciseProvider.exercises).thenReturn(getTestExercises());
when(mockNutritionProvider.ingredients).thenReturn([ingredient1, ingredient2]);
});
Widget createSettingsScreen({locale = 'en'}) {