Simplify loading of exercise data

While this has more manual steps than I would prefer, it is definitely better
than loading the individual entries and mapping them to an forth
This commit is contained in:
Roland Geider
2022-05-11 00:03:05 +02:00
parent 50d7c6845d
commit f56619939e
19 changed files with 478 additions and 329 deletions

View File

@@ -2,6 +2,8 @@ Start emulator and run
`flutter drive --driver=test_driver/screenshot_driver.dart --target=integration_test/app_test.dart`
This will generate some screenshots and save them to the Play Store metadata folder
See
* <https://github.com/openfoodfacts/smooth-app/issues/217#issuecomment-1092678779>

View File

@@ -88,6 +88,7 @@ class ExerciseBase extends Equatable {
List<Muscle>? musclesSecondary,
List<Equipment>? equipment,
List<ExerciseImage>? images,
List<Exercise>? exercises,
ExerciseCategory? category,
}) {
this.images = images ?? [];
@@ -111,6 +112,10 @@ class ExerciseBase extends Equatable {
this.equipment = equipment;
equipmentIds = equipment.map((e) => e.id).toList();
}
if (exercises != null) {
this.exercises = exercises;
}
}
/// Returns exercises for the given language

View File

@@ -36,7 +36,7 @@ class ExerciseCategory extends Equatable {
@override
String toString() {
return 'Category $id';
return 'Category $id: $name';
}
// Boilerplate

View File

@@ -40,4 +40,9 @@ class Equipment extends Equatable {
@override
List<Object?> get props => [id, name];
@override
String toString() {
return 'Equipment: $id - $name';
}
}

View File

@@ -52,7 +52,7 @@ class Exercise extends Equatable {
final String description;
@JsonKey(ignore: true)
List<Comment> tips = [];
List<Comment> notes = [];
@JsonKey(ignore: true)
List<Alias> alias = [];

View File

@@ -48,4 +48,9 @@ class ExerciseImage {
// Boilerplate
factory ExerciseImage.fromJson(Map<String, dynamic> json) => _$ExerciseImageFromJson(json);
Map<String, dynamic> toJson() => _$ExerciseImageToJson(this);
@override
String toString() {
return 'Image $id: $url';
}
}

View File

@@ -44,4 +44,9 @@ class Muscle extends Equatable {
@override
List<Object?> get props => [id, name, isFront];
@override
String toString() {
return 'Muscle: $id - $name';
}
}

View File

@@ -44,15 +44,10 @@ class ExercisesProvider with ChangeNotifier {
static const CACHE_VERSION = 3;
static const daysToCache = 7;
static const _exerciseInfoUrlPath = 'exerciseinfo';
static const _exerciseBaseUrlPath = 'exercise-base';
static const _exerciseUrlPath = 'exercise';
static const _exerciseBaseInfoUrlPath = 'exercisebaseinfo';
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';
@@ -68,10 +63,6 @@ class ExercisesProvider with ChangeNotifier {
List<Equipment> _equipment = [];
List<Language> _languages = [];
List<Variation> _variations = [];
List<Exercise> _exercises = [];
set exercises(List<Exercise> exercises) {
_exercises = exercises;
}
Filters? _filters;
Filters? get filters => _filters;
@@ -103,7 +94,6 @@ class ExercisesProvider with ChangeNotifier {
ExercisesProvider(this.baseProvider);
List<Exercise> get items => [..._exercises];
List<ExerciseBase> get bases => [..._exerciseBases];
List<ExerciseCategory> get categories => [..._categories];
List<Muscle> get muscles => [..._muscles];
@@ -177,25 +167,9 @@ class ExercisesProvider with ChangeNotifier {
_muscles = [];
_categories = [];
_languages = [];
_exercises = [];
_exerciseBases = [];
}
List<ExerciseBase> findByCategory(ExerciseCategory? category) {
if (category == null) {
return bases;
}
return bases.where((base) => base.category.id == category.id).toList();
}
/// Find exercise by ID
Exercise findExerciseById(int id) {
return _exercises.firstWhere(
(exercise) => exercise.id == id,
orElse: () => throw NoSuchEntryException(),
);
}
/// Find exercise base by ID
ExerciseBase findExerciseBaseById(int id) {
return _exerciseBases.firstWhere(
@@ -326,60 +300,56 @@ class ExercisesProvider with ChangeNotifier {
/// If the exercise is not known locally, it is fetched from the server.
/// This method is called when a workout is first loaded, after that the
/// regular not-async getById method can be used
Future<Exercise> fetchAndSetExercise(int exerciseId) async {
Future<ExerciseBase> fetchAndSetExerciseBase(int exerciseBaseId) async {
try {
return findExerciseById(exerciseId);
return findExerciseBaseById(exerciseBaseId);
} on NoSuchEntryException {
// Get exercise from the server and save to cache
// TODO: do this right (and save to cache)
final exerciseTranslationData = await baseProvider.fetch(
baseProvider.makeUrl(
_exerciseUrlPath,
id: exerciseId,
),
);
final exercise = Exercise.fromJson(exerciseTranslationData);
final exerciseBaseData = await baseProvider.fetch(
baseProvider.makeUrl(_exerciseBaseUrlPath, id: exercise.baseId),
);
final base = ExerciseBase.fromJson(exerciseBaseData);
//setExerciseBaseData(base, [exercise]);
/*
final prefs = await SharedPreferences.getInstance();
final exerciseTranslationData = await baseProvider.fetch(
baseProvider.makeUrl(
_exerciseUrlPath,
id: exerciseId,
),
final baseData = await baseProvider.fetch(
baseProvider.makeUrl(_exerciseBaseInfoUrlPath, id: exerciseBaseId),
);
final exercise = Exercise.fromJson(exerciseTranslationData);
final exerciseBaseData = await baseProvider.fetch(
baseProvider.makeUrl(_exerciseBaseUrlPath, id: exercise.baseId),
);
final newBase = readExerciseBaseFromBaseInfo(baseData);
final base = setExerciseBaseData(ExerciseBase.fromJson(exerciseBaseData), [exercise]);
//exerciseData['exercises'].add(exercise.toJson());
//prefs.setString(PREFS_EXERCISES, json.encode(exerciseData));
//log("Saved exercise '${exercise.name}' to cache.");
*/
return exercise;
// TODO: save to cache. Since we can't easily generate the JSON, perhaps just reload?
_exerciseBases.add(newBase);
return newBase;
}
}
/// Returns the exercise with the given ID
///
/// If the exercise is not known locally, it is fetched from the server.
/// This method is called when a workout is first loaded, after that the
/// regular not-async getById method can be used
Future<ExerciseBase> fetchAndSetExerciseBase(int exerciseBaseId) async {
return findExerciseBaseById(exerciseBaseId);
/// Parses the response from the exercisebaseinfo endpoint and returns
/// a full exercise base
ExerciseBase readExerciseBaseFromBaseInfo(dynamic baseData) {
final category = ExerciseCategory.fromJson(baseData['category']);
final musclesPrimary = baseData['muscles'].map((e) => Muscle.fromJson(e)).toList();
final musclesSecondary = baseData['muscles_secondary'].map((e) => Muscle.fromJson(e)).toList();
final equipment = baseData['equipment'].map((e) => Equipment.fromJson(e)).toList();
final images = baseData['images'].map((e) => ExerciseImage.fromJson(e)).toList();
final List<Exercise> exercises = [];
for (final exerciseData in baseData['exercises']) {
final exercise = Exercise.fromJson(exerciseData);
exercise.alias = exerciseData['aliases']
.map((e) => Alias(exerciseId: exercise.id!, alias: e['alias']))
.toList()
.cast<Alias>();
exercise.notes =
exerciseData['notes'].map((e) => Comment.fromJson(e)).toList().cast<Comment>();
exercises.add(exercise);
}
final exerciseBase = ExerciseBase(
id: baseData['id'],
uuid: baseData['uuid'],
creationDate: null,
//creationDate: toDate(baseData['creation_date']),
musclesSecondary: musclesSecondary.cast<Muscle>(),
muscles: musclesPrimary.cast<Muscle>(),
equipment: equipment.cast<Equipment>(),
category: category,
images: images.cast<ExerciseImage>(),
exercises: exercises);
return exerciseBase;
}
/// Checks the required cache version
@@ -404,74 +374,11 @@ class ExercisesProvider with ChangeNotifier {
}
}
List<ExerciseBase> mapImages(dynamic data, List<ExerciseBase> bases) {
final List<ExerciseImage> images =
data.map<ExerciseImage>((e) => ExerciseImage.fromJson(e)).toList();
for (final b in bases) {
b.images = images.where((image) => image.exerciseBaseId == b.id).toList();
}
return bases;
}
List<ExerciseBase> setBaseData(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();
return base;
});
return bases.toList();
} catch (e) {
rethrow;
}
}
List<dynamic> mapBases(List<ExerciseBase> bases, List<Exercise> exercises) {
try {
List<Exercise> out = [];
for (var base in bases) {
final filteredExercises = exercises.where((e) => e.baseId == base.id);
for (final exercise in filteredExercises) {
exercise.base = base;
base.exercises.add(exercise);
out.add(exercise);
}
}
return [bases, out];
} catch (e) {
rethrow;
}
}
List<Exercise> mapLanguages(List<Exercise> exercises) {
for (final exercise in exercises) {
exercise.languageObj = findLanguageById(exercise.languageId);
}
return exercises;
}
List<Exercise> mapAliases(dynamic data, List<Exercise> exercises) {
final List<Alias> alias = data.map<Alias>((e) => Alias.fromJson(e)).toList();
for (final e in alias) {
exercises.firstWhere((exercise) => exercise.id == e.exerciseId).alias.add(e);
}
return exercises;
}
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;
}
Future<void> fetchAndSetExercises() async {
clear();
//fetchAndSetExerciseBase(9);
// Load exercises from cache, if available
final prefs = await SharedPreferences.getInstance();
await checkExerciseCacheVersion();
@@ -484,18 +391,7 @@ class ExercisesProvider with ChangeNotifier {
cacheData['categories'].forEach((e) => _categories.add(ExerciseCategory.fromJson(e)));
cacheData['languages'].forEach((e) => _languages.add(Language.fromJson(e)));
cacheData['variations'].forEach((e) => _variations.add(Variation.fromJson(e)));
_exercises =
cacheData['exercise-translations'].map<Exercise>((e) => Exercise.fromJson(e)).toList();
_exercises = mapComments(cacheData['exercise-comments'], _exercises);
_exercises = mapAliases(cacheData['exercise-alias'], _exercises);
_exercises = mapLanguages(_exercises);
_exerciseBases = setBaseData(cacheData['bases'], _exercises);
_exerciseBases = mapImages(cacheData['exercise-images'], _exerciseBases);
final out = mapBases(_exerciseBases, _exercises);
_exerciseBases = out[0];
_exercises = out[1];
cacheData['bases'].forEach((e) => _exerciseBases.add(readExerciseBaseFromBaseInfo(e)));
_initFilters();
log("Read ${_exerciseBases.length} exercises from cache. Valid till ${cacheData['expiresIn']}");
@@ -505,11 +401,7 @@ class ExercisesProvider with ChangeNotifier {
// Load categories, muscles, equipment and languages
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'})),
baseProvider.fetch(baseProvider.makeUrl(_exerciseImagesUrlPath, query: {'limit': '1000'})),
baseProvider.fetch(baseProvider.makeUrl(_exerciseBaseInfoUrlPath, query: {'limit': '1000'})),
fetchAndSetCategories(),
fetchAndSetMuscles(),
fetchAndSetEquipment(),
@@ -517,23 +409,9 @@ class ExercisesProvider with ChangeNotifier {
fetchAndSetVariations(),
]);
final exerciseBaseData = data[0]['results'];
final exerciseData = data[1]['results'];
final commentsData = data[2]['results'];
final aliasData = data[3]['results'];
final imageData = data[4]['results'];
// Load exercise
_exercises = exerciseData.map<Exercise>((e) => Exercise.fromJson(e)).toList();
_exercises = mapComments(commentsData, _exercises);
_exercises = mapAliases(aliasData, _exercises);
_exercises = mapLanguages(_exercises);
_exerciseBases = setBaseData(exerciseBaseData, _exercises);
_exerciseBases = mapImages(imageData, _exerciseBases);
final out = mapBases(_exerciseBases, _exercises);
_exerciseBases = out[0];
_exercises = out[1];
_exerciseBases =
exerciseBaseData.map((e) => readExerciseBaseFromBaseInfo(e)).toList().cast<ExerciseBase>();
try {
// Save the result to the cache
@@ -547,12 +425,8 @@ class ExercisesProvider with ChangeNotifier {
'languages': _languages.map((e) => e.toJson()).toList(),
'variations': _variations.map((e) => e.toJson()).toList(),
'bases': exerciseBaseData,
'exercise-translations': exerciseData,
'exercise-comments': commentsData,
'exercise-alias': aliasData,
'exercise-images': imageData,
};
log("Saved ${_exercises.length} exercises to cache. Valid till ${cacheData['expiresIn']}");
log("Saved ${_exerciseBases.length} exercises to cache. Valid till ${cacheData['expiresIn']}");
await prefs.setString(PREFS_EXERCISES, json.encode(cacheData));
_initFilters();
@@ -581,7 +455,7 @@ class ExercisesProvider with ChangeNotifier {
);
// Process the response
return await Future.wait(
return Future.wait(
(result['suggestions'] as List).map<Future<ExerciseBase>>(
(entry) => fetchAndSetExerciseBase(entry['data']['base_id']),
),

View File

@@ -3,6 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:wger/providers/add_exercise_provider.dart';
import 'package:wger/providers/exercises.dart';
import 'package:wger/screens/exercise_screen.dart';
import 'package:wger/widgets/add_exercise/steps/basics.dart';
import 'package:wger/widgets/add_exercise/steps/description.dart';
import 'package:wger/widgets/add_exercise/steps/images.dart';
@@ -42,9 +43,9 @@ class _AddExerciseScreenState extends State<AddExerciseScreen> {
ElevatedButton(
onPressed: () async {
final id = await context.read<AddExerciseProvider>().addExercise();
final exercise = await context.read<ExercisesProvider>().fetchAndSetExercise(id);
final base = await context.read<ExercisesProvider>().fetchAndSetExerciseBase(id);
//Navigator.pushNamed(context, ExerciseDetailScreen.routeName, arguments: exercise);
Navigator.pushNamed(context, ExerciseDetailScreen.routeName, arguments: base);
},
child: Text(AppLocalizations.of(context).save),
)

View File

@@ -101,12 +101,12 @@ class ExerciseDetail extends StatelessWidget {
List<Widget> getNotes(BuildContext context) {
final List<Widget> out = [];
if (_exercise.tips.isNotEmpty) {
if (_exercise.notes.isNotEmpty) {
out.add(Text(
AppLocalizations.of(context).notes,
style: Theme.of(context).textTheme.headline5,
));
for (final e in _exercise.tips) {
for (final e in _exercise.notes) {
out.add(Text(e.comment));
}
out.add(const SizedBox(height: PADDING));

View File

@@ -0,0 +1,87 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:wger/providers/exercises.dart';
import '../../test_data/exercises.dart';
import '../fixtures/fixture_reader.dart';
import '../measurements/measurement_provider_test.mocks.dart';
main() {
late MockWgerBaseProvider mockBaseProvider;
late ExercisesProvider provider;
const String exerciseBaseInfoUrl = 'exercisebaseinfo';
final Uri tExerciseBaseInfoUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$exerciseBaseInfoUrl/9/',
);
final Map<String, dynamic> tExerciseBaseInfoMap = jsonDecode(
fixture('exercises/exercisebaseinfo_response.json'),
);
setUp(() {
mockBaseProvider = MockWgerBaseProvider();
provider = ExercisesProvider(mockBaseProvider);
provider.exerciseBases = getTestExerciseBases();
// Mock base info response
when(
mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 9),
).thenReturn(tExerciseBaseInfoUri);
when(mockBaseProvider.fetch(tExerciseBaseInfoUri))
.thenAnswer((_) => Future.value(tExerciseBaseInfoMap));
});
group('Correctly loads and parses data from the server', () {
test('test that fetchAndSetExerciseBase finds an existing base', () async {
// arrange and act
final base = await provider.fetchAndSetExerciseBase(1);
// assert
verifyNever(provider.baseProvider.fetch(tExerciseBaseInfoUri));
expect(base.id, 1);
});
test('test that fetchAndSetExerciseBase fetches a new base', () async {
// arrange and act
final base = await provider.fetchAndSetExerciseBase(9);
// assert
verify(provider.baseProvider.fetch(tExerciseBaseInfoUri));
expect(base.id, 9);
});
test('Load the readExerciseBaseFromBaseInfo parse method', () {
// arrange
// arrange and act
final base = provider.readExerciseBaseFromBaseInfo(tExerciseBaseInfoMap);
// assert
expect(base.id, 9);
expect(base.uuid, '1b020b3a-3732-4c7e-92fd-a0cec90ed69b');
expect(base.categoryId, 10);
expect(base.equipment.map((e) => e.name), ['Kettlebell']);
expect(base.muscles.map((e) => e.name), [
'Biceps femoris',
'Brachialis',
'Obliquus externus abdominis',
]);
expect(base.musclesSecondary.map((e) => e.name), [
'Anterior deltoid',
'Trapezius',
]);
expect(base.images.map((e) => e.uuid), [
'1f5d2b2f-d4ea-4eeb-9377-56176465e08d',
'ab645585-26ef-4992-a9ec-15425687ece9',
'd8aa5990-bb47-4111-9823-e2fbd98fe07f',
'49a159e1-1e00-409a-81c9-b4d4489fbd67'
]);
});
});
}

View File

@@ -19,6 +19,7 @@ main() {
late ExercisesProvider provider;
const String categoryUrl = 'exercisecategory';
const String exerciseBaseInfoUrl = 'exercisebaseinfo';
const String muscleUrl = 'muscle';
const String equipmentUrl = 'equipment';
const String languageUrl = 'language';
@@ -30,6 +31,12 @@ main() {
path: 'api/v2/$categoryUrl/',
);
final Uri texerciseBaseInfoUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$exerciseBaseInfoUrl/',
);
final Uri tMuscleEntriesUri = Uri(
scheme: 'http',
host: 'localhost',
@@ -59,10 +66,21 @@ main() {
const equipment1 = Equipment(id: 1, name: 'Barbell');
const language1 = Language(id: 1, shortName: 'de', fullName: 'Deutsch');
final Map<String, dynamic> tCategoryMap = jsonDecode(fixture('exercise_category_entries.json'));
final Map<String, dynamic> tMuscleMap = jsonDecode(fixture('exercise_muscles_entries.json'));
final Map<String, dynamic> tEquipmentMap = jsonDecode(fixture('exercise_equipment_entries.json'));
final Map<String, dynamic> tLanguageMap = jsonDecode(fixture('exercise_language_entries.json'));
final Map<String, dynamic> tCategoryMap = jsonDecode(
fixture('exercises/category_entries.json'),
);
final Map<String, dynamic> tMuscleMap = jsonDecode(
fixture('exercises/muscles_entries.json'),
);
final Map<String, dynamic> tEquipmentMap = jsonDecode(
fixture('exercises/equipment_entries.json'),
);
final Map<String, dynamic> tLanguageMap = jsonDecode(
fixture('exercises/language_entries.json'),
);
final Map<String, dynamic> tExerciseBaseInfoMap = jsonDecode(
fixture('exercises/exercisebaseinfo_response.json'),
);
setUp(() {
mockBaseProvider = MockWgerBaseProvider();
@@ -84,6 +102,11 @@ main() {
// Mock languages
when(mockBaseProvider.makeUrl(languageUrl)).thenReturn(tLanguageEntriesUri);
when(mockBaseProvider.fetch(tLanguageEntriesUri)).thenAnswer((_) => Future.value(tLanguageMap));
// Mock base info response
when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl)).thenReturn(texerciseBaseInfoUri);
when(mockBaseProvider.fetch(texerciseBaseInfoUri))
.thenAnswer((_) => Future.value(tExerciseBaseInfoMap));
});
group('findCategoryById()', () {
@@ -181,7 +204,6 @@ main() {
equipment: FilterCategory<Equipment>(title: 'Equipment', items: {}),
);
provider.exercises = data.getTestExercises();
provider.exerciseBases = data.getTestExerciseBases();
});
@@ -290,17 +312,18 @@ main() {
group('Search term', () {
late Uri tSearchByNameUri;
setUp(() {
final String tSearchTerm = 'press';
final String tSearchLanguage = 'en';
Map<String, dynamic> query = {'term': tSearchTerm, 'language': tSearchLanguage};
const String tSearchTerm = 'press';
const String tSearchLanguage = 'en';
final Map<String, dynamic> query = {'term': tSearchTerm, 'language': tSearchLanguage};
tSearchByNameUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$searchExerciseUrl/',
queryParameters: query,
);
final Map<String, dynamic> tSearchResponse =
jsonDecode(fixture('exercise_search_entries.json'));
final Map<String, dynamic> tSearchResponse = jsonDecode(
fixture('exercises/exercise_search_entries.json'),
);
// Mock exercise search
when(

View File

@@ -0,0 +1,187 @@
{
"id": 9,
"uuid": "1b020b3a-3732-4c7e-92fd-a0cec90ed69b",
"category": {
"id": 10,
"name": "Abs"
},
"muscles": [
{
"id": 11,
"name": "Biceps femoris",
"is_front": false,
"image_url_main": "/static/images/muscles/main/muscle-11.svg",
"image_url_secondary": "/static/images/muscles/secondary/muscle-11.svg"
},
{
"id": 13,
"name": "Brachialis",
"is_front": true,
"image_url_main": "/static/images/muscles/main/muscle-13.svg",
"image_url_secondary": "/static/images/muscles/secondary/muscle-13.svg"
},
{
"id": 14,
"name": "Obliquus externus abdominis",
"is_front": true,
"image_url_main": "/static/images/muscles/main/muscle-14.svg",
"image_url_secondary": "/static/images/muscles/secondary/muscle-14.svg"
}
],
"muscles_secondary": [
{
"id": 2,
"name": "Anterior deltoid",
"is_front": true,
"image_url_main": "/static/images/muscles/main/muscle-2.svg",
"image_url_secondary": "/static/images/muscles/secondary/muscle-2.svg"
},
{
"id": 9,
"name": "Trapezius",
"is_front": false,
"image_url_main": "/static/images/muscles/main/muscle-9.svg",
"image_url_secondary": "/static/images/muscles/secondary/muscle-9.svg"
}
],
"equipment": [
{
"id": 10,
"name": "Kettlebell"
}
],
"license": {
"id": 2,
"full_name": "Creative Commons Attribution Share Alike 4",
"short_name": "CC-BY-SA 4",
"url": "https://creativecommons.org/licenses/by-sa/4.0/deed.en"
},
"license_author": "deusinvictus",
"images": [
{
"id": 1,
"uuid": "1f5d2b2f-d4ea-4eeb-9377-56176465e08d",
"exercise_base": 9,
"image": "http://localhost:8000/media/exercise-images/9/1f5d2b2f-d4ea-4eeb-9377-56176465e08d.jpg",
"is_main": true,
"status": "2",
"style": "4"
},
{
"id": 2,
"uuid": "ab645585-26ef-4992-a9ec-15425687ece9",
"exercise_base": 9,
"image": "http://localhost:8000/media/exercise-images/9/ab645585-26ef-4992-a9ec-15425687ece9.jpg",
"is_main": false,
"status": "2",
"style": "4"
},
{
"id": 3,
"uuid": "d8aa5990-bb47-4111-9823-e2fbd98fe07f",
"exercise_base": 9,
"image": "http://localhost:8000/media/exercise-images/9/d8aa5990-bb47-4111-9823-e2fbd98fe07f.jpg",
"is_main": false,
"status": "2",
"style": "4"
},
{
"id": 4,
"uuid": "49a159e1-1e00-409a-81c9-b4d4489fbd67",
"exercise_base": 9,
"image": "http://localhost:8000/media/exercise-images/9/49a159e1-1e00-409a-81c9-b4d4489fbd67.jpg",
"is_main": false,
"status": "2",
"style": "4"
}
],
"exercises": [
{
"id": 345,
"uuid": "c788d643-150a-4ac7-97ef-84643c6419bf",
"aliases": [
{
"id": 3,
"alias": "double handed kettlebell"
},
{
"id": 4,
"alias": "Kettlebell russian style"
}
],
"name": "2 Handed Kettlebell Swing",
"exercise_base": 9,
"description": "<p>Two Handed Russian Style Kettlebell swing</p>\n<p> </p>\n<p><strong style=\"\">Pizza</strong> (Italian: [ˈpittsa], Neapolitan: [ˈpittsə]) is a dish of Italian origin consisting of a usually round, flat base of leavened wheat-based dough topped with tomatoes, cheese, and often various other ingredients (such as anchovies, mushrooms, onions, olives, pineapple, meat, etc.), which is then baked at a high temperature, traditionally in a wood-fired oven.[1] A small pizza is sometimes called a pizzetta. A person who makes pizza is known as a <strong style=\"\">pizzaiolo</strong>.</p>",
"notes": [
{
"id": 134,
"exercise": 345,
"comment": "it's important to do the exercise correctly"
},
{
"id": 135,
"exercise": 345,
"comment": "put a lot of effort into this exercise"
},
{
"id": 136,
"exercise": 345,
"comment": "have fun"
}
],
"creation_date": "2015-08-03",
"language": 2,
"license": 2,
"license_author": "deusinvictus"
},
{
"id": 856,
"uuid": "ed4d1112-1dc5-40b1-8bb7-51d34a123009",
"aliases": [
{
"id": 5,
"alias": "Kettlebell mit zwei Hände"
}
],
"name": "Kettlebell Con Dos Manos",
"exercise_base": 9,
"description": "<p>Cualquier descripción aquí...</p>\n<p>La <em style=\"\"><strong>pizza</strong></em> es una preparación culinaria que consiste en un pan plano, habitualmente de forma circular, elaborado con harina de trigo, levadura, agua y sal (a veces aceite de oliva) que tradicionalmente se cubre con salsa de tomate y mozzarella y se hornea a alta temperatura en un horno de leña.234 El lugar donde se venden pizzas se denomina «pizzería» y al obrador, «pizzero» (<em style=\"\">pizzaiolo</em> en italiano). Aunque se considera que su origen está en la gastronomía italiana,3 particularmente la napolitana,2 su consumo está extendido a casi todos los países del mundo con diversas variantes locales, que incorporan distintos ingredientes para cubrir la masa.1 Junto con la hamburguesa, la pizza está considerada la comida más difundida del mundo,56 como consecuencia de la diáspora italiana que se estableció en América a lo largo del siglo xx principalmente en Nueva York,3 Buenos Aires o Chicago.78</p>",
"notes": [
{
"id": 137,
"exercise": 856,
"comment": "haz el ejercicio correctamente"
},
{
"id": 138,
"exercise": 856,
"comment": "pasatelo bien!"
}
],
"creation_date": "2022-03-14",
"language": 4,
"license": 2,
"license_author": "tester McTestface"
},
{
"id": 855,
"uuid": "2fe5f04b-5c9d-448c-a973-3fad6ddd4f74",
"aliases": [],
"name": "Zweihändiges Kettlebell",
"exercise_base": 9,
"description": "<p>Irgendeine Beschreibung hier...</p>\n<p>Eine <strong style=\"\">Pizza</strong> (Aussprache [ˈpɪtsa], ital. [ˈpitːsa], deutscher Plural: <em style=\"\">die Pizzas</em> oder <em style=\"\">die Pizzen</em>[1]) ist ein vor dem Backen würzig belegtes Fladenbrot aus einfachem Hefeteig aus der italienischen Küche. Die heutige international verbreitete Variante mit Tomatensauce und Käse als Basis stammt vermutlich aus Neapel. 2017 wurde die neapolitanische Kunst des Pizzabäckers (<em style=\"\">Art of Neapolitan Pizzaiuolo</em>) von der UNESCO in die repräsentative Liste des immateriellen Kulturerbes der Menschheit aufgenommen.[2]</p>",
"notes": [
{
"id": 139,
"exercise": 855,
"comment": "mach die Übung richtig"
}
],
"creation_date": "2022-03-14",
"language": 1,
"license": 2,
"license_author": "tester McTestface"
}
],
"variations": 25
}

View File

@@ -2,18 +2,17 @@
// in wger/test/workout/workout_set_form_test.dart.
// Do not manually edit this file.
import 'dart:async' as _i10;
import 'dart:ui' as _i11;
import 'dart:async' as _i9;
import 'dart:ui' as _i10;
import 'package:mockito/mockito.dart' as _i1;
import 'package:wger/models/exercises/base.dart' as _i4;
import 'package:wger/models/exercises/category.dart' as _i5;
import 'package:wger/models/exercises/equipment.dart' as _i6;
import 'package:wger/models/exercises/exercise.dart' as _i3;
import 'package:wger/models/exercises/language.dart' as _i8;
import 'package:wger/models/exercises/muscle.dart' as _i7;
import 'package:wger/models/exercises/base.dart' as _i3;
import 'package:wger/models/exercises/category.dart' as _i4;
import 'package:wger/models/exercises/equipment.dart' as _i5;
import 'package:wger/models/exercises/language.dart' as _i7;
import 'package:wger/models/exercises/muscle.dart' as _i6;
import 'package:wger/providers/base_provider.dart' as _i2;
import 'package:wger/providers/exercises.dart' as _i9;
import 'package:wger/providers/exercises.dart' as _i8;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -28,23 +27,21 @@ import 'package:wger/providers/exercises.dart' as _i9;
class _FakeWgerBaseProvider_0 extends _i1.Fake implements _i2.WgerBaseProvider {
}
class _FakeExercise_1 extends _i1.Fake implements _i3.Exercise {}
class _FakeExerciseBase_1 extends _i1.Fake implements _i3.ExerciseBase {}
class _FakeExerciseBase_2 extends _i1.Fake implements _i4.ExerciseBase {}
class _FakeExerciseCategory_3 extends _i1.Fake implements _i5.ExerciseCategory {
class _FakeExerciseCategory_2 extends _i1.Fake implements _i4.ExerciseCategory {
}
class _FakeEquipment_4 extends _i1.Fake implements _i6.Equipment {}
class _FakeEquipment_3 extends _i1.Fake implements _i5.Equipment {}
class _FakeMuscle_5 extends _i1.Fake implements _i7.Muscle {}
class _FakeMuscle_4 extends _i1.Fake implements _i6.Muscle {}
class _FakeLanguage_6 extends _i1.Fake implements _i8.Language {}
class _FakeLanguage_5 extends _i1.Fake implements _i7.Language {}
/// A class which mocks [ExercisesProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
MockExercisesProvider() {
_i1.throwOnMissingStub(this);
}
@@ -54,81 +51,66 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0()) as _i2.WgerBaseProvider);
@override
set exerciseBases(List<_i4.ExerciseBase>? exercisesBases) =>
set exerciseBases(List<_i3.ExerciseBase>? exercisesBases) =>
super.noSuchMethod(Invocation.setter(#exerciseBases, exercisesBases),
returnValueForMissingStub: null);
@override
set exercises(List<_i3.Exercise>? exercises) =>
super.noSuchMethod(Invocation.setter(#exercises, exercises),
returnValueForMissingStub: null);
@override
List<_i4.ExerciseBase> get filteredExerciseBases =>
List<_i3.ExerciseBase> get filteredExerciseBases =>
(super.noSuchMethod(Invocation.getter(#filteredExerciseBases),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
returnValue: <_i3.ExerciseBase>[]) as List<_i3.ExerciseBase>);
@override
set filteredExerciseBases(List<_i4.ExerciseBase>? newFilteredExercises) =>
set filteredExerciseBases(List<_i3.ExerciseBase>? newFilteredExercises) =>
super.noSuchMethod(
Invocation.setter(#filteredExerciseBases, newFilteredExercises),
returnValueForMissingStub: null);
@override
Map<int, List<_i4.ExerciseBase>> get exerciseBasesByVariation =>
Map<int, List<_i3.ExerciseBase>> get exerciseBasesByVariation =>
(super.noSuchMethod(Invocation.getter(#exerciseBasesByVariation),
returnValue: <int, List<_i4.ExerciseBase>>{})
as Map<int, List<_i4.ExerciseBase>>);
returnValue: <int, List<_i3.ExerciseBase>>{})
as Map<int, List<_i3.ExerciseBase>>);
@override
List<_i3.Exercise> get items => (super.noSuchMethod(Invocation.getter(#items),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
List<_i4.ExerciseBase> get bases =>
List<_i3.ExerciseBase> get bases =>
(super.noSuchMethod(Invocation.getter(#bases),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
returnValue: <_i3.ExerciseBase>[]) as List<_i3.ExerciseBase>);
@override
List<_i5.ExerciseCategory> get categories =>
List<_i4.ExerciseCategory> get categories =>
(super.noSuchMethod(Invocation.getter(#categories),
returnValue: <_i5.ExerciseCategory>[]) as List<_i5.ExerciseCategory>);
returnValue: <_i4.ExerciseCategory>[]) as List<_i4.ExerciseCategory>);
@override
List<_i7.Muscle> get muscles =>
List<_i6.Muscle> get muscles =>
(super.noSuchMethod(Invocation.getter(#muscles),
returnValue: <_i7.Muscle>[]) as List<_i7.Muscle>);
returnValue: <_i6.Muscle>[]) as List<_i6.Muscle>);
@override
List<_i6.Equipment> get equipment =>
List<_i5.Equipment> get equipment =>
(super.noSuchMethod(Invocation.getter(#equipment),
returnValue: <_i6.Equipment>[]) as List<_i6.Equipment>);
returnValue: <_i5.Equipment>[]) as List<_i5.Equipment>);
@override
List<_i8.Language> get languages =>
List<_i7.Language> get languages =>
(super.noSuchMethod(Invocation.getter(#languages),
returnValue: <_i8.Language>[]) as List<_i8.Language>);
returnValue: <_i7.Language>[]) as List<_i7.Language>);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
_i10.Future<void> setFilters(_i9.Filters? newFilters) => (super.noSuchMethod(
Invocation.method(#setFilters, [newFilters]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> setFilters(_i8.Filters? newFilters) =>
(super.noSuchMethod(Invocation.method(#setFilters, [newFilters]),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<void> findByFilters() => (super.noSuchMethod(
Invocation.method(#findByFilters, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> findByFilters() =>
(super.noSuchMethod(Invocation.method(#findByFilters, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
void clear() => super.noSuchMethod(Invocation.method(#clear, []),
returnValueForMissingStub: null);
@override
List<_i4.ExerciseBase> findByCategory(_i5.ExerciseCategory? category) =>
(super.noSuchMethod(Invocation.method(#findByCategory, [category]),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
_i3.Exercise findExerciseById(int? id) =>
(super.noSuchMethod(Invocation.method(#findExerciseById, [id]),
returnValue: _FakeExercise_1()) as _i3.Exercise);
@override
_i4.ExerciseBase findExerciseBaseById(int? id) =>
_i3.ExerciseBase findExerciseBaseById(int? id) =>
(super.noSuchMethod(Invocation.method(#findExerciseBaseById, [id]),
returnValue: _FakeExerciseBase_2()) as _i4.ExerciseBase);
returnValue: _FakeExerciseBase_1()) as _i3.ExerciseBase);
@override
List<_i4.ExerciseBase> findExerciseBasesByVariationId(int? id,
List<_i3.ExerciseBase> findExerciseBasesByVariationId(int? id,
{int? exerciseIdToExclude, int? languageId}) =>
(super.noSuchMethod(
Invocation.method(#findExerciseBasesByVariationId, [
@@ -137,111 +119,84 @@ class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
#exerciseIdToExclude: exerciseIdToExclude,
#languageId: languageId
}),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
returnValue: <_i3.ExerciseBase>[]) as List<_i3.ExerciseBase>);
@override
_i5.ExerciseCategory findCategoryById(int? id) =>
_i4.ExerciseCategory findCategoryById(int? id) =>
(super.noSuchMethod(Invocation.method(#findCategoryById, [id]),
returnValue: _FakeExerciseCategory_3()) as _i5.ExerciseCategory);
returnValue: _FakeExerciseCategory_2()) as _i4.ExerciseCategory);
@override
_i6.Equipment findEquipmentById(int? id) =>
_i5.Equipment findEquipmentById(int? id) =>
(super.noSuchMethod(Invocation.method(#findEquipmentById, [id]),
returnValue: _FakeEquipment_4()) as _i6.Equipment);
returnValue: _FakeEquipment_3()) as _i5.Equipment);
@override
_i7.Muscle findMuscleById(int? id) =>
_i6.Muscle findMuscleById(int? id) =>
(super.noSuchMethod(Invocation.method(#findMuscleById, [id]),
returnValue: _FakeMuscle_5()) as _i7.Muscle);
returnValue: _FakeMuscle_4()) as _i6.Muscle);
@override
_i8.Language findLanguageById(int? id) =>
_i7.Language findLanguageById(int? id) =>
(super.noSuchMethod(Invocation.method(#findLanguageById, [id]),
returnValue: _FakeLanguage_6()) as _i8.Language);
returnValue: _FakeLanguage_5()) as _i7.Language);
@override
_i10.Future<void> fetchAndSetCategories() => (super.noSuchMethod(
Invocation.method(#fetchAndSetCategories, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> fetchAndSetCategories() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetCategories, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<void> fetchAndSetVariations() => (super.noSuchMethod(
Invocation.method(#fetchAndSetVariations, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> fetchAndSetVariations() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetVariations, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<void> fetchAndSetMuscles() => (super.noSuchMethod(
Invocation.method(#fetchAndSetMuscles, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> fetchAndSetMuscles() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetMuscles, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<void> fetchAndSetEquipment() => (super.noSuchMethod(
Invocation.method(#fetchAndSetEquipment, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> fetchAndSetEquipment() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetEquipment, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<void> fetchAndSetLanguages() => (super.noSuchMethod(
Invocation.method(#fetchAndSetLanguages, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i9.Future<void> fetchAndSetLanguages() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetLanguages, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
_i10.Future<_i3.Exercise> fetchAndSetExercise(int? exerciseId) =>
(super.noSuchMethod(Invocation.method(#fetchAndSetExercise, [exerciseId]),
returnValue: Future<_i3.Exercise>.value(_FakeExercise_1()))
as _i10.Future<_i3.Exercise>);
@override
_i10.Future<_i4.ExerciseBase> fetchAndSetExerciseBase(int? exerciseBaseId) =>
_i9.Future<_i3.ExerciseBase> fetchAndSetExerciseBase(int? exerciseBaseId) =>
(super.noSuchMethod(
Invocation.method(#fetchAndSetExerciseBase, [exerciseBaseId]),
returnValue:
Future<_i4.ExerciseBase>.value(_FakeExerciseBase_2()))
as _i10.Future<_i4.ExerciseBase>);
Future<_i3.ExerciseBase>.value(_FakeExerciseBase_1()))
as _i9.Future<_i3.ExerciseBase>);
@override
_i10.Future<void> checkExerciseCacheVersion() => (super.noSuchMethod(
Invocation.method(#checkExerciseCacheVersion, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
_i3.ExerciseBase readExerciseBaseFromBaseInfo(dynamic baseData) =>
(super.noSuchMethod(
Invocation.method(#readExerciseBaseFromBaseInfo, [baseData]),
returnValue: _FakeExerciseBase_1()) as _i3.ExerciseBase);
@override
List<_i4.ExerciseBase> mapImages(
dynamic data, List<_i4.ExerciseBase>? bases) =>
(super.noSuchMethod(Invocation.method(#mapImages, [data, bases]),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
_i9.Future<void> checkExerciseCacheVersion() =>
(super.noSuchMethod(Invocation.method(#checkExerciseCacheVersion, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
List<_i4.ExerciseBase> setBaseData(
dynamic data, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#setBaseData, [data, exercises]),
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
_i9.Future<void> fetchAndSetExercises() =>
(super.noSuchMethod(Invocation.method(#fetchAndSetExercises, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i9.Future<void>);
@override
List<dynamic> mapBases(
List<_i4.ExerciseBase>? bases, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#mapBases, [bases, exercises]),
returnValue: <dynamic>[]) as List<dynamic>);
@override
List<_i3.Exercise> mapLanguages(List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#mapLanguages, [exercises]),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
List<_i3.Exercise> mapAliases(dynamic data, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#mapAliases, [data, exercises]),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
List<_i3.Exercise> mapComments(dynamic data, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#mapComments, [data, exercises]),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
_i10.Future<void> fetchAndSetExercises() => (super.noSuchMethod(
Invocation.method(#fetchAndSetExercises, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
_i10.Future<List<_i4.ExerciseBase>> searchExercise(String? name,
_i9.Future<List<_i3.ExerciseBase>> searchExercise(String? name,
[String? languageCode = r'en']) =>
(super.noSuchMethod(
Invocation.method(#searchExercise, [name, languageCode]),
returnValue:
Future<List<_i4.ExerciseBase>>.value(<_i4.ExerciseBase>[]))
as _i10.Future<List<_i4.ExerciseBase>>);
Future<List<_i3.ExerciseBase>>.value(<_i3.ExerciseBase>[]))
as _i9.Future<List<_i3.ExerciseBase>>);
@override
void addListener(_i11.VoidCallback? listener) =>
void addListener(_i10.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#addListener, [listener]),
returnValueForMissingStub: null);
@override
void removeListener(_i11.VoidCallback? listener) =>
void removeListener(_i10.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
returnValueForMissingStub: null);
@override