mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Improve exercise initial loading and reading from cache
This commit is contained in:
@@ -41,8 +41,10 @@ const DAYS_TO_CACHE = 20;
|
||||
/// Name of the submit button in forms
|
||||
const SUBMIT_BUTTON_KEY_NAME = 'submit-button';
|
||||
|
||||
/// Local Preferences key for exercises
|
||||
/// Local Preferences keys
|
||||
const PREFS_EXERCISES = 'exerciseData';
|
||||
const PREFS_EXERCISE_CACHE_VERSION = 'cacheVersion';
|
||||
const PREFS_INGREDIENTS = 'ingredientData';
|
||||
|
||||
const DEFAULT_ANIMATION_DURATION = Duration(milliseconds: 200);
|
||||
const DEFAULT_ANIMATION_CURVE = Curves.bounceIn;
|
||||
|
||||
@@ -25,11 +25,15 @@ class Comment {
|
||||
@JsonKey(required: true)
|
||||
final int id;
|
||||
|
||||
@JsonKey(required: true, name: 'exercise')
|
||||
final int exerciseId;
|
||||
|
||||
@JsonKey(required: true)
|
||||
final String comment;
|
||||
|
||||
Comment({
|
||||
required this.id,
|
||||
required this.exerciseId,
|
||||
required this.comment,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@ part of 'comment.dart';
|
||||
// **************************************************************************
|
||||
|
||||
Comment _$CommentFromJson(Map<String, dynamic> json) {
|
||||
$checkKeys(json, requiredKeys: const ['id', 'comment']);
|
||||
$checkKeys(json, requiredKeys: const ['id', 'exercise', 'comment']);
|
||||
return Comment(
|
||||
id: json['id'] as int,
|
||||
exerciseId: json['exercise'] as int,
|
||||
comment: json['comment'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'exercise': instance.exerciseId,
|
||||
'comment': instance.comment,
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:wger/models/exercises/alias.dart';
|
||||
import 'package:wger/models/exercises/base.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
import 'package:wger/models/exercises/comment.dart';
|
||||
@@ -60,6 +61,9 @@ class Exercise extends Equatable {
|
||||
@JsonKey(ignore: true)
|
||||
List<Comment> tips = [];
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
List<Alias> alias = [];
|
||||
|
||||
Exercise(
|
||||
{required this.id,
|
||||
required this.baseId,
|
||||
|
||||
@@ -26,8 +26,10 @@ import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/exercises/alias.dart';
|
||||
import 'package:wger/models/exercises/base.dart';
|
||||
import 'package:wger/models/exercises/category.dart';
|
||||
import 'package:wger/models/exercises/comment.dart';
|
||||
import 'package:wger/models/exercises/equipment.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/exercises/language.dart';
|
||||
@@ -38,14 +40,17 @@ class ExercisesProvider with ChangeNotifier {
|
||||
final WgerBaseProvider baseProvider;
|
||||
|
||||
static const EXERCISE_CACHE_DAYS = 7;
|
||||
static const CACHE_VERSION = 2;
|
||||
|
||||
static const _exerciseInfoUrlPath = 'exerciseinfo';
|
||||
static const _exerciseBaseUrlPath = 'exercise-base';
|
||||
static const _exerciseUrlPath = 'exercise';
|
||||
static const _exerciseSearchPath = 'exercise/search';
|
||||
|
||||
static const _exerciseVariationsUrlPath = 'variation';
|
||||
static const _exerciseCommentUrlPath = 'exercisecomment';
|
||||
static const _exerciseImagesUrlPath = 'exerciseimage';
|
||||
static const _exerciseAliasUrlPath = 'exercisealias';
|
||||
static const _categoriesUrlPath = 'exercisecategory';
|
||||
static const _musclesUrlPath = 'muscle';
|
||||
static const _equipmentUrlPath = 'equipment';
|
||||
@@ -260,7 +265,7 @@ class ExercisesProvider with ChangeNotifier {
|
||||
baseProvider.makeUrl(_exerciseBaseUrlPath, id: exercise.baseId),
|
||||
);
|
||||
final base = ExerciseBase.fromJson(exerciseBaseData);
|
||||
setExerciseBaseData(base, [exercise]);
|
||||
//setExerciseBaseData(base, [exercise]);
|
||||
|
||||
/*
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -288,21 +293,62 @@ class ExercisesProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function that sets different objects such as category, etc.
|
||||
ExerciseBase setExerciseBaseData(ExerciseBase base, List<Exercise> exercises) {
|
||||
base.category = findCategoryById(base.categoryId);
|
||||
base.muscles = base.musclesIds.map((e) => findMuscleById(e)).toList();
|
||||
base.musclesSecondary = base.musclesSecondaryIds.map((e) => findMuscleById(e)).toList();
|
||||
base.equipment = base.equipmentIds.map((e) => findEquipmentById(e)).toList();
|
||||
List<ExerciseBase> mapBases(dynamic data, List<Exercise> exercises) {
|
||||
try {
|
||||
final bases = data.map<ExerciseBase>((e) {
|
||||
final base = ExerciseBase.fromJson(e);
|
||||
base.category = findCategoryById(base.categoryId);
|
||||
base.muscles = base.musclesIds.map((e) => findMuscleById(e)).toList();
|
||||
base.musclesSecondary = base.musclesSecondaryIds.map((e) => findMuscleById(e)).toList();
|
||||
base.equipment = base.equipmentIds.map((e) => findEquipmentById(e)).toList();
|
||||
|
||||
exercises.forEach((e) {
|
||||
e.base = base;
|
||||
e.language = findLanguageById(e.languageId);
|
||||
exercises.forEach((e) {
|
||||
e.base = base;
|
||||
e.language = findLanguageById(e.languageId);
|
||||
});
|
||||
base.exercises = exercises;
|
||||
return base;
|
||||
});
|
||||
return bases.toList();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
print('********************');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
List<Exercise> mapAliases(dynamic data, List<Exercise> exercises) {
|
||||
List<Alias> alias = data.map<Alias>((e) => Alias.fromJson(e)).toList();
|
||||
alias.forEach((e) {
|
||||
exercises.firstWhere((exercise) => exercise.id == e.exerciseId).alias.add(e);
|
||||
});
|
||||
base.exercises = [];
|
||||
base.exercises = exercises;
|
||||
return exercises;
|
||||
}
|
||||
|
||||
return base;
|
||||
List<Exercise> mapComments(dynamic data, List<Exercise> exercises) {
|
||||
List<Comment> comments = data.map<Comment>((e) => Comment.fromJson(e)).toList();
|
||||
comments.forEach((e) {
|
||||
exercises.firstWhere((exercise) => exercise.id == e.exerciseId).tips.add(e);
|
||||
});
|
||||
return exercises;
|
||||
}
|
||||
|
||||
/// Checks the required cache version
|
||||
///
|
||||
/// This is needed since the content of the exercise cache can change and we need
|
||||
/// to invalidate it as a result
|
||||
checkExerciseCacheVersion() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.containsKey(PREFS_EXERCISE_CACHE_VERSION)) {
|
||||
final cacheVersion = prefs.getInt(PREFS_EXERCISE_CACHE_VERSION);
|
||||
if (cacheVersion! != CACHE_VERSION) {
|
||||
prefs.remove(PREFS_EXERCISES);
|
||||
}
|
||||
prefs.setInt(PREFS_EXERCISE_CACHE_VERSION, CACHE_VERSION);
|
||||
} else {
|
||||
prefs.remove(PREFS_EXERCISES);
|
||||
prefs.setInt(PREFS_EXERCISE_CACHE_VERSION, CACHE_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchAndSetExercises() async {
|
||||
@@ -311,6 +357,7 @@ class ExercisesProvider with ChangeNotifier {
|
||||
print(Intl.shortLocale(Intl.getCurrentLocale()));
|
||||
print('---------');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await checkExerciseCacheVersion();
|
||||
|
||||
if (prefs.containsKey(PREFS_EXERCISES)) {
|
||||
final cacheData = json.decode(prefs.getString(PREFS_EXERCISES)!);
|
||||
@@ -319,14 +366,12 @@ class ExercisesProvider with ChangeNotifier {
|
||||
cacheData['muscles'].forEach((e) => _muscles.add(Muscle.fromJson(e)));
|
||||
cacheData['categories'].forEach((e) => _categories.add(ExerciseCategory.fromJson(e)));
|
||||
cacheData['languages'].forEach((e) => _languages.add(Language.fromJson(e)));
|
||||
cacheData['exercise-translations'].forEach((e) => _exercises.add(Exercise.fromJson(e)));
|
||||
cacheData['bases'].forEach((e) {
|
||||
var base = setExerciseBaseData(
|
||||
ExerciseBase.fromJson(e),
|
||||
_exercises.where((element) => element.baseId == e['id']).toList(),
|
||||
);
|
||||
_exerciseBases.add(base);
|
||||
});
|
||||
|
||||
List<Exercise> exercises =
|
||||
cacheData['exercise-translations'].map<Exercise>((e) => Exercise.fromJson(e)).toList();
|
||||
exercises = mapComments(cacheData['exercise-comments'], exercises);
|
||||
_exercises = mapAliases(cacheData['exercise-alias'], exercises);
|
||||
_exerciseBases = mapBases(cacheData['bases'], _exercises);
|
||||
_initFilters();
|
||||
log("Read ${_exerciseBases.length} exercises from cache. Valid till ${cacheData['expiresIn']}");
|
||||
return;
|
||||
@@ -334,44 +379,28 @@ class ExercisesProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
// Load categories, muscles, equipment and languages
|
||||
await Future.wait([
|
||||
final data = await Future.wait<dynamic>([
|
||||
baseProvider.fetch(baseProvider.makeUrl(_exerciseBaseUrlPath, query: {'limit': '1000'})),
|
||||
baseProvider.fetch(baseProvider.makeUrl(_exerciseUrlPath, query: {'limit': '1000'})),
|
||||
baseProvider.fetch(baseProvider.makeUrl(_exerciseCommentUrlPath, query: {'limit': '1000'})),
|
||||
baseProvider.fetch(baseProvider.makeUrl(_exerciseAliasUrlPath, query: {'limit': '1000'})),
|
||||
fetchAndSetCategories(),
|
||||
fetchAndSetMuscles(),
|
||||
fetchAndSetEquipment(),
|
||||
fetchAndSetLanguages(),
|
||||
]);
|
||||
final exerciseBaseData = data[0]['results'];
|
||||
final exerciseData = data[1]['results'];
|
||||
final commentsData = data[2]['results'];
|
||||
final aliasData = data[3]['results'];
|
||||
|
||||
final exerciseBaseData = await baseProvider.fetch(
|
||||
baseProvider.makeUrl(_exerciseBaseUrlPath, query: {'limit': '1000'}),
|
||||
);
|
||||
|
||||
final exerciseTranslationData = await baseProvider.fetch(
|
||||
baseProvider.makeUrl(
|
||||
_exerciseUrlPath,
|
||||
query: {'limit': '1000'},
|
||||
),
|
||||
);
|
||||
|
||||
List<Exercise> exerciseTranslation = exerciseTranslationData['results'].map<Exercise>((e) {
|
||||
return Exercise.fromJson(e);
|
||||
}).toList();
|
||||
|
||||
for (var e in exerciseBaseData['results']) {
|
||||
var base = setExerciseBaseData(
|
||||
ExerciseBase.fromJson(e),
|
||||
exerciseTranslation.where((element) => element.baseId == e['id']).toList(),
|
||||
);
|
||||
_exerciseBases.add(base);
|
||||
}
|
||||
// Load exercise
|
||||
List<Exercise> exercises = exerciseData.map<Exercise>((e) => Exercise.fromJson(e)).toList();
|
||||
exercises = mapComments(commentsData, exercises);
|
||||
_exercises = mapAliases(aliasData, exercises);
|
||||
_exerciseBases = mapBases(exerciseBaseData, _exercises);
|
||||
|
||||
try {
|
||||
List<Exercise> exerciseTranslations = [];
|
||||
_exerciseBases.forEach((base) {
|
||||
base.exercises.forEach((exercise) {
|
||||
exerciseTranslations.add(exercise);
|
||||
});
|
||||
});
|
||||
|
||||
// Save the result to the cache
|
||||
final cacheData = {
|
||||
'date': DateTime.now().toIso8601String(),
|
||||
@@ -380,8 +409,10 @@ class ExercisesProvider with ChangeNotifier {
|
||||
'categories': _categories.map((e) => e.toJson()).toList(),
|
||||
'muscles': _muscles.map((e) => e.toJson()).toList(),
|
||||
'languages': _languages.map((e) => e.toJson()).toList(),
|
||||
'exercise-translations': exerciseTranslations.map((e) => e.toJson()).toList(),
|
||||
'bases': _exerciseBases.map((e) => e.toJson()).toList(),
|
||||
'bases': exerciseBaseData,
|
||||
'exercise-translations': exerciseData,
|
||||
'exercise-comments': commentsData,
|
||||
'exercise-alias': aliasData,
|
||||
};
|
||||
log("Saved ${_exercises.length} exercises from cache. Valid till ${cacheData['expiresIn']}");
|
||||
|
||||
@@ -391,6 +422,8 @@ class ExercisesProvider with ChangeNotifier {
|
||||
} on MissingRequiredKeysException catch (error) {
|
||||
log(error.missingKeys.toString());
|
||||
throw (error);
|
||||
} catch (e) {
|
||||
throw (e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/exceptions/no_such_entry_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/models/nutrition/ingredient.dart';
|
||||
import 'package:wger/models/nutrition/log.dart';
|
||||
import 'package:wger/models/nutrition/meal.dart';
|
||||
@@ -68,7 +69,10 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
NutritionalPlan findById(int id) {
|
||||
return _plans.firstWhere((plan) => plan.id == id);
|
||||
return _plans.firstWhere(
|
||||
(plan) => plan.id == id,
|
||||
orElse: () => throw NoSuchEntryException(),
|
||||
);
|
||||
}
|
||||
|
||||
Meal? findMealById(int id) {
|
||||
@@ -120,7 +124,7 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
NutritionalPlan plan;
|
||||
try {
|
||||
plan = findById(planId);
|
||||
} on StateError catch (e) {
|
||||
} on NoSuchEntryException catch (e) {
|
||||
plan = await fetchAndSetPlanSparse(planId);
|
||||
}
|
||||
|
||||
@@ -280,8 +284,8 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
Future<void> fetchIngredientsFromCache() async {
|
||||
// Load exercises from cache, if available
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.containsKey('ingredientData')) {
|
||||
final ingredientData = json.decode(prefs.getString('ingredientData')!);
|
||||
if (prefs.containsKey(PREFS_INGREDIENTS)) {
|
||||
final ingredientData = json.decode(prefs.getString(PREFS_INGREDIENTS)!);
|
||||
if (DateTime.parse(ingredientData['expiresIn']).isAfter(DateTime.now())) {
|
||||
ingredientData['ingredients'].forEach((e) => _ingredients.add(Ingredient.fromJson(e)));
|
||||
log("Read ${ingredientData['ingredients'].length} ingredients from cache. Valid till ${ingredientData['expiresIn']}");
|
||||
@@ -295,7 +299,7 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
'expiresIn': DateTime.now().add(Duration(days: DAYS_TO_CACHE)).toIso8601String(),
|
||||
'ingredients': []
|
||||
};
|
||||
prefs.setString('ingredientData', json.encode(ingredientData));
|
||||
prefs.setString(PREFS_INGREDIENTS, json.encode(ingredientData));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user