Workout logs and settings now have a reference to an exercise base

This puts this code in sync with the backend and is logically better, since
the translations can be displayed when needed and are not hard coded
This commit is contained in:
Roland Geider
2022-05-10 16:53:44 +02:00
parent 61cdf72798
commit f834950cec
34 changed files with 361 additions and 294 deletions

View File

@@ -201,7 +201,7 @@ void main() {
creationDate: DateTime(2021, 1, 15),
name: 'Squats',
description: 'add clever text',
base: tBase1,
baseId: tBase1,
language: tLanguage1,
);
@@ -211,7 +211,7 @@ void main() {
creationDate: DateTime(2021, 1, 15),
name: 'Bench press',
description: 'add clever text',
base: tBase1,
baseId: tBase1,
language: tLanguage1,
);
@@ -221,7 +221,7 @@ void main() {
creationDate: DateTime(2021, 1, 15),
name: 'deadLift',
description: 'add clever text',
base: tBase1,
baseId: tBase1,
language: tLanguage1,
);
@@ -231,7 +231,7 @@ void main() {
creationDate: DateTime(2021, 1, 15),
name: 'Crunches',
description: 'add clever text',
base: tBase1,
baseId: tBase1,
language: tLanguage1,
);
@@ -243,7 +243,7 @@ void main() {
final setting1 = Setting(
setId: 1,
order: 1,
exerciseId: 1,
exerciseBaseId: 1,
repetitionUnitId: 1,
reps: 5,
weightUnitId: 1,
@@ -252,13 +252,13 @@ void main() {
);
setting1.repetitionUnit = repetitionUnit1;
setting1.weightUnit = weightUnit1;
setting1.exercise = squats;
setting1.exerciseBase = squats;
setting1.weight = 100;
final setting2 = Setting(
setId: 1,
order: 1,
exerciseId: 2,
exerciseBaseId: 2,
repetitionUnitId: 1,
reps: 6,
weightUnitId: 1,
@@ -267,13 +267,13 @@ void main() {
);
setting2.repetitionUnit = repetitionUnit1;
setting2.weightUnit = weightUnit1;
setting2.exercise = benchPress;
setting2.exerciseBase = benchPress;
setting2.weight = 80;
final setting2b = Setting(
setId: 1,
order: 1,
exerciseId: 2,
exerciseBaseId: 2,
repetitionUnitId: 1,
reps: 8,
weightUnitId: 1,
@@ -282,13 +282,13 @@ void main() {
);
setting2b.repetitionUnit = repetitionUnit1;
setting2b.weightUnit = weightUnit1;
setting2b.exercise = benchPress;
setting2b.exerciseBase = benchPress;
setting2b.weight = 60;
final setting3 = Setting(
setId: 1,
order: 1,
exerciseId: 2,
exerciseBaseId: 2,
repetitionUnitId: 1,
reps: 20,
weightUnitId: 1,
@@ -297,12 +297,12 @@ void main() {
);
setting3.repetitionUnit = repetitionUnit1;
setting3.weightUnit = weightUnit1;
setting3.exercise = crunches;
setting3.exerciseBase = crunches;
final setting4 = Setting(
setId: 1,
order: 1,
exerciseId: 2,
exerciseBaseId: 2,
repetitionUnitId: 1,
reps: 8,
weightUnitId: 1,
@@ -311,7 +311,7 @@ void main() {
);
setting4.repetitionUnit = repetitionUnit1;
setting4.weightUnit = weightUnit1;
setting4.exercise = deadLift;
setting4.exerciseBase = deadLift;
setting4.weight = 120;
final set1 = Set.withData(
@@ -320,7 +320,7 @@ void main() {
sets: 3,
order: 1,
);
set1.addExercise(squats);
set1.addExerciseBase(squats);
set1.settings.add(setting1);
set1.settings.add(setting1);
set1.settings.add(setting1);
@@ -332,7 +332,7 @@ void main() {
sets: 3,
order: 1,
);
set2.addExercise(benchPress);
set2.addExerciseBase(benchPress);
set2.settings.add(setting2);
set2.settings.add(setting2);
set2.settings.add(setting2b);
@@ -344,7 +344,7 @@ void main() {
sets: 3,
order: 1,
);
set3.addExercise(crunches);
set3.addExerciseBase(crunches);
set3.settings.add(setting3);
final set4 = Set.withData(
@@ -353,7 +353,7 @@ void main() {
sets: 3,
order: 1,
);
set4.addExercise(deadLift);
set4.addExerciseBase(deadLift);
set4.settings.add(setting4);
set4.settings.add(setting4);
set4.settings.add(setting4);

View File

@@ -121,7 +121,7 @@ class ExerciseBase extends Equatable {
/// translation in English. This is something that should never happen,
/// but we can't make sure that no local installation hasn't deleted
/// the entry in English.
Exercise getExercises(String language) {
Exercise getExercise(String language) {
return exercises.firstWhere(
(e) => e.languageObj.shortName == language,
orElse: () => exercises.firstWhere(

View File

@@ -20,12 +20,8 @@ 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';
import 'package:wger/models/exercises/equipment.dart';
import 'package:wger/models/exercises/image.dart';
import 'package:wger/models/exercises/language.dart';
import 'package:wger/models/exercises/muscle.dart';
part 'exercise.g.dart';
@@ -49,9 +45,6 @@ class Exercise extends Equatable {
@JsonKey(required: true, name: 'exercise_base')
late int? baseId;
@JsonKey(ignore: true)
late ExerciseBase baseObj;
@JsonKey(required: true)
final String name;
@@ -70,12 +63,11 @@ class Exercise extends Equatable {
this.creationDate,
required this.name,
required this.description,
base,
int? baseId,
language,
}) {
if (base != null) {
baseObj = base;
baseId = base.id;
if (baseId != null) {
baseId = baseId;
}
if (language != null) {
@@ -84,15 +76,7 @@ class Exercise extends Equatable {
}
}
ExerciseImage? get getMainImage => baseObj.getMainImage;
ExerciseCategory get category => baseObj.category;
List<ExerciseImage> get images => baseObj.images;
List<Equipment> get equipment => baseObj.equipment;
List<Muscle> get muscles => baseObj.muscles;
List<Muscle> get musclesSecondary => baseObj.musclesSecondary;
set base(ExerciseBase base) {
baseObj = base;
baseId = base.id;
}
@@ -114,6 +98,5 @@ class Exercise extends Equatable {
creationDate,
name,
description,
baseObj,
];
}

View File

@@ -27,9 +27,8 @@ Exercise _$ExerciseFromJson(Map<String, dynamic> json) {
: DateTime.parse(json['creation_date'] as String),
name: json['name'] as String,
description: json['description'] as String,
)
..languageId = json['language'] as int
..baseId = json['exercise_base'] as int?;
baseId: json['exercise_base'] as int?,
)..languageId = json['language'] as int;
}
Map<String, dynamic> _$ExerciseToJson(Exercise instance) => <String, dynamic>{

View File

@@ -21,7 +21,7 @@ import 'dart:ui';
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/helpers/json.dart';
import 'package:wger/helpers/misc.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/workouts/repetition_unit.dart';
import 'package:wger/models/workouts/weight_unit.dart';
@@ -32,11 +32,11 @@ class Log {
@JsonKey(required: true)
int? id;
@JsonKey(required: true, name: 'exercise')
late int exerciseId;
@JsonKey(required: true, name: 'exercise_base')
late int exerciseBaseId;
@JsonKey(ignore: true)
late Exercise exerciseObj;
late ExerciseBase exerciseBaseObj;
@JsonKey(required: true, name: 'workout')
late int workoutPlan;
@@ -70,7 +70,7 @@ class Log {
Log({
this.id,
required this.exerciseId,
required this.exerciseBaseId,
required this.workoutPlan,
required this.reps,
required this.rir,
@@ -86,9 +86,9 @@ class Log {
factory Log.fromJson(Map<String, dynamic> json) => _$LogFromJson(json);
Map<String, dynamic> toJson() => _$LogToJson(this);
set exercise(Exercise exercise) {
exerciseObj = exercise;
exerciseId = exercise.id!;
set exerciseBase(ExerciseBase base) {
exerciseBaseObj = base;
exerciseBaseId = base.id!;
}
set weightUnit(WeightUnit weightUnit) {
@@ -123,7 +123,7 @@ class Log {
//ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(o) {
return o is Log &&
exerciseId == o.exerciseId &&
exerciseBaseId == o.exerciseBaseId &&
weight == o.weight &&
weightUnitId == o.weightUnitId &&
reps == o.reps &&
@@ -133,13 +133,13 @@ class Log {
@override
//ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => hashValues(exerciseId, weight, weightUnitId, reps, repetitionUnitId, rir);
int get hashCode => hashValues(exerciseBaseId, weight, weightUnitId, reps, repetitionUnitId, rir);
//@override
//int get hashCode => super.hashCode;
@override
String toString() {
return 'Log(id: $id, ex: $exerciseId, weightU: $weightUnitId, w: $weight, repU: $repetitionUnitId, rep: $reps, rir: $rir)';
return 'Log(id: $id, ex: $exerciseBaseId, weightU: $weightUnitId, w: $weight, repU: $repetitionUnitId, rep: $reps, rir: $rir)';
}
}

View File

@@ -11,7 +11,7 @@ Log _$LogFromJson(Map<String, dynamic> json) {
json,
requiredKeys: const [
'id',
'exercise',
'exercise_base',
'workout',
'reps',
'repetition_unit',
@@ -22,7 +22,7 @@ Log _$LogFromJson(Map<String, dynamic> json) {
);
return Log(
id: json['id'] as int?,
exerciseId: json['exercise'] as int,
exerciseBaseId: json['exercise_base'] as int,
workoutPlan: json['workout'] as int,
reps: json['reps'] as int,
rir: json['rir'] as String?,
@@ -35,7 +35,7 @@ Log _$LogFromJson(Map<String, dynamic> json) {
Map<String, dynamic> _$LogToJson(Log instance) => <String, dynamic>{
'id': instance.id,
'exercise': instance.exerciseId,
'exercise_base': instance.exerciseBaseId,
'workout': instance.workoutPlan,
'reps': instance.reps,
'rir': instance.rir,

View File

@@ -17,7 +17,7 @@
*/
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/workouts/setting.dart';
part 'set.g.dart';
@@ -42,10 +42,10 @@ class Set {
late String comment;
@JsonKey(ignore: true)
List<Exercise> exercisesObj = [];
List<ExerciseBase> exerciseBasesObj = [];
@JsonKey(ignore: true)
List<int> exercisesIds = [];
List<int> exerciseBasesIds = [];
@JsonKey(ignore: true)
List<Setting> settings = [];
@@ -76,8 +76,8 @@ class Set {
this.sets = sets ?? DEFAULT_NR_SETS;
this.order = order ?? 1;
this.comment = comment ?? '';
exercisesObj = exercises ?? [];
exercisesIds = exercisesObj.map((e) => e.id!).toList();
exerciseBasesObj = exercises ?? [];
exerciseBasesIds = exerciseBasesObj.map((e) => e.id!).toList();
this.settings = settings ?? [];
this.settingsComputed = settingsComputed ?? [];
if (day != null) {
@@ -92,7 +92,7 @@ class Set {
for (final setting in settings) {
final foundSettings = out.where(
(element) => element.exerciseId == setting.exerciseId,
(element) => element.exerciseBaseId == setting.exerciseBaseId,
);
if (foundSettings.isEmpty) {
@@ -102,26 +102,26 @@ class Set {
return out;
}
void addExercise(Exercise exercise) {
exercisesObj.add(exercise);
exercisesIds.add(exercise.id!);
void addExerciseBase(ExerciseBase base) {
exerciseBasesObj.add(base);
exerciseBasesIds.add(base.id!);
}
void removeExercise(Exercise exercise) {
exercisesObj.removeWhere((e) => e.id == exercise.id);
exercisesIds.removeWhere((e) => e == exercise.id);
void removeExercise(ExerciseBase base) {
exerciseBasesObj.removeWhere((e) => e.id == base.id);
exerciseBasesIds.removeWhere((e) => e == base.id);
}
/// Returns all settings for the given exercise
List<Setting> filterSettingsByExercise(Exercise exercise) {
return settings.where((element) => element.exerciseId == exercise.id).toList();
List<Setting> filterSettingsByExercise(ExerciseBase exerciseBase) {
return settings.where((element) => element.exerciseBaseId == exerciseBase.id).toList();
}
/// Returns a list with all repetitions for the given exercise
List<String> getSmartRepr(Exercise exercise) {
List<String> getSmartRepr(ExerciseBase exerciseBase) {
final List<String> out = [];
final settingList = filterSettingsByExercise(exercise);
final settingList = filterSettingsByExercise(exerciseBase);
if (settingList.isEmpty) {
out.add('');
@@ -141,8 +141,8 @@ class Set {
}
/// Returns a string with all repetitions for the given exercise
String getSmartTextRepr(Exercise exercise) {
return getSmartRepr(exercise).join(' ');
String getSmartTextRepr(ExerciseBase execiseBase) {
return getSmartRepr(execiseBase).join(' ');
}
// Boilerplate

View File

@@ -19,7 +19,7 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/helpers/json.dart';
import 'package:wger/helpers/misc.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/workouts/repetition_unit.dart';
import 'package:wger/models/workouts/weight_unit.dart';
@@ -42,10 +42,10 @@ class Setting {
late int order;
@JsonKey(ignore: true)
late Exercise exerciseObj;
late ExerciseBase exerciseBaseObj;
@JsonKey(required: true, name: 'exercise')
late int exerciseId;
@JsonKey(required: true, name: 'exercise_base')
late int exerciseBaseId;
@JsonKey(required: true, name: 'repetition_unit')
late int repetitionUnitId;
@@ -77,7 +77,7 @@ class Setting {
this.id,
required this.setId,
required this.order,
required this.exerciseId,
required this.exerciseBaseId,
required this.repetitionUnitId,
required this.reps,
required this.weightUnitId,
@@ -91,9 +91,9 @@ class Setting {
factory Setting.fromJson(Map<String, dynamic> json) => _$SettingFromJson(json);
Map<String, dynamic> toJson() => _$SettingToJson(this);
set exercise(Exercise exercise) {
exerciseObj = exercise;
exerciseId = exercise.id!;
set exerciseBase(ExerciseBase exerciseBase) {
exerciseBaseObj = exerciseBase;
exerciseBaseId = exerciseBase.id!;
}
set weightUnit(WeightUnit weightUnit) {

View File

@@ -13,7 +13,7 @@ Setting _$SettingFromJson(Map<String, dynamic> json) {
'id',
'set',
'order',
'exercise',
'exercise_base',
'repetition_unit',
'reps',
'weight',
@@ -26,7 +26,7 @@ Setting _$SettingFromJson(Map<String, dynamic> json) {
id: json['id'] as int?,
setId: json['set'] as int,
order: json['order'] as int,
exerciseId: json['exercise'] as int,
exerciseBaseId: json['exercise_base'] as int,
repetitionUnitId: json['repetition_unit'] as int,
reps: json['reps'] as int?,
weightUnitId: json['weight_unit'] as int,
@@ -39,7 +39,7 @@ Map<String, dynamic> _$SettingToJson(Setting instance) => <String, dynamic>{
'id': instance.id,
'set': instance.setId,
'order': instance.order,
'exercise': instance.exerciseId,
'exercise_base': instance.exerciseBaseId,
'repetition_unit': instance.repetitionUnitId,
'reps': instance.reps,
'weight': numToString(instance.weight),

View File

@@ -17,6 +17,7 @@
*/
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/workouts/day.dart';
import 'package:wger/models/workouts/log.dart';
@@ -72,8 +73,8 @@ class WorkoutPlan {
/// means here that the values are the same, i.e. logs with the same weight,
/// reps, etc. are considered equal. Workout ID, Log ID and date are not
/// considered.
List<Log> filterLogsByExercise(Exercise exercise, {bool unique = false}) {
var out = logs.where((element) => element.exerciseId == exercise.id).toList();
List<Log> filterLogsByExerciseBase(ExerciseBase exerciseBase, {bool unique = false}) {
var out = logs.where((element) => element.exerciseBaseId == exerciseBase.id).toList();
if (unique) {
out = out.toSet().toList();
@@ -88,7 +89,7 @@ class WorkoutPlan {
Map<DateTime, Map<String, dynamic>> get logData {
final out = <DateTime, Map<String, dynamic>>{};
for (final log in logs) {
final exercise = log.exerciseObj;
final exercise = log.exerciseBaseObj;
final date = log.date;
if (!out.containsKey(date)) {

View File

@@ -155,8 +155,7 @@ class ExercisesProvider with ChangeNotifier {
List<ExerciseBase> filteredItems = _exerciseBases;
if (filters!.searchTerm.length > 1) {
final exercises = await searchExercise(filters!.searchTerm);
filteredItems = exercises.map((e) => e.baseObj).toList();
filteredItems = await searchExercise(filters!.searchTerm);
}
// Filter by exercise category and equipment (REPLACE WITH HTTP REQUEST)
@@ -182,11 +181,11 @@ class ExercisesProvider with ChangeNotifier {
_exerciseBases = [];
}
List<Exercise> findByCategory(ExerciseCategory? category) {
List<ExerciseBase> findByCategory(ExerciseCategory? category) {
if (category == null) {
return items;
return bases;
}
return items.where((exercise) => exercise.category == category).toList();
return bases.where((base) => base.category.id == category.id).toList();
}
/// Find exercise by ID
@@ -197,6 +196,14 @@ class ExercisesProvider with ChangeNotifier {
);
}
/// Find exercise base by ID
ExerciseBase findExerciseBaseById(int id) {
return _exerciseBases.firstWhere(
(base) => base.id == id,
orElse: () => throw NoSuchEntryException(),
);
}
/// Find exercise bases by variation IDs
///
/// exerciseIdToExclude: the ID of the exercise to exclude from the list of
@@ -206,17 +213,21 @@ class ExercisesProvider with ChangeNotifier {
///
/// languageId: the ID of the language to filter the results by. If this
/// parameter is not passed, all exercises are returned.
List<Exercise> findExercisesByVariationId(int id, {int? exerciseIdToExclude, int? languageId}) {
var out = _exercises
List<ExerciseBase> findExerciseBasesByVariationId(int id,
{int? exerciseIdToExclude, int? languageId}) {
var out = _exerciseBases
.where(
(base) => base.baseObj.variationId == id,
(base) => base.variationId == id,
)
.toList();
/*
if (languageId != null) {
out = out.where((e) => e.languageId == languageId).toList();
}
*/
if (exerciseIdToExclude != null) {
out = out.where((e) => e.id != exerciseIdToExclude).toList();
}
@@ -362,6 +373,15 @@ class ExercisesProvider with ChangeNotifier {
}
}
/// 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);
}
/// Checks the required cache version
///
/// This is needed since the content of the exercise cache can change and we need
@@ -415,7 +435,7 @@ class ExercisesProvider with ChangeNotifier {
for (var base in bases) {
final filteredExercises = exercises.where((e) => e.baseId == base.id);
for (final exercise in filteredExercises) {
exercise.baseObj = base;
exercise.base = base;
base.exercises.add(exercise);
out.add(exercise);
}
@@ -547,7 +567,7 @@ class ExercisesProvider with ChangeNotifier {
///
/// We could do this locally, but the server has better text searching capabilities
/// with postgresql.
Future<List<Exercise>> searchExercise(String name, [String languageCode = 'en']) async {
Future<List<ExerciseBase>> searchExercise(String name, [String languageCode = 'en']) async {
if (name.length <= 1) {
return [];
}
@@ -562,8 +582,8 @@ class ExercisesProvider with ChangeNotifier {
// Process the response
return await Future.wait(
(result['suggestions'] as List).map<Future<Exercise>>(
(entry) => fetchAndSetExercise(entry['data']['id']),
(result['suggestions'] as List).map<Future<ExerciseBase>>(
(entry) => fetchAndSetExerciseBase(entry['data']['base_id']),
),
);
}

View File

@@ -202,15 +202,16 @@ class WorkoutPlansProvider extends WgerBaseProvider with ChangeNotifier {
for (final settingEntry in settingData) {
final workoutSetting = Setting.fromJson(settingEntry);
workoutSetting.exercise = await _exercises.fetchAndSetExercise(workoutSetting.exerciseId);
workoutSetting.exerciseBase =
await _exercises.fetchAndSetExerciseBase(workoutSetting.exerciseBaseId);
workoutSetting.weightUnit = _weightUnits.firstWhere(
(e) => e.id == workoutSetting.weightUnitId,
);
workoutSetting.repetitionUnit = _repetitionUnit.firstWhere(
(e) => e.id == workoutSetting.repetitionUnitId,
);
if (!workoutSet.exercisesIds.contains(workoutSetting.exerciseId)) {
workoutSet.addExercise(workoutSetting.exerciseObj);
if (!workoutSet.exerciseBasesIds.contains(workoutSetting.exerciseBaseId)) {
workoutSet.addExerciseBase(workoutSetting.exerciseBaseObj);
}
settings.add(workoutSetting);
@@ -243,7 +244,7 @@ class WorkoutPlansProvider extends WgerBaseProvider with ChangeNotifier {
final log = Log.fromJson(entry);
log.weightUnit = _weightUnits.firstWhere((e) => e.id == log.weightUnitId);
log.repetitionUnit = _repetitionUnit.firstWhere((e) => e.id == log.weightUnitId);
log.exercise = await _exercises.fetchAndSetExercise(log.exerciseId);
log.exerciseBase = await _exercises.fetchAndSetExerciseBase(log.exerciseBaseId);
plan.logs.add(log);
} catch (e) {
dev.log('fire! fire!');
@@ -499,7 +500,7 @@ class WorkoutPlansProvider extends WgerBaseProvider with ChangeNotifier {
log.id = newLog.id;
log.weightUnit = _weightUnits.firstWhere((e) => e.id == log.weightUnitId);
log.repetitionUnit = _repetitionUnit.firstWhere((e) => e.id == log.weightUnitId);
log.exercise = await _exercises.fetchAndSetExercise(log.exerciseId);
log.exerciseBase = await _exercises.fetchAndSetExerciseBase(log.exerciseBaseId);
final plan = findById(log.workoutPlan);
plan.logs.add(log);

View File

@@ -17,7 +17,7 @@
*/
import 'package:flutter/material.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/widgets/exercises/exercises.dart';
class ExerciseDetailScreen extends StatelessWidget {
@@ -27,15 +27,15 @@ class ExerciseDetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final exercise = ModalRoute.of(context)!.settings.arguments as Exercise;
final exerciseBase = ModalRoute.of(context)!.settings.arguments as ExerciseBase;
return Scaffold(
appBar: AppBar(
title: Text(exercise.name),
title: Text(exerciseBase.getExercise(Localizations.localeOf(context).languageCode).name),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ExerciseDetail(exercise),
child: ExerciseDetail(exerciseBase),
),
);
}

View File

@@ -70,9 +70,8 @@ class _ExercisesList extends StatelessWidget {
},
itemCount: exerciseBaseList.length,
itemBuilder: (context, index) {
final exercise = exerciseBaseList[index];
return ExerciseListTile(
exercise: exercise.getExercises(Localizations.localeOf(context).languageCode),
exerciseBase: exerciseBaseList[index],
);
/*

View File

@@ -43,7 +43,7 @@ class DuplicatesAndVariationsStepContent extends StatelessWidget {
.map(
(base) => Text(
base
.getExercises(
.getExercise(
Localizations.localeOf(context).languageCode)
.name,
overflow: TextOverflow.ellipsis,
@@ -77,7 +77,7 @@ class DuplicatesAndVariationsStepContent extends StatelessWidget {
children: [
Text(
base
.getExercises(Localizations.localeOf(context).languageCode)
.getExercise(Localizations.localeOf(context).languageCode)
.name,
overflow: TextOverflow.ellipsis,
),

View File

@@ -382,9 +382,9 @@ class _DashboardWorkoutWidgetState extends State<DashboardWorkoutWidget> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.exerciseObj.name),
Text(s.exerciseBaseObj.name),
const SizedBox(width: 10),
MutedText(set.getSmartRepr(s.exerciseObj).join('\n')),
MutedText(set.getSmartRepr(s.exerciseBaseObj).join('\n')),
],
),
const SizedBox(height: 10),

View File

@@ -23,6 +23,7 @@ import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/i18n.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/providers/exercises.dart';
@@ -32,13 +33,16 @@ import 'package:wger/widgets/exercises/list_tile.dart';
import 'package:wger/widgets/exercises/videos.dart';
class ExerciseDetail extends StatelessWidget {
final Exercise _exercise;
final ExerciseBase _exerciseBase;
late Exercise _exercise;
static const PADDING = 9.0;
const ExerciseDetail(this._exercise);
ExerciseDetail(this._exerciseBase);
@override
Widget build(BuildContext context) {
_exercise = _exerciseBase.getExercise(Localizations.localeOf(context).languageCode);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -71,7 +75,7 @@ class ExerciseDetail extends StatelessWidget {
List<Widget> getVariations(BuildContext context) {
final List<Widget> out = [];
if (_exercise.baseObj.variationId == null) {
if (_exerciseBase.variationId == null) {
return out;
}
@@ -80,14 +84,14 @@ class ExerciseDetail extends StatelessWidget {
style: Theme.of(context).textTheme.headline5,
));
Provider.of<ExercisesProvider>(context, listen: false)
.findExercisesByVariationId(
_exercise.baseObj.variationId!,
.findExerciseBasesByVariationId(
_exerciseBase.variationId!,
languageId: _exercise.languageId,
exerciseIdToExclude: _exercise.id,
exerciseIdToExclude: _exerciseBase.id,
)
.forEach((element) {
out.add(ExerciseListTile(
exercise: element,
exerciseBase: element,
));
});
@@ -125,8 +129,8 @@ class ExerciseDetail extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: PADDING),
child: MuscleWidget(
muscles: _exercise.muscles,
musclesSecondary: _exercise.musclesSecondary,
muscles: _exerciseBase.muscles,
musclesSecondary: _exerciseBase.musclesSecondary,
isFront: true,
),
),
@@ -135,8 +139,8 @@ class ExerciseDetail extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: PADDING),
child: MuscleWidget(
muscles: _exercise.muscles,
musclesSecondary: _exercise.musclesSecondary,
muscles: _exerciseBase.muscles,
musclesSecondary: _exerciseBase.musclesSecondary,
isFront: false,
),
),
@@ -149,7 +153,7 @@ class ExerciseDetail extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const MuscleColorHelper(main: true),
..._exercise.muscles.map((e) => Text(e.name)).toList(),
..._exerciseBase.muscles.map((e) => Text(e.name)).toList(),
],
),
);
@@ -159,7 +163,7 @@ class ExerciseDetail extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const MuscleColorHelper(main: false),
..._exercise.musclesSecondary.map((e) => Text(e.name)).toList(),
..._exerciseBase.musclesSecondary.map((e) => Text(e.name)).toList(),
],
),
);
@@ -183,8 +187,8 @@ class ExerciseDetail extends StatelessWidget {
List<Widget> getImages() {
// TODO: add carousel for the other images
final List<Widget> out = [];
if (_exercise.getMainImage != null) {
out.add(ExerciseImageWidget(image: _exercise.getMainImage));
if (_exerciseBase.getMainImage != null) {
out.add(ExerciseImageWidget(image: _exerciseBase.getMainImage));
out.add(const SizedBox(height: PADDING));
}
@@ -195,10 +199,10 @@ class ExerciseDetail extends StatelessWidget {
final List<Widget> out = [];
out.add(
Chip(label: Text(getTranslation(_exercise.baseObj.category.name, context))),
Chip(label: Text(getTranslation(_exerciseBase.category.name, context))),
);
if (_exercise.baseObj.equipment.isNotEmpty) {
_exercise.baseObj.equipment
if (_exerciseBase.equipment.isNotEmpty) {
_exerciseBase.equipment
.map((e) => Chip(label: Text(getTranslation(e.name, context))))
.forEach((element) {
out.add(element);
@@ -211,8 +215,8 @@ class ExerciseDetail extends StatelessWidget {
List<Widget> getVideos() {
// TODO: add carousel for the other videos
final List<Widget> out = [];
if (_exercise.baseObj.videos.isNotEmpty) {
_exercise.baseObj.videos.map((v) => ExerciseVideoWidget(video: v)).forEach((element) {
if (_exerciseBase.videos.isNotEmpty) {
_exerciseBase.videos.map((v) => ExerciseVideoWidget(video: v)).forEach((element) {
out.add(element);
});

View File

@@ -18,14 +18,14 @@
import 'package:flutter/material.dart';
import 'package:wger/helpers/i18n.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/screens/exercise_screen.dart';
import 'package:wger/widgets/exercises/images.dart';
class ExerciseListTile extends StatelessWidget {
const ExerciseListTile({Key? key, required this.exercise}) : super(key: key);
const ExerciseListTile({Key? key, required this.exerciseBase}) : super(key: key);
final Exercise exercise;
final ExerciseBase exerciseBase;
@override
Widget build(BuildContext context) {
@@ -44,23 +44,23 @@ class ExerciseListTile extends StatelessWidget {
height: IMG_SIZE,
width: IMG_SIZE,
child: ExerciseImageWidget(
image: exercise.getMainImage,
image: exerciseBase.getMainImage,
),
),
),
),
),
title: Text(
exercise.name,
exerciseBase.getExercise(Localizations.localeOf(context).languageCode).name,
//style: theme.textTheme.headline6,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
subtitle: Text(
'${getTranslation(exercise.category.name, context)} / ${exercise.equipment.map((e) => getTranslation(e.name, context)).toList().join(', ')}',
'${getTranslation(exerciseBase.category.name, context)} / ${exerciseBase.equipment.map((e) => getTranslation(e.name, context)).toList().join(', ')}',
),
onTap: () {
Navigator.pushNamed(context, ExerciseDetailScreen.routeName, arguments: exercise);
Navigator.pushNamed(context, ExerciseDetailScreen.routeName, arguments: exerciseBase);
},
/*
trailing: Container(

View File

@@ -50,7 +50,7 @@ class SettingWidget extends StatelessWidget {
return ListTile(
leading: InkWell(
child: SizedBox(
child: ExerciseImageWidget(image: setting.exerciseObj.getMainImage),
child: ExerciseImageWidget(image: setting.exerciseBaseObj.getMainImage),
width: 45,
),
onTap: () {
@@ -58,8 +58,10 @@ class SettingWidget extends StatelessWidget {
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(setting.exerciseObj.name),
content: ExerciseDetail(setting.exerciseObj),
title: Text(setting.exerciseBaseObj
.getExercise(Localizations.localeOf(context).languageCode)
.name),
content: ExerciseDetail(setting.exerciseBaseObj),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
@@ -73,11 +75,12 @@ class SettingWidget extends StatelessWidget {
);
},
),
title: Text(setting.exerciseObj.name),
title: Text(
setting.exerciseBaseObj.getExercise(Localizations.localeOf(context).languageCode).name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...set.getSmartRepr(setting.exerciseObj).map((e) => Text(e)).toList(),
...set.getSmartRepr(setting.exerciseBaseObj).map((e) => Text(e)).toList(),
],
),
);

View File

@@ -21,7 +21,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:provider/provider.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/workouts/day.dart';
import 'package:wger/models/workouts/repetition_unit.dart';
import 'package:wger/models/workouts/set.dart';
@@ -276,16 +276,16 @@ class _SetFormWidgetState extends State<SetFormWidget> {
final _exercisesController = TextEditingController();
/// Removes an exercise from the current set
void removeExercise(Exercise exercise) {
void removeExerciseBase(ExerciseBase base) {
setState(() {
widget._set.removeExercise(exercise);
widget._set.removeExercise(base);
});
}
/// Adds an exercise to the current set
void addExercise(Exercise exercise) {
void addExercise(ExerciseBase base) {
setState(() {
widget._set.addExercise(exercise);
widget._set.addExerciseBase(base);
addSettings();
});
}
@@ -294,12 +294,12 @@ class _SetFormWidgetState extends State<SetFormWidget> {
void addSettings() {
widget._set.settings = [];
int order = 0;
for (final exercise in widget._set.exercisesObj) {
for (final exercise in widget._set.exerciseBasesObj) {
order++;
for (int loop = 0; loop < widget._set.sets; loop++) {
final Setting setting = Setting.empty();
setting.order = order;
setting.exercise = exercise;
setting.exerciseBase = exercise;
setting.weightUnit =
Provider.of<WorkoutPlansProvider>(context, listen: false).defaultWeightUnit;
setting.repetitionUnit =
@@ -355,7 +355,7 @@ class _SetFormWidgetState extends State<SetFormWidget> {
child: Column(
children: [
Card(
child: TypeAheadFormField<Exercise>(
child: TypeAheadFormField<ExerciseBase>(
key: const Key('field-typeahead'),
textFieldConfiguration: TextFieldConfiguration(
controller: _exercisesController,
@@ -397,13 +397,15 @@ class _SetFormWidgetState extends State<SetFormWidget> {
Localizations.localeOf(context).languageCode,
);
},
itemBuilder: (BuildContext context, Exercise exerciseSuggestion) {
itemBuilder: (BuildContext context, ExerciseBase exerciseSuggestion) {
return ListTile(
leading: SizedBox(
width: 45,
child: ExerciseImageWidget(image: exerciseSuggestion.getMainImage),
),
title: Text(exerciseSuggestion.name),
title: Text(exerciseSuggestion
.getExercise(Localizations.localeOf(context).languageCode)
.name),
subtitle: Text(
'${exerciseSuggestion.category.name} / ${exerciseSuggestion.equipment.map((e) => e.name).join(', ')}',
),
@@ -412,13 +414,13 @@ class _SetFormWidgetState extends State<SetFormWidget> {
transitionBuilder: (context, suggestionsBox, controller) {
return suggestionsBox;
},
onSuggestionSelected: (Exercise exerciseSuggestion) {
onSuggestionSelected: (ExerciseBase exerciseSuggestion) {
addExercise(exerciseSuggestion);
this._exercisesController.text = '';
},
validator: (value) {
// At least one exercise must be selected
if (widget._set.exercisesIds.isEmpty) {
if (widget._set.exerciseBasesIds.isEmpty) {
return AppLocalizations.of(context).selectExercise;
}
@@ -453,12 +455,13 @@ class _SetFormWidgetState extends State<SetFormWidget> {
},
),
const SizedBox(height: 10),
...widget._set.exercisesObj.asMap().entries.map((entry) {
...widget._set.exerciseBasesObj.asMap().entries.map((entry) {
final index = entry.key;
final exercise = entry.value;
final showSupersetInfo = (index + 1) < widget._set.exercisesObj.length;
final settings =
widget._set.settings.where((e) => e.exerciseObj.id == exercise.id).toList();
final showSupersetInfo = (index + 1) < widget._set.exerciseBasesObj.length;
final settings = widget._set.settings
.where((e) => e.exerciseBaseObj.id == exercise.id)
.toList();
return Column(
children: [
@@ -467,7 +470,7 @@ class _SetFormWidgetState extends State<SetFormWidget> {
settings,
_detailed,
_currentSetSliderValue,
removeExercise,
removeExerciseBase,
),
if (showSupersetInfo)
const Padding(
@@ -530,14 +533,14 @@ class _SetFormWidgetState extends State<SetFormWidget> {
}
class ExerciseSetting extends StatelessWidget {
final Exercise _exercise;
final ExerciseBase _exerciseBase;
late final int _numberOfSets;
final bool _detailed;
final Function removeExercise;
final List<Setting> _settings;
ExerciseSetting(
this._exercise,
this._exerciseBase,
this._settings,
this._detailed,
double sliderValue,
@@ -630,16 +633,16 @@ class ExerciseSetting extends StatelessWidget {
children: [
ListTile(
title: Text(
_exercise.name,
_exerciseBase.getExercise(Localizations.localeOf(context).languageCode).name,
style: Theme.of(context).textTheme.headline6,
),
subtitle: Text(_exercise.category.name),
subtitle: Text(_exerciseBase.category.name),
contentPadding: EdgeInsets.zero,
leading: ExerciseImageWidget(image: _exercise.getMainImage),
leading: ExerciseImageWidget(image: _exerciseBase.getMainImage),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
removeExercise(_exercise);
removeExercise(_exerciseBase);
}),
),
const Divider(),

View File

@@ -29,7 +29,7 @@ import 'package:wger/helpers/i18n.dart';
import 'package:wger/helpers/json.dart';
import 'package:wger/helpers/misc.dart';
import 'package:wger/helpers/ui.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/base.dart';
import 'package:wger/models/workouts/day.dart';
import 'package:wger/models/workouts/log.dart';
import 'package:wger/models/workouts/session.dart';
@@ -85,11 +85,11 @@ class _GymModeState extends State<GymMode> {
for (final set in widget._workoutDay.sets) {
var firstPage = true;
for (final setting in set.settingsComputed) {
final exercise = Provider.of<ExercisesProvider>(context, listen: false)
.findExerciseById(setting.exerciseId);
final exerciseBase = Provider.of<ExercisesProvider>(context, listen: false)
.findExerciseBaseById(setting.exerciseBaseId);
if (firstPage) {
_exercisePages[exercise.name] = currentPage;
_exercisePages[exerciseBase.uuid!] = currentPage;
currentPage++;
}
@@ -114,13 +114,13 @@ class _GymModeState extends State<GymMode> {
var firstPage = true;
for (final setting in set.settingsComputed) {
final ratioCompleted = currentElement / _totalElements;
final exercise = exerciseProvider.findExerciseById(setting.exerciseId);
final exerciseBase = exerciseProvider.findExerciseBaseById(setting.exerciseBaseId);
currentElement++;
if (firstPage) {
out.add(ExerciseOverview(
_controller,
exercise,
exerciseBase,
ratioCompleted,
_exercisePages,
));
@@ -130,7 +130,7 @@ class _GymModeState extends State<GymMode> {
_controller,
setting,
set,
exercise,
exerciseBase,
workoutProvider.findById(widget._workoutDay.workoutId),
ratioCompleted,
_exercisePages,
@@ -194,10 +194,12 @@ class StartPage extends StatelessWidget {
return Column(
children: [
Text(
s.exerciseObj.name,
s.exerciseBaseObj
.getExercise(Localizations.localeOf(context).languageCode)
.name,
style: Theme.of(context).textTheme.headline6,
),
...set.getSmartRepr(s.exerciseObj).map((e) => Text(e)).toList(),
...set.getSmartRepr(s.exerciseBaseObj).map((e) => Text(e)).toList(),
const SizedBox(height: 15),
],
);
@@ -230,7 +232,7 @@ class LogPage extends StatefulWidget {
final PageController _controller;
final Setting _setting;
final Set _set;
final Exercise _exercise;
final ExerciseBase _exerciseBase;
final WorkoutPlan _workoutPlan;
final double _ratioCompleted;
final Map<String, int> _exercisePages;
@@ -240,14 +242,14 @@ class LogPage extends StatefulWidget {
this._controller,
this._setting,
this._set,
this._exercise,
this._exerciseBase,
this._workoutPlan,
this._ratioCompleted,
this._exercisePages,
) {
_log.date = DateTime.now();
_log.workoutPlan = _workoutPlan.id!;
_log.exercise = _exercise;
_log.exerciseBase = _exerciseBase;
_log.weightUnit = _setting.weightUnitObj;
_log.repetitionUnit = _setting.repetitionUnitObj;
_log.rir = _setting.rir;
@@ -524,7 +526,9 @@ class _LogPageState extends State<LogPage> {
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
...widget._workoutPlan.filterLogsByExercise(widget._exercise, unique: true).map((log) {
...widget._workoutPlan
.filterLogsByExerciseBase(widget._exerciseBase, unique: true)
.map((log) {
return ListTile(
title: Text(log.singleLogRepTextNoNl),
subtitle:
@@ -618,7 +622,7 @@ class _LogPageState extends State<LogPage> {
return Column(
children: [
NavigationHeader(
widget._exercise.name,
widget._exerciseBase.getExercise(Localizations.localeOf(context).languageCode).name,
widget._controller,
exercisePages: widget._exercisePages,
),
@@ -636,11 +640,11 @@ class _LogPageState extends State<LogPage> {
),
const SizedBox(height: 10),
Expanded(
child: (widget._workoutPlan.filterLogsByExercise(widget._exercise).isNotEmpty)
child: (widget._workoutPlan.filterLogsByExerciseBase(widget._exerciseBase).isNotEmpty)
? getPastLogs()
: Container()),
// Only show calculator for barbell
if (widget._log.exerciseObj.equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL))
if (widget._log.exerciseBaseObj.equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL))
getPlates(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
@@ -657,13 +661,13 @@ class _LogPageState extends State<LogPage> {
class ExerciseOverview extends StatelessWidget {
final PageController _controller;
final Exercise _exercise;
final ExerciseBase _exerciseBase;
final double _ratioCompleted;
final Map<String, int> _exercisePages;
const ExerciseOverview(
this._controller,
this._exercise,
this._exerciseBase,
this._ratioCompleted,
this._exercisePages,
);
@@ -673,7 +677,7 @@ class ExerciseOverview extends StatelessWidget {
return Column(
children: [
NavigationHeader(
_exercise.name,
_exerciseBase.getExercise(Localizations.localeOf(context).languageCode).name,
_controller,
exercisePages: _exercisePages,
),
@@ -683,29 +687,32 @@ class ExerciseOverview extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 15),
children: [
Text(
getTranslation(_exercise.category.name, context),
getTranslation(_exerciseBase.category.name, context),
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
..._exercise.equipment
..._exerciseBase.equipment
.map((e) => Text(
getTranslation(e.name, context),
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
))
.toList(),
if (_exercise.images.isNotEmpty)
if (_exerciseBase.images.isNotEmpty)
SizedBox(
width: double.infinity,
height: 200,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
..._exercise.images.map((e) => ExerciseImageWidget(image: e)).toList(),
..._exerciseBase.images.map((e) => ExerciseImageWidget(image: e)).toList(),
],
),
),
Html(data: _exercise.description),
Html(
data: _exerciseBase
.getExercise(Localizations.localeOf(context).languageCode)
.description),
],
),
),

View File

@@ -212,7 +212,7 @@ main() {
// assert
verifyNever(provider.baseProvider.fetch(tSearchByNameUri));
expect(provider.filteredExerciseBases, [data.getTestExercises()[0].baseObj]);
expect(provider.filteredExerciseBases, [data.getTestExerciseBases()[0]]);
});
test('A muscle is selected with no search term. Should not find results', () async {

View File

@@ -38,7 +38,7 @@ void main() {
supportedLocales: AppLocalizations.supportedLocales,
navigatorKey: GlobalKey<NavigatorState>(),
home: Scaffold(
body: ExerciseDetail(getTestExercises()[0]),
body: ExerciseDetail(getTestExerciseBases()[0]),
),
),
);
@@ -64,10 +64,8 @@ void main() {
reason: 'Two diagrams, one for front, one for the back',
);
expect(find.text('Booty'), findsOneWidget, reason: 'Secondary muscles');
expect(find.text('Description'), findsOneWidget, reason: 'Description header');
expect(find.text('add clever text'), findsOneWidget, reason: 'Description');
expect(find.text('Lorem ipsum etc'), findsOneWidget, reason: 'Description');
expect(find.text('Variations'), findsNothing);
});
}

View File

@@ -4,6 +4,7 @@
"value": "Bench Press Narrow Grip",
"data": {
"id": 1,
"base_id": 1,
"name": "test exercise 1",
"category": "Arms",
"image": "/media/exercise-images/76/Narrow-grip-bench-press-2.png",
@@ -14,6 +15,7 @@
"value": "Close-grip Bench Press",
"data": {
"id": 2,
"base_id": 2,
"name": "test exercise 2",
"category": "Arms",
"image": null,

View File

@@ -15,8 +15,8 @@ import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/screens/nutritional_plan_screen.dart';
import 'package:wger/widgets/nutrition/forms.dart';
import '../../test_data/nutritional_plans.dart';
import '../../test_data/nutritional_plans.dart';
import '../fixtures/fixture_reader.dart';
import '../other/base_provider_test.mocks.dart';
import '../utils.dart';
@@ -49,20 +49,14 @@ void main() {
final Uri tUriEmptyCode = Uri.parse('https://localhost/api/v2/ingredient/?code=\"%20\"');
final Uri tUriBadCode = Uri.parse('https://localhost/api/v2/ingredient/?code=222');
when(client
.get(tUriRightCode, headers: {'authorization': 'Token FooBar', 'user-agent': 'wger App'}))
.thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_right_code.json'), 200)));
when(client.get(tUriRightCode, headers: anyNamed('headers'))).thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_right_code.json'), 200)));
when(client
.get(tUriEmptyCode, headers: {'authorization': 'Token FooBar', 'user-agent': 'wger App'}))
.thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_wrong_code.json'), 200)));
when(client.get(tUriEmptyCode, headers: anyNamed('headers'))).thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_wrong_code.json'), 200)));
when(client
.get(tUriBadCode, headers: {'authorization': 'Token FooBar', 'user-agent': 'wger App'}))
.thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_wrong_code.json'), 200)));
when(client.get(tUriBadCode, headers: anyNamed('headers'))).thenAnswer(
(_) => Future.value(http.Response(fixture('search_ingredient_wrong_code.json'), 200)));
setUp(() {
plan1 = getNutritionalPlan();

View File

@@ -45,9 +45,11 @@ void main() {
final mockExerciseProvider = MockExercisesProvider();
final WorkoutPlan workoutPlan = getWorkout();
when(mockExerciseProvider.findExerciseById(1)).thenReturn(getTestExercises()[0]);
when(mockExerciseProvider.findExerciseById(2)).thenReturn(getTestExercises()[1]);
when(mockExerciseProvider.findExerciseById(3)).thenReturn(getTestExercises()[2]);
final bases = getTestExerciseBases();
when(mockExerciseProvider.findExerciseBaseById(1)).thenReturn(bases[0]);
when(mockExerciseProvider.findExerciseBaseById(2)).thenReturn(bases[1]);
when(mockExerciseProvider.findExerciseBaseById(3)).thenReturn(bases[2]);
return ChangeNotifierProvider<WorkoutPlansProvider>(
create: (context) => WorkoutPlansProvider(
@@ -90,7 +92,7 @@ void main() {
//
expect(find.byType(StartPage), findsOneWidget);
expect(find.text('Your workout today'), findsOneWidget);
expect(find.text('test exercise 1'), findsOneWidget);
expect(find.text('test exercise 2'), findsOneWidget);
expect(find.byIcon(Icons.close), findsOneWidget);
expect(find.byIcon(Icons.menu), findsOneWidget);
expect(find.byIcon(Icons.chevron_left), findsNothing);
@@ -101,7 +103,7 @@ void main() {
//
// Exercise overview page
//
expect(find.text('test exercise 1'), findsOneWidget);
expect(find.text('test exercise 2'), findsOneWidget);
expect(find.byType(ExerciseOverview), findsOneWidget);
expect(find.byIcon(Icons.close), findsOneWidget);
expect(find.byIcon(Icons.menu), findsOneWidget);
@@ -113,7 +115,7 @@ void main() {
//
// Log
//
expect(find.text('test exercise 1'), findsOneWidget);
expect(find.text('test exercise 2'), findsOneWidget);
expect(find.byType(LogPage), findsOneWidget);
expect(find.byType(Form), findsOneWidget);
expect(find.byType(ListTile), findsNWidgets(3), reason: 'Two logs and the switch tile');
@@ -156,7 +158,7 @@ void main() {
//
// Log
//
expect(find.text('test exercise 1'), findsOneWidget);
expect(find.text('test exercise 2'), findsOneWidget);
expect(find.byType(LogPage), findsOneWidget);
expect(find.byType(Form), findsOneWidget);
await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0));

View File

@@ -39,7 +39,7 @@ void main() {
final setting1 = Setting(
setId: 1,
order: 1,
exerciseId: 1,
exerciseBaseId: 1,
repetitionUnitId: 1,
reps: 2,
weightUnitId: 1,

View File

@@ -25,7 +25,7 @@ void main() {
test('Repetitions and weigh units', () async {
final workout = getWorkout();
final set = workout.days.first.sets.first;
final exercise1 = set.exercisesObj[0];
final exercise1 = set.exerciseBasesObj[0];
expect(set.getSmartTextRepr(exercise1), '2 × 10 kg (2 RiR)');
});

View File

@@ -39,7 +39,7 @@ void main() {
final setting1 = Setting(
setId: 1,
order: 1,
exerciseId: 1,
exerciseBaseId: 1,
repetitionUnitId: 1,
reps: 2,
weightUnitId: 1,

View File

@@ -28,7 +28,7 @@ void main() {
log1 = Log(
id: 123,
workoutPlan: 100,
exerciseId: 1,
exerciseBaseId: 1,
reps: 10,
rir: '1.5',
repetitionUnitId: 1,
@@ -39,7 +39,7 @@ void main() {
log2 = Log(
id: 9,
workoutPlan: 42,
exerciseId: 1,
exerciseBaseId: 1,
reps: 10,
rir: '1.5',
repetitionUnitId: 1,

View File

@@ -27,16 +27,16 @@ void main() {
final workout = getWorkout();
expect(workout.logs.length, 3);
final logExercise1 = workout.filterLogsByExercise(getTestExercises()[0]);
final logExercise1 = workout.filterLogsByExerciseBase(getTestExerciseBases()[0]);
expect(logExercise1.length, 2);
expect(logExercise1[0].id, 1);
expect(logExercise1[1].id, 2);
final logExercise2 = workout.filterLogsByExercise(getTestExercises()[1]);
final logExercise2 = workout.filterLogsByExerciseBase(getTestExerciseBases()[1]);
expect(logExercise2.length, 1);
expect(logExercise2[0].id, 3);
expect(workout.filterLogsByExercise(getTestExercises()[2]).length, 0);
expect(workout.filterLogsByExerciseBase(getTestExerciseBases()[2]).length, 0);
});
});
}

View File

@@ -6,14 +6,14 @@ import 'dart:async' as _i10;
import 'dart:ui' as _i11;
import 'package:mockito/mockito.dart' as _i1;
import 'package:wger/models/exercises/base.dart' as _i9;
import 'package:wger/models/exercises/category.dart' as _i4;
import 'package:wger/models/exercises/equipment.dart' as _i5;
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 _i7;
import 'package:wger/models/exercises/muscle.dart' as _i6;
import 'package:wger/models/exercises/language.dart' as _i8;
import 'package:wger/models/exercises/muscle.dart' as _i7;
import 'package:wger/providers/base_provider.dart' as _i2;
import 'package:wger/providers/exercises.dart' as _i8;
import 'package:wger/providers/exercises.dart' as _i9;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -30,19 +30,21 @@ class _FakeWgerBaseProvider_0 extends _i1.Fake implements _i2.WgerBaseProvider {
class _FakeExercise_1 extends _i1.Fake implements _i3.Exercise {}
class _FakeExerciseCategory_2 extends _i1.Fake implements _i4.ExerciseCategory {
class _FakeExerciseBase_2 extends _i1.Fake implements _i4.ExerciseBase {}
class _FakeExerciseCategory_3 extends _i1.Fake implements _i5.ExerciseCategory {
}
class _FakeEquipment_3 extends _i1.Fake implements _i5.Equipment {}
class _FakeEquipment_4 extends _i1.Fake implements _i6.Equipment {}
class _FakeMuscle_4 extends _i1.Fake implements _i6.Muscle {}
class _FakeMuscle_5 extends _i1.Fake implements _i7.Muscle {}
class _FakeLanguage_5 extends _i1.Fake implements _i7.Language {}
class _FakeLanguage_6 extends _i1.Fake implements _i8.Language {}
/// A class which mocks [ExercisesProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
class MockExercisesProvider extends _i1.Mock implements _i9.ExercisesProvider {
MockExercisesProvider() {
_i1.throwOnMissingStub(this);
}
@@ -52,7 +54,7 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
(super.noSuchMethod(Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0()) as _i2.WgerBaseProvider);
@override
set exerciseBases(List<_i9.ExerciseBase>? exercisesBases) =>
set exerciseBases(List<_i4.ExerciseBase>? exercisesBases) =>
super.noSuchMethod(Invocation.setter(#exerciseBases, exercisesBases),
returnValueForMissingStub: null);
@override
@@ -60,48 +62,48 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
super.noSuchMethod(Invocation.setter(#exercises, exercises),
returnValueForMissingStub: null);
@override
List<_i9.ExerciseBase> get filteredExerciseBases =>
List<_i4.ExerciseBase> get filteredExerciseBases =>
(super.noSuchMethod(Invocation.getter(#filteredExerciseBases),
returnValue: <_i9.ExerciseBase>[]) as List<_i9.ExerciseBase>);
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
set filteredExerciseBases(List<_i9.ExerciseBase>? newFilteredExercises) =>
set filteredExerciseBases(List<_i4.ExerciseBase>? newFilteredExercises) =>
super.noSuchMethod(
Invocation.setter(#filteredExerciseBases, newFilteredExercises),
returnValueForMissingStub: null);
@override
Map<int, List<_i9.ExerciseBase>> get exerciseBasesByVariation =>
Map<int, List<_i4.ExerciseBase>> get exerciseBasesByVariation =>
(super.noSuchMethod(Invocation.getter(#exerciseBasesByVariation),
returnValue: <int, List<_i9.ExerciseBase>>{})
as Map<int, List<_i9.ExerciseBase>>);
returnValue: <int, List<_i4.ExerciseBase>>{})
as Map<int, List<_i4.ExerciseBase>>);
@override
List<_i3.Exercise> get items => (super.noSuchMethod(Invocation.getter(#items),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
@override
List<_i9.ExerciseBase> get bases =>
List<_i4.ExerciseBase> get bases =>
(super.noSuchMethod(Invocation.getter(#bases),
returnValue: <_i9.ExerciseBase>[]) as List<_i9.ExerciseBase>);
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
List<_i4.ExerciseCategory> get categories =>
List<_i5.ExerciseCategory> get categories =>
(super.noSuchMethod(Invocation.getter(#categories),
returnValue: <_i4.ExerciseCategory>[]) as List<_i4.ExerciseCategory>);
returnValue: <_i5.ExerciseCategory>[]) as List<_i5.ExerciseCategory>);
@override
List<_i6.Muscle> get muscles =>
List<_i7.Muscle> get muscles =>
(super.noSuchMethod(Invocation.getter(#muscles),
returnValue: <_i6.Muscle>[]) as List<_i6.Muscle>);
returnValue: <_i7.Muscle>[]) as List<_i7.Muscle>);
@override
List<_i5.Equipment> get equipment =>
List<_i6.Equipment> get equipment =>
(super.noSuchMethod(Invocation.getter(#equipment),
returnValue: <_i5.Equipment>[]) as List<_i5.Equipment>);
returnValue: <_i6.Equipment>[]) as List<_i6.Equipment>);
@override
List<_i7.Language> get languages =>
List<_i8.Language> get languages =>
(super.noSuchMethod(Invocation.getter(#languages),
returnValue: <_i7.Language>[]) as List<_i7.Language>);
returnValue: <_i8.Language>[]) as List<_i8.Language>);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
as bool);
@override
_i10.Future<void> setFilters(_i8.Filters? newFilters) => (super.noSuchMethod(
_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>);
@@ -114,40 +116,44 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
void clear() => super.noSuchMethod(Invocation.method(#clear, []),
returnValueForMissingStub: null);
@override
List<_i3.Exercise> findByCategory(_i4.ExerciseCategory? category) =>
List<_i4.ExerciseBase> findByCategory(_i5.ExerciseCategory? category) =>
(super.noSuchMethod(Invocation.method(#findByCategory, [category]),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
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
List<_i3.Exercise> findExercisesByVariationId(int? id,
_i4.ExerciseBase findExerciseBaseById(int? id) =>
(super.noSuchMethod(Invocation.method(#findExerciseBaseById, [id]),
returnValue: _FakeExerciseBase_2()) as _i4.ExerciseBase);
@override
List<_i4.ExerciseBase> findExerciseBasesByVariationId(int? id,
{int? exerciseIdToExclude, int? languageId}) =>
(super.noSuchMethod(
Invocation.method(#findExercisesByVariationId, [
Invocation.method(#findExerciseBasesByVariationId, [
id
], {
#exerciseIdToExclude: exerciseIdToExclude,
#languageId: languageId
}),
returnValue: <_i3.Exercise>[]) as List<_i3.Exercise>);
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
_i4.ExerciseCategory findCategoryById(int? id) =>
_i5.ExerciseCategory findCategoryById(int? id) =>
(super.noSuchMethod(Invocation.method(#findCategoryById, [id]),
returnValue: _FakeExerciseCategory_2()) as _i4.ExerciseCategory);
returnValue: _FakeExerciseCategory_3()) as _i5.ExerciseCategory);
@override
_i5.Equipment findEquipmentById(int? id) =>
_i6.Equipment findEquipmentById(int? id) =>
(super.noSuchMethod(Invocation.method(#findEquipmentById, [id]),
returnValue: _FakeEquipment_3()) as _i5.Equipment);
returnValue: _FakeEquipment_4()) as _i6.Equipment);
@override
_i6.Muscle findMuscleById(int? id) =>
_i7.Muscle findMuscleById(int? id) =>
(super.noSuchMethod(Invocation.method(#findMuscleById, [id]),
returnValue: _FakeMuscle_4()) as _i6.Muscle);
returnValue: _FakeMuscle_5()) as _i7.Muscle);
@override
_i7.Language findLanguageById(int? id) =>
_i8.Language findLanguageById(int? id) =>
(super.noSuchMethod(Invocation.method(#findLanguageById, [id]),
returnValue: _FakeLanguage_5()) as _i7.Language);
returnValue: _FakeLanguage_6()) as _i8.Language);
@override
_i10.Future<void> fetchAndSetCategories() => (super.noSuchMethod(
Invocation.method(#fetchAndSetCategories, []),
@@ -179,23 +185,30 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
returnValue: Future<_i3.Exercise>.value(_FakeExercise_1()))
as _i10.Future<_i3.Exercise>);
@override
_i10.Future<_i4.ExerciseBase> fetchAndSetExerciseBase(int? exerciseBaseId) =>
(super.noSuchMethod(
Invocation.method(#fetchAndSetExerciseBase, [exerciseBaseId]),
returnValue:
Future<_i4.ExerciseBase>.value(_FakeExerciseBase_2()))
as _i10.Future<_i4.ExerciseBase>);
@override
_i10.Future<void> checkExerciseCacheVersion() => (super.noSuchMethod(
Invocation.method(#checkExerciseCacheVersion, []),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
List<_i9.ExerciseBase> mapImages(
dynamic data, List<_i9.ExerciseBase>? bases) =>
List<_i4.ExerciseBase> mapImages(
dynamic data, List<_i4.ExerciseBase>? bases) =>
(super.noSuchMethod(Invocation.method(#mapImages, [data, bases]),
returnValue: <_i9.ExerciseBase>[]) as List<_i9.ExerciseBase>);
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
List<_i9.ExerciseBase> setBaseData(
List<_i4.ExerciseBase> setBaseData(
dynamic data, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#setBaseData, [data, exercises]),
returnValue: <_i9.ExerciseBase>[]) as List<_i9.ExerciseBase>);
returnValue: <_i4.ExerciseBase>[]) as List<_i4.ExerciseBase>);
@override
List<dynamic> mapBases(
List<_i9.ExerciseBase>? bases, List<_i3.Exercise>? exercises) =>
List<_i4.ExerciseBase>? bases, List<_i3.Exercise>? exercises) =>
(super.noSuchMethod(Invocation.method(#mapBases, [bases, exercises]),
returnValue: <dynamic>[]) as List<dynamic>);
@override
@@ -216,12 +229,13 @@ class MockExercisesProvider extends _i1.Mock implements _i8.ExercisesProvider {
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value()) as _i10.Future<void>);
@override
_i10.Future<List<_i3.Exercise>> searchExercise(String? name,
_i10.Future<List<_i4.ExerciseBase>> searchExercise(String? name,
[String? languageCode = r'en']) =>
(super.noSuchMethod(
Invocation.method(#searchExercise, [name, languageCode]),
returnValue: Future<List<_i3.Exercise>>.value(<_i3.Exercise>[]))
as _i10.Future<List<_i3.Exercise>>);
returnValue:
Future<List<_i4.ExerciseBase>>.value(<_i4.ExerciseBase>[]))
as _i10.Future<List<_i4.ExerciseBase>>);
@override
void addListener(_i11.VoidCallback? listener) =>
super.noSuchMethod(Invocation.method(#addListener, [listener]),

View File

@@ -25,6 +25,7 @@ import 'package:wger/models/exercises/muscle.dart';
const tLanguage1 = Language(id: 1, shortName: 'de', fullName: 'Deutsch');
const tLanguage2 = Language(id: 2, shortName: 'en', fullName: 'English');
const tLanguage3 = Language(id: 3, shortName: 'fr', fullName: 'Français');
const tMuscle1 = Muscle(id: 1, name: 'Flutterus maximus', isFront: true);
const tMuscle2 = Muscle(id: 2, name: 'Biceps', isFront: true);
@@ -41,7 +42,7 @@ const tEquipment3 = Equipment(id: 2, name: 'Matress');
final tBase1 = ExerciseBase(
id: 1,
uuid: 'uuid1',
uuid: '364f196c-881b-4839-8bfc-9e8f651521b6',
creationDate: DateTime(2021, 09, 01),
updateDate: DateTime(2021, 09, 10),
category: tCategory1,
@@ -52,7 +53,7 @@ final tBase1 = ExerciseBase(
final tBase2 = ExerciseBase(
id: 2,
uuid: 'uuid2',
uuid: '82415754-fc4c-49ea-8ca7-1516dd36d5a0',
creationDate: DateTime(2021, 08, 01),
updateDate: DateTime(2021, 08, 10),
category: tCategory2,
@@ -63,7 +64,7 @@ final tBase2 = ExerciseBase(
final tBase3 = ExerciseBase(
id: 3,
uuid: 'uuid3',
uuid: 'ca84e2c5-5608-4d6d-ba57-6d4b6b5e7acd',
creationDate: DateTime(2021, 08, 01),
updateDate: DateTime(2021, 08, 01),
category: tCategory3,
@@ -74,31 +75,61 @@ final tBase3 = ExerciseBase(
final tExercise1 = Exercise(
id: 1,
uuid: 'uuid',
uuid: 'f4cc326b-e497-4bd7-a71d-0eb1db522743',
creationDate: DateTime(2021, 1, 15),
name: 'test exercise 1',
description: 'add clever text',
base: tBase1,
baseId: tBase1.id,
language: tLanguage1,
);
final tExercise2 = Exercise(
id: 2,
uuid: '111-2222-44444',
uuid: 'b7f51a1a-0368-4dfc-a03c-d629a4089b4a',
creationDate: DateTime(2021, 1, 15),
name: 'test exercise 2',
description: 'Lorem ipsum etc',
base: tBase2,
baseId: tBase2.id,
language: tLanguage2,
);
final tExercise3 = Exercise(
id: 3,
uuid: 'a3b6c7bb-9d22-4119-a5fc-818584d5e9bc',
uuid: 'd83f572d-add5-48dc-89cf-75f6770284f1',
creationDate: DateTime(2021, 4, 1),
name: 'test exercise 3',
description: 'The man in black fled across the desert, and the gunslinger followed',
base: tBase3,
baseId: tBase3.id,
language: tLanguage3,
);
final tExercise4 = Exercise(
id: 4,
uuid: 'a3e96c1d-b35f-4b0e-9cf4-ca37666cf521',
creationDate: DateTime(2021, 4, 1),
name: 'test exercise 4',
description: 'The man in black fled across the desert, and the gunslinger followed',
baseId: tBase3.id,
language: tLanguage1,
);
final tExercise5 = Exercise(
id: 5,
uuid: '8c49a816-2247-4116-94bb-b5c0ce09c609',
creationDate: DateTime(2021, 4, 1),
name: 'test exercise 5',
description: 'The man in black fled across the desert, and the gunslinger followed',
baseId: tBase3.id,
language: tLanguage2,
);
final tExercise6 = Exercise(
id: 6,
uuid: '259a637e-957f-4fe1-b61b-f56e3793ebcd',
creationDate: DateTime(2021, 4, 1),
name: 'test exercise 5',
description: 'The man in black fled across the desert, and the gunslinger followed',
baseId: tBase3.id,
language: tLanguage2,
);
@@ -107,5 +138,9 @@ List<Exercise> getTestExercises() {
}
List<ExerciseBase> getTestExerciseBases() {
return getTestExercises().map((e) => e.baseObj).toList();
tBase1.exercises = [tExercise1, tExercise2, tExercise3];
tBase2.exercises = [tExercise4, tExercise5];
tBase3.exercises = [tExercise6];
return [tBase1, tBase2, tBase3];
}

View File

@@ -33,10 +33,12 @@ const RepetitionUnit repetitionUnit1 = RepetitionUnit(id: 1, name: 'Repetitions'
const RepetitionUnit repetitionUnit2 = RepetitionUnit(id: 2, name: 'Hours');
WorkoutPlan getWorkout() {
final testBases = getTestExerciseBases();
final setting1 = Setting(
setId: 1,
order: 1,
exerciseId: 1,
exerciseBaseId: 1,
repetitionUnitId: 1,
reps: 2,
weightUnitId: 1,
@@ -45,7 +47,7 @@ WorkoutPlan getWorkout() {
);
setting1.repetitionUnit = repetitionUnit1;
setting1.weightUnit = weightUnit1;
setting1.exercise = getTestExercises()[0];
setting1.exerciseBase = testBases[0];
setting1.weight = 10;
final log1 = Log.empty()
@@ -55,7 +57,7 @@ WorkoutPlan getWorkout() {
..date = DateTime(2021, 5, 1)
..reps = 10
..workoutPlan = 1;
log1.exercise = getTestExercises()[0];
log1.exerciseBase = testBases[0];
log1.weightUnit = weightUnit1;
log1.repetitionUnit = repetitionUnit1;
@@ -66,7 +68,7 @@ WorkoutPlan getWorkout() {
..date = DateTime(2021, 5, 1)
..reps = 12
..workoutPlan = 1;
log2.exercise = getTestExercises()[0];
log2.exerciseBase = testBases[0];
log2.weightUnit = weightUnit1;
log2.repetitionUnit = repetitionUnit1;
@@ -77,7 +79,7 @@ WorkoutPlan getWorkout() {
..date = DateTime(2021, 5, 2)
..reps = 8
..workoutPlan = 1;
log3.exercise = getTestExercises()[1];
log3.exerciseBase = testBases[1];
log3.weightUnit = weightUnit1;
log3.repetitionUnit = repetitionUnit1;
@@ -88,7 +90,7 @@ WorkoutPlan getWorkout() {
order: 1,
comment: 'Important to do exercises correctly',
);
set1.addExercise(getTestExercises()[0]);
set1.addExerciseBase(testBases[0]);
set1.settings.add(setting1);
set1.settingsComputed = [setting1, setting1];