From faf1018f2a7ee0eb03eb3b38ac76efec33b02df1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 29 Jan 2024 12:07:26 +0100 Subject: [PATCH] Fixes for flutter typeahead 5.1.0 Also, add some models for the API response from the search --- integration_test/3_gym_mode.dart | 2 +- lib/models/exercises/exercise_api.dart | 41 ++ .../exercises/exercise_api.freezed.dart | 564 ++++++++++++++++++ lib/models/exercises/exercise_api.g.dart | 44 ++ lib/models/exercises/ingredient_api.dart | 40 ++ .../exercises/ingredient_api.freezed.dart | 533 +++++++++++++++++ lib/models/exercises/ingredient_api.g.dart | 50 ++ lib/providers/exercises.dart | 9 +- lib/providers/nutrition.dart | 5 +- lib/widgets/nutrition/widgets.dart | 70 ++- lib/widgets/workouts/forms.dart | 178 +++--- pubspec.lock | 6 +- pubspec.yaml | 2 +- test/auth/auth_screen_test.dart | 4 +- test/exercises/contribute_exercise_test.dart | 2 +- .../exercise_provider_load_test.dart | 2 +- test/exercises/exercise_provider_test.dart | 12 +- .../exercises_detail_widget_test.dart | 2 +- test/exercises/model_exercise_test.dart | 2 +- .../nutritional_meal_form_test.mocks.dart | 14 +- .../nutritional_meal_item_form_test.dart | 16 +- .../nutritional_plan_form_test.mocks.dart | 14 +- test/workout/gym_mode_screen_test.dart | 2 +- test/workout/workout_plan_model_test.dart | 6 +- test/workout/workout_set_form_test.dart | 9 + test_data/exercises.dart | 2 +- test_data/screenshots_exercises.dart | 24 +- test_data/workouts.dart | 2 +- 28 files changed, 1485 insertions(+), 172 deletions(-) create mode 100644 lib/models/exercises/ingredient_api.dart create mode 100644 lib/models/exercises/ingredient_api.freezed.dart create mode 100644 lib/models/exercises/ingredient_api.g.dart diff --git a/integration_test/3_gym_mode.dart b/integration_test/3_gym_mode.dart index 51ccbc75..88b50fbe 100644 --- a/integration_test/3_gym_mode.dart +++ b/integration_test/3_gym_mode.dart @@ -15,7 +15,7 @@ import '../test_data/workouts.dart'; Widget createGymModeScreen({locale = 'en'}) { final key = GlobalKey(); - final bases = getTestExerciseBases(); + final bases = getTestExercises(); final workout = getWorkout(exercises: getScreenshotExercises()); final mockExerciseProvider = MockExercisesProvider(); diff --git a/lib/models/exercises/exercise_api.dart b/lib/models/exercises/exercise_api.dart index 44d72c7f..fb9c36e0 100644 --- a/lib/models/exercises/exercise_api.dart +++ b/lib/models/exercises/exercise_api.dart @@ -48,3 +48,44 @@ class ExerciseApiData with _$ExerciseApiData { factory ExerciseApiData.fromJson(Map json) => _$ExerciseApiDataFromJson(json); } + +/// Model for the search results returned from the /api/v2/exercise/search endpoint +/// +@freezed +class ExerciseSearchDetails with _$ExerciseSearchDetails { + factory ExerciseSearchDetails({ + // ignore: invalid_annotation_target + @JsonKey(name: 'id') required int translationId, + // ignore: invalid_annotation_target + @JsonKey(name: 'base_id') required int exerciseId, + required String name, + required String category, + required String? image, + // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') required String? imageThumbnail, + }) = _ExerciseSearchDetails; + + factory ExerciseSearchDetails.fromJson(Map json) => + _$ExerciseSearchDetailsFromJson(json); +} + +@freezed +class ExerciseSearchEntry with _$ExerciseSearchEntry { + factory ExerciseSearchEntry({ + required String value, + required ExerciseSearchDetails data, + }) = _ExerciseSearchEntry; + + factory ExerciseSearchEntry.fromJson(Map json) => + _$ExerciseSearchEntryFromJson(json); +} + +@freezed +class ExerciseApiSearch with _$ExerciseApiSearch { + factory ExerciseApiSearch({ + required List suggestions, + }) = _ExerciseApiSearch; + + factory ExerciseApiSearch.fromJson(Map json) => + _$ExerciseApiSearchFromJson(json); +} diff --git a/lib/models/exercises/exercise_api.freezed.dart b/lib/models/exercises/exercise_api.freezed.dart index ecc6aa7f..2e41abb3 100644 --- a/lib/models/exercises/exercise_api.freezed.dart +++ b/lib/models/exercises/exercise_api.freezed.dart @@ -545,3 +545,567 @@ abstract class _ExerciseBaseData implements ExerciseApiData { _$$ExerciseBaseDataImplCopyWith<_$ExerciseBaseDataImpl> get copyWith => throw _privateConstructorUsedError; } + +ExerciseSearchDetails _$ExerciseSearchDetailsFromJson(Map json) { + return _ExerciseSearchDetails.fromJson(json); +} + +/// @nodoc +mixin _$ExerciseSearchDetails { +// ignore: invalid_annotation_target + @JsonKey(name: 'id') + int get translationId => throw _privateConstructorUsedError; // ignore: invalid_annotation_target + @JsonKey(name: 'base_id') + int get exerciseId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get category => throw _privateConstructorUsedError; + String? get image => throw _privateConstructorUsedError; // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') + String? get imageThumbnail => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ExerciseSearchDetailsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ExerciseSearchDetailsCopyWith<$Res> { + factory $ExerciseSearchDetailsCopyWith( + ExerciseSearchDetails value, $Res Function(ExerciseSearchDetails) then) = + _$ExerciseSearchDetailsCopyWithImpl<$Res, ExerciseSearchDetails>; + @useResult + $Res call( + {@JsonKey(name: 'id') int translationId, + @JsonKey(name: 'base_id') int exerciseId, + String name, + String category, + String? image, + @JsonKey(name: 'image_thumbnail') String? imageThumbnail}); +} + +/// @nodoc +class _$ExerciseSearchDetailsCopyWithImpl<$Res, $Val extends ExerciseSearchDetails> + implements $ExerciseSearchDetailsCopyWith<$Res> { + _$ExerciseSearchDetailsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? translationId = null, + Object? exerciseId = null, + Object? name = null, + Object? category = null, + Object? image = freezed, + Object? imageThumbnail = freezed, + }) { + return _then(_value.copyWith( + translationId: null == translationId + ? _value.translationId + : translationId // ignore: cast_nullable_to_non_nullable + as int, + exerciseId: null == exerciseId + ? _value.exerciseId + : exerciseId // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + category: null == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + imageThumbnail: freezed == imageThumbnail + ? _value.imageThumbnail + : imageThumbnail // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ExerciseSearchDetailsImplCopyWith<$Res> + implements $ExerciseSearchDetailsCopyWith<$Res> { + factory _$$ExerciseSearchDetailsImplCopyWith( + _$ExerciseSearchDetailsImpl value, $Res Function(_$ExerciseSearchDetailsImpl) then) = + __$$ExerciseSearchDetailsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: 'id') int translationId, + @JsonKey(name: 'base_id') int exerciseId, + String name, + String category, + String? image, + @JsonKey(name: 'image_thumbnail') String? imageThumbnail}); +} + +/// @nodoc +class __$$ExerciseSearchDetailsImplCopyWithImpl<$Res> + extends _$ExerciseSearchDetailsCopyWithImpl<$Res, _$ExerciseSearchDetailsImpl> + implements _$$ExerciseSearchDetailsImplCopyWith<$Res> { + __$$ExerciseSearchDetailsImplCopyWithImpl( + _$ExerciseSearchDetailsImpl _value, $Res Function(_$ExerciseSearchDetailsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? translationId = null, + Object? exerciseId = null, + Object? name = null, + Object? category = null, + Object? image = freezed, + Object? imageThumbnail = freezed, + }) { + return _then(_$ExerciseSearchDetailsImpl( + translationId: null == translationId + ? _value.translationId + : translationId // ignore: cast_nullable_to_non_nullable + as int, + exerciseId: null == exerciseId + ? _value.exerciseId + : exerciseId // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + category: null == category + ? _value.category + : category // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + imageThumbnail: freezed == imageThumbnail + ? _value.imageThumbnail + : imageThumbnail // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ExerciseSearchDetailsImpl implements _ExerciseSearchDetails { + _$ExerciseSearchDetailsImpl( + {@JsonKey(name: 'id') required this.translationId, + @JsonKey(name: 'base_id') required this.exerciseId, + required this.name, + required this.category, + required this.image, + @JsonKey(name: 'image_thumbnail') required this.imageThumbnail}); + + factory _$ExerciseSearchDetailsImpl.fromJson(Map json) => + _$$ExerciseSearchDetailsImplFromJson(json); + +// ignore: invalid_annotation_target + @override + @JsonKey(name: 'id') + final int translationId; +// ignore: invalid_annotation_target + @override + @JsonKey(name: 'base_id') + final int exerciseId; + @override + final String name; + @override + final String category; + @override + final String? image; +// ignore: invalid_annotation_target + @override + @JsonKey(name: 'image_thumbnail') + final String? imageThumbnail; + + @override + String toString() { + return 'ExerciseSearchDetails(translationId: $translationId, exerciseId: $exerciseId, name: $name, category: $category, image: $image, imageThumbnail: $imageThumbnail)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ExerciseSearchDetailsImpl && + (identical(other.translationId, translationId) || + other.translationId == translationId) && + (identical(other.exerciseId, exerciseId) || other.exerciseId == exerciseId) && + (identical(other.name, name) || other.name == name) && + (identical(other.category, category) || other.category == category) && + (identical(other.image, image) || other.image == image) && + (identical(other.imageThumbnail, imageThumbnail) || + other.imageThumbnail == imageThumbnail)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, translationId, exerciseId, name, category, image, imageThumbnail); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ExerciseSearchDetailsImplCopyWith<_$ExerciseSearchDetailsImpl> get copyWith => + __$$ExerciseSearchDetailsImplCopyWithImpl<_$ExerciseSearchDetailsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ExerciseSearchDetailsImplToJson( + this, + ); + } +} + +abstract class _ExerciseSearchDetails implements ExerciseSearchDetails { + factory _ExerciseSearchDetails( + {@JsonKey(name: 'id') required final int translationId, + @JsonKey(name: 'base_id') required final int exerciseId, + required final String name, + required final String category, + required final String? image, + @JsonKey(name: 'image_thumbnail') required final String? imageThumbnail}) = + _$ExerciseSearchDetailsImpl; + + factory _ExerciseSearchDetails.fromJson(Map json) = + _$ExerciseSearchDetailsImpl.fromJson; + + @override // ignore: invalid_annotation_target + @JsonKey(name: 'id') + int get translationId; + @override // ignore: invalid_annotation_target + @JsonKey(name: 'base_id') + int get exerciseId; + @override + String get name; + @override + String get category; + @override + String? get image; + @override // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') + String? get imageThumbnail; + @override + @JsonKey(ignore: true) + _$$ExerciseSearchDetailsImplCopyWith<_$ExerciseSearchDetailsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ExerciseSearchEntry _$ExerciseSearchEntryFromJson(Map json) { + return _ExerciseSearchEntry.fromJson(json); +} + +/// @nodoc +mixin _$ExerciseSearchEntry { + String get value => throw _privateConstructorUsedError; + ExerciseSearchDetails get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ExerciseSearchEntryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ExerciseSearchEntryCopyWith<$Res> { + factory $ExerciseSearchEntryCopyWith( + ExerciseSearchEntry value, $Res Function(ExerciseSearchEntry) then) = + _$ExerciseSearchEntryCopyWithImpl<$Res, ExerciseSearchEntry>; + @useResult + $Res call({String value, ExerciseSearchDetails data}); + + $ExerciseSearchDetailsCopyWith<$Res> get data; +} + +/// @nodoc +class _$ExerciseSearchEntryCopyWithImpl<$Res, $Val extends ExerciseSearchEntry> + implements $ExerciseSearchEntryCopyWith<$Res> { + _$ExerciseSearchEntryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + Object? data = null, + }) { + return _then(_value.copyWith( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as ExerciseSearchDetails, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $ExerciseSearchDetailsCopyWith<$Res> get data { + return $ExerciseSearchDetailsCopyWith<$Res>(_value.data, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ExerciseSearchEntryImplCopyWith<$Res> + implements $ExerciseSearchEntryCopyWith<$Res> { + factory _$$ExerciseSearchEntryImplCopyWith( + _$ExerciseSearchEntryImpl value, $Res Function(_$ExerciseSearchEntryImpl) then) = + __$$ExerciseSearchEntryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String value, ExerciseSearchDetails data}); + + @override + $ExerciseSearchDetailsCopyWith<$Res> get data; +} + +/// @nodoc +class __$$ExerciseSearchEntryImplCopyWithImpl<$Res> + extends _$ExerciseSearchEntryCopyWithImpl<$Res, _$ExerciseSearchEntryImpl> + implements _$$ExerciseSearchEntryImplCopyWith<$Res> { + __$$ExerciseSearchEntryImplCopyWithImpl( + _$ExerciseSearchEntryImpl _value, $Res Function(_$ExerciseSearchEntryImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + Object? data = null, + }) { + return _then(_$ExerciseSearchEntryImpl( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as ExerciseSearchDetails, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ExerciseSearchEntryImpl implements _ExerciseSearchEntry { + _$ExerciseSearchEntryImpl({required this.value, required this.data}); + + factory _$ExerciseSearchEntryImpl.fromJson(Map json) => + _$$ExerciseSearchEntryImplFromJson(json); + + @override + final String value; + @override + final ExerciseSearchDetails data; + + @override + String toString() { + return 'ExerciseSearchEntry(value: $value, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ExerciseSearchEntryImpl && + (identical(other.value, value) || other.value == value) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, value, data); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ExerciseSearchEntryImplCopyWith<_$ExerciseSearchEntryImpl> get copyWith => + __$$ExerciseSearchEntryImplCopyWithImpl<_$ExerciseSearchEntryImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ExerciseSearchEntryImplToJson( + this, + ); + } +} + +abstract class _ExerciseSearchEntry implements ExerciseSearchEntry { + factory _ExerciseSearchEntry( + {required final String value, + required final ExerciseSearchDetails data}) = _$ExerciseSearchEntryImpl; + + factory _ExerciseSearchEntry.fromJson(Map json) = + _$ExerciseSearchEntryImpl.fromJson; + + @override + String get value; + @override + ExerciseSearchDetails get data; + @override + @JsonKey(ignore: true) + _$$ExerciseSearchEntryImplCopyWith<_$ExerciseSearchEntryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ExerciseApiSearch _$ExerciseApiSearchFromJson(Map json) { + return _ExerciseApiSearch.fromJson(json); +} + +/// @nodoc +mixin _$ExerciseApiSearch { + List get suggestions => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ExerciseApiSearchCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ExerciseApiSearchCopyWith<$Res> { + factory $ExerciseApiSearchCopyWith( + ExerciseApiSearch value, $Res Function(ExerciseApiSearch) then) = + _$ExerciseApiSearchCopyWithImpl<$Res, ExerciseApiSearch>; + @useResult + $Res call({List suggestions}); +} + +/// @nodoc +class _$ExerciseApiSearchCopyWithImpl<$Res, $Val extends ExerciseApiSearch> + implements $ExerciseApiSearchCopyWith<$Res> { + _$ExerciseApiSearchCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? suggestions = null, + }) { + return _then(_value.copyWith( + suggestions: null == suggestions + ? _value.suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ExerciseApiSearchImplCopyWith<$Res> implements $ExerciseApiSearchCopyWith<$Res> { + factory _$$ExerciseApiSearchImplCopyWith( + _$ExerciseApiSearchImpl value, $Res Function(_$ExerciseApiSearchImpl) then) = + __$$ExerciseApiSearchImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List suggestions}); +} + +/// @nodoc +class __$$ExerciseApiSearchImplCopyWithImpl<$Res> + extends _$ExerciseApiSearchCopyWithImpl<$Res, _$ExerciseApiSearchImpl> + implements _$$ExerciseApiSearchImplCopyWith<$Res> { + __$$ExerciseApiSearchImplCopyWithImpl( + _$ExerciseApiSearchImpl _value, $Res Function(_$ExerciseApiSearchImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? suggestions = null, + }) { + return _then(_$ExerciseApiSearchImpl( + suggestions: null == suggestions + ? _value._suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ExerciseApiSearchImpl implements _ExerciseApiSearch { + _$ExerciseApiSearchImpl({required final List suggestions}) + : _suggestions = suggestions; + + factory _$ExerciseApiSearchImpl.fromJson(Map json) => + _$$ExerciseApiSearchImplFromJson(json); + + final List _suggestions; + @override + List get suggestions { + if (_suggestions is EqualUnmodifiableListView) return _suggestions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_suggestions); + } + + @override + String toString() { + return 'ExerciseApiSearch(suggestions: $suggestions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ExerciseApiSearchImpl && + const DeepCollectionEquality().equals(other._suggestions, _suggestions)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_suggestions)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ExerciseApiSearchImplCopyWith<_$ExerciseApiSearchImpl> get copyWith => + __$$ExerciseApiSearchImplCopyWithImpl<_$ExerciseApiSearchImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ExerciseApiSearchImplToJson( + this, + ); + } +} + +abstract class _ExerciseApiSearch implements ExerciseApiSearch { + factory _ExerciseApiSearch({required final List suggestions}) = + _$ExerciseApiSearchImpl; + + factory _ExerciseApiSearch.fromJson(Map json) = _$ExerciseApiSearchImpl.fromJson; + + @override + List get suggestions; + @override + @JsonKey(ignore: true) + _$$ExerciseApiSearchImplCopyWith<_$ExerciseApiSearchImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/exercises/exercise_api.g.dart b/lib/models/exercises/exercise_api.g.dart index 3fc31410..2eead130 100644 --- a/lib/models/exercises/exercise_api.g.dart +++ b/lib/models/exercises/exercise_api.g.dart @@ -56,3 +56,47 @@ Map _$$ExerciseBaseDataImplToJson(_$ExerciseBaseDataImpl instan 'author_history': instance.authors, 'total_authors_history': instance.authorsGlobal, }; + +_$ExerciseSearchDetailsImpl _$$ExerciseSearchDetailsImplFromJson(Map json) => + _$ExerciseSearchDetailsImpl( + translationId: json['id'] as int, + exerciseId: json['base_id'] as int, + name: json['name'] as String, + category: json['category'] as String, + image: json['image'] as String?, + imageThumbnail: json['image_thumbnail'] as String?, + ); + +Map _$$ExerciseSearchDetailsImplToJson(_$ExerciseSearchDetailsImpl instance) => + { + 'id': instance.translationId, + 'base_id': instance.exerciseId, + 'name': instance.name, + 'category': instance.category, + 'image': instance.image, + 'image_thumbnail': instance.imageThumbnail, + }; + +_$ExerciseSearchEntryImpl _$$ExerciseSearchEntryImplFromJson(Map json) => + _$ExerciseSearchEntryImpl( + value: json['value'] as String, + data: ExerciseSearchDetails.fromJson(json['data'] as Map), + ); + +Map _$$ExerciseSearchEntryImplToJson(_$ExerciseSearchEntryImpl instance) => + { + 'value': instance.value, + 'data': instance.data, + }; + +_$ExerciseApiSearchImpl _$$ExerciseApiSearchImplFromJson(Map json) => + _$ExerciseApiSearchImpl( + suggestions: (json['suggestions'] as List) + .map((e) => ExerciseSearchEntry.fromJson(e as Map)) + .toList(), + ); + +Map _$$ExerciseApiSearchImplToJson(_$ExerciseApiSearchImpl instance) => + { + 'suggestions': instance.suggestions, + }; diff --git a/lib/models/exercises/ingredient_api.dart b/lib/models/exercises/ingredient_api.dart new file mode 100644 index 00000000..68d7c33a --- /dev/null +++ b/lib/models/exercises/ingredient_api.dart @@ -0,0 +1,40 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'ingredient_api.freezed.dart'; +part 'ingredient_api.g.dart'; + +/// Model for the search results returned from the /api/v2/ingredient/search endpoint +@freezed +class IngredientApiSearchDetails with _$IngredientApiSearchDetails { + factory IngredientApiSearchDetails({ + required int id, + required String name, + required String? image, + // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') required String? imageThumbnail, + }) = _IngredientApiSearchDetails; + + factory IngredientApiSearchDetails.fromJson(Map json) => + _$IngredientApiSearchDetailsFromJson(json); +} + +@freezed +class IngredientApiSearchEntry with _$IngredientApiSearchEntry { + factory IngredientApiSearchEntry({ + required String value, + required IngredientApiSearchDetails data, + }) = _IngredientApiSearchEntry; + + factory IngredientApiSearchEntry.fromJson(Map json) => + _$IngredientApiSearchEntryFromJson(json); +} + +@freezed +class IngredientApiSearch with _$IngredientApiSearch { + factory IngredientApiSearch({ + required List suggestions, + }) = _IngredientApiSearch; + + factory IngredientApiSearch.fromJson(Map json) => + _$IngredientApiSearchFromJson(json); +} diff --git a/lib/models/exercises/ingredient_api.freezed.dart b/lib/models/exercises/ingredient_api.freezed.dart new file mode 100644 index 00000000..32131031 --- /dev/null +++ b/lib/models/exercises/ingredient_api.freezed.dart @@ -0,0 +1,533 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'ingredient_api.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +IngredientApiSearchDetails _$IngredientApiSearchDetailsFromJson(Map json) { + return _IngredientApiSearchDetails.fromJson(json); +} + +/// @nodoc +mixin _$IngredientApiSearchDetails { + int get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String? get image => throw _privateConstructorUsedError; // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') + String? get imageThumbnail => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IngredientApiSearchDetailsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IngredientApiSearchDetailsCopyWith<$Res> { + factory $IngredientApiSearchDetailsCopyWith( + IngredientApiSearchDetails value, $Res Function(IngredientApiSearchDetails) then) = + _$IngredientApiSearchDetailsCopyWithImpl<$Res, IngredientApiSearchDetails>; + @useResult + $Res call( + {int id, + String name, + String? image, + @JsonKey(name: 'image_thumbnail') String? imageThumbnail}); +} + +/// @nodoc +class _$IngredientApiSearchDetailsCopyWithImpl<$Res, $Val extends IngredientApiSearchDetails> + implements $IngredientApiSearchDetailsCopyWith<$Res> { + _$IngredientApiSearchDetailsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? image = freezed, + Object? imageThumbnail = freezed, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + imageThumbnail: freezed == imageThumbnail + ? _value.imageThumbnail + : imageThumbnail // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IngredientApiSearchDetailsImplCopyWith<$Res> + implements $IngredientApiSearchDetailsCopyWith<$Res> { + factory _$$IngredientApiSearchDetailsImplCopyWith(_$IngredientApiSearchDetailsImpl value, + $Res Function(_$IngredientApiSearchDetailsImpl) then) = + __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int id, + String name, + String? image, + @JsonKey(name: 'image_thumbnail') String? imageThumbnail}); +} + +/// @nodoc +class __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res> + extends _$IngredientApiSearchDetailsCopyWithImpl<$Res, _$IngredientApiSearchDetailsImpl> + implements _$$IngredientApiSearchDetailsImplCopyWith<$Res> { + __$$IngredientApiSearchDetailsImplCopyWithImpl(_$IngredientApiSearchDetailsImpl _value, + $Res Function(_$IngredientApiSearchDetailsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? image = freezed, + Object? imageThumbnail = freezed, + }) { + return _then(_$IngredientApiSearchDetailsImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + image: freezed == image + ? _value.image + : image // ignore: cast_nullable_to_non_nullable + as String?, + imageThumbnail: freezed == imageThumbnail + ? _value.imageThumbnail + : imageThumbnail // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IngredientApiSearchDetailsImpl implements _IngredientApiSearchDetails { + _$IngredientApiSearchDetailsImpl( + {required this.id, + required this.name, + required this.image, + @JsonKey(name: 'image_thumbnail') required this.imageThumbnail}); + + factory _$IngredientApiSearchDetailsImpl.fromJson(Map json) => + _$$IngredientApiSearchDetailsImplFromJson(json); + + @override + final int id; + @override + final String name; + @override + final String? image; +// ignore: invalid_annotation_target + @override + @JsonKey(name: 'image_thumbnail') + final String? imageThumbnail; + + @override + String toString() { + return 'IngredientApiSearchDetails(id: $id, name: $name, image: $image, imageThumbnail: $imageThumbnail)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IngredientApiSearchDetailsImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.image, image) || other.image == image) && + (identical(other.imageThumbnail, imageThumbnail) || + other.imageThumbnail == imageThumbnail)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, name, image, imageThumbnail); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl> get copyWith => + __$$IngredientApiSearchDetailsImplCopyWithImpl<_$IngredientApiSearchDetailsImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$IngredientApiSearchDetailsImplToJson( + this, + ); + } +} + +abstract class _IngredientApiSearchDetails implements IngredientApiSearchDetails { + factory _IngredientApiSearchDetails( + {required final int id, + required final String name, + required final String? image, + @JsonKey(name: 'image_thumbnail') required final String? imageThumbnail}) = + _$IngredientApiSearchDetailsImpl; + + factory _IngredientApiSearchDetails.fromJson(Map json) = + _$IngredientApiSearchDetailsImpl.fromJson; + + @override + int get id; + @override + String get name; + @override + String? get image; + @override // ignore: invalid_annotation_target + @JsonKey(name: 'image_thumbnail') + String? get imageThumbnail; + @override + @JsonKey(ignore: true) + _$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl> get copyWith => + throw _privateConstructorUsedError; +} + +IngredientApiSearchEntry _$IngredientApiSearchEntryFromJson(Map json) { + return _IngredientApiSearchEntry.fromJson(json); +} + +/// @nodoc +mixin _$IngredientApiSearchEntry { + String get value => throw _privateConstructorUsedError; + IngredientApiSearchDetails get data => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IngredientApiSearchEntryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IngredientApiSearchEntryCopyWith<$Res> { + factory $IngredientApiSearchEntryCopyWith( + IngredientApiSearchEntry value, $Res Function(IngredientApiSearchEntry) then) = + _$IngredientApiSearchEntryCopyWithImpl<$Res, IngredientApiSearchEntry>; + @useResult + $Res call({String value, IngredientApiSearchDetails data}); + + $IngredientApiSearchDetailsCopyWith<$Res> get data; +} + +/// @nodoc +class _$IngredientApiSearchEntryCopyWithImpl<$Res, $Val extends IngredientApiSearchEntry> + implements $IngredientApiSearchEntryCopyWith<$Res> { + _$IngredientApiSearchEntryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + Object? data = null, + }) { + return _then(_value.copyWith( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as IngredientApiSearchDetails, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $IngredientApiSearchDetailsCopyWith<$Res> get data { + return $IngredientApiSearchDetailsCopyWith<$Res>(_value.data, (value) { + return _then(_value.copyWith(data: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$IngredientApiSearchEntryImplCopyWith<$Res> + implements $IngredientApiSearchEntryCopyWith<$Res> { + factory _$$IngredientApiSearchEntryImplCopyWith(_$IngredientApiSearchEntryImpl value, + $Res Function(_$IngredientApiSearchEntryImpl) then) = + __$$IngredientApiSearchEntryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String value, IngredientApiSearchDetails data}); + + @override + $IngredientApiSearchDetailsCopyWith<$Res> get data; +} + +/// @nodoc +class __$$IngredientApiSearchEntryImplCopyWithImpl<$Res> + extends _$IngredientApiSearchEntryCopyWithImpl<$Res, _$IngredientApiSearchEntryImpl> + implements _$$IngredientApiSearchEntryImplCopyWith<$Res> { + __$$IngredientApiSearchEntryImplCopyWithImpl( + _$IngredientApiSearchEntryImpl _value, $Res Function(_$IngredientApiSearchEntryImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? value = null, + Object? data = null, + }) { + return _then(_$IngredientApiSearchEntryImpl( + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + data: null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as IngredientApiSearchDetails, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IngredientApiSearchEntryImpl implements _IngredientApiSearchEntry { + _$IngredientApiSearchEntryImpl({required this.value, required this.data}); + + factory _$IngredientApiSearchEntryImpl.fromJson(Map json) => + _$$IngredientApiSearchEntryImplFromJson(json); + + @override + final String value; + @override + final IngredientApiSearchDetails data; + + @override + String toString() { + return 'IngredientApiSearchEntry(value: $value, data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IngredientApiSearchEntryImpl && + (identical(other.value, value) || other.value == value) && + (identical(other.data, data) || other.data == data)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, value, data); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl> get copyWith => + __$$IngredientApiSearchEntryImplCopyWithImpl<_$IngredientApiSearchEntryImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$IngredientApiSearchEntryImplToJson( + this, + ); + } +} + +abstract class _IngredientApiSearchEntry implements IngredientApiSearchEntry { + factory _IngredientApiSearchEntry( + {required final String value, + required final IngredientApiSearchDetails data}) = _$IngredientApiSearchEntryImpl; + + factory _IngredientApiSearchEntry.fromJson(Map json) = + _$IngredientApiSearchEntryImpl.fromJson; + + @override + String get value; + @override + IngredientApiSearchDetails get data; + @override + @JsonKey(ignore: true) + _$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +IngredientApiSearch _$IngredientApiSearchFromJson(Map json) { + return _IngredientApiSearch.fromJson(json); +} + +/// @nodoc +mixin _$IngredientApiSearch { + List get suggestions => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IngredientApiSearchCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IngredientApiSearchCopyWith<$Res> { + factory $IngredientApiSearchCopyWith( + IngredientApiSearch value, $Res Function(IngredientApiSearch) then) = + _$IngredientApiSearchCopyWithImpl<$Res, IngredientApiSearch>; + @useResult + $Res call({List suggestions}); +} + +/// @nodoc +class _$IngredientApiSearchCopyWithImpl<$Res, $Val extends IngredientApiSearch> + implements $IngredientApiSearchCopyWith<$Res> { + _$IngredientApiSearchCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? suggestions = null, + }) { + return _then(_value.copyWith( + suggestions: null == suggestions + ? _value.suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IngredientApiSearchImplCopyWith<$Res> + implements $IngredientApiSearchCopyWith<$Res> { + factory _$$IngredientApiSearchImplCopyWith( + _$IngredientApiSearchImpl value, $Res Function(_$IngredientApiSearchImpl) then) = + __$$IngredientApiSearchImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List suggestions}); +} + +/// @nodoc +class __$$IngredientApiSearchImplCopyWithImpl<$Res> + extends _$IngredientApiSearchCopyWithImpl<$Res, _$IngredientApiSearchImpl> + implements _$$IngredientApiSearchImplCopyWith<$Res> { + __$$IngredientApiSearchImplCopyWithImpl( + _$IngredientApiSearchImpl _value, $Res Function(_$IngredientApiSearchImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? suggestions = null, + }) { + return _then(_$IngredientApiSearchImpl( + suggestions: null == suggestions + ? _value._suggestions + : suggestions // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IngredientApiSearchImpl implements _IngredientApiSearch { + _$IngredientApiSearchImpl({required final List suggestions}) + : _suggestions = suggestions; + + factory _$IngredientApiSearchImpl.fromJson(Map json) => + _$$IngredientApiSearchImplFromJson(json); + + final List _suggestions; + @override + List get suggestions { + if (_suggestions is EqualUnmodifiableListView) return _suggestions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_suggestions); + } + + @override + String toString() { + return 'IngredientApiSearch(suggestions: $suggestions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IngredientApiSearchImpl && + const DeepCollectionEquality().equals(other._suggestions, _suggestions)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_suggestions)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith => + __$$IngredientApiSearchImplCopyWithImpl<_$IngredientApiSearchImpl>(this, _$identity); + + @override + Map toJson() { + return _$$IngredientApiSearchImplToJson( + this, + ); + } +} + +abstract class _IngredientApiSearch implements IngredientApiSearch { + factory _IngredientApiSearch({required final List suggestions}) = + _$IngredientApiSearchImpl; + + factory _IngredientApiSearch.fromJson(Map json) = + _$IngredientApiSearchImpl.fromJson; + + @override + List get suggestions; + @override + @JsonKey(ignore: true) + _$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/exercises/ingredient_api.g.dart b/lib/models/exercises/ingredient_api.g.dart new file mode 100644 index 00000000..c962982d --- /dev/null +++ b/lib/models/exercises/ingredient_api.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ingredient_api.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IngredientApiSearchDetailsImpl _$$IngredientApiSearchDetailsImplFromJson( + Map json) => + _$IngredientApiSearchDetailsImpl( + id: json['id'] as int, + name: json['name'] as String, + image: json['image'] as String?, + imageThumbnail: json['image_thumbnail'] as String?, + ); + +Map _$$IngredientApiSearchDetailsImplToJson( + _$IngredientApiSearchDetailsImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'image': instance.image, + 'image_thumbnail': instance.imageThumbnail, + }; + +_$IngredientApiSearchEntryImpl _$$IngredientApiSearchEntryImplFromJson(Map json) => + _$IngredientApiSearchEntryImpl( + value: json['value'] as String, + data: IngredientApiSearchDetails.fromJson(json['data'] as Map), + ); + +Map _$$IngredientApiSearchEntryImplToJson( + _$IngredientApiSearchEntryImpl instance) => + { + 'value': instance.value, + 'data': instance.data, + }; + +_$IngredientApiSearchImpl _$$IngredientApiSearchImplFromJson(Map json) => + _$IngredientApiSearchImpl( + suggestions: (json['suggestions'] as List) + .map((e) => IngredientApiSearchEntry.fromJson(e as Map)) + .toList(), + ); + +Map _$$IngredientApiSearchImplToJson(_$IngredientApiSearchImpl instance) => + { + 'suggestions': instance.suggestions, + }; diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index eed534b7..3dabccad 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -649,12 +649,9 @@ class ExercisesProvider with ChangeNotifier { ), ); - // Process the response - return Future.wait( - (result['suggestions'] as List).map>( - (entry) => fetchAndSetExercise(entry['data']['base_id']), - ), - ); + // Load the ingredients + final results = ExerciseApiSearch.fromJson(result); + return Future.wait(results.suggestions.map((e) => fetchAndSetExercise(e.data.exerciseId))); } } diff --git a/lib/providers/nutrition.dart b/lib/providers/nutrition.dart index 789008a3..2fd2cb2a 100644 --- a/lib/providers/nutrition.dart +++ b/lib/providers/nutrition.dart @@ -24,6 +24,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:wger/exceptions/http_exception.dart'; import 'package:wger/exceptions/no_such_entry_exception.dart'; import 'package:wger/helpers/consts.dart'; +import 'package:wger/models/exercises/ingredient_api.dart'; import 'package:wger/models/nutrition/image.dart'; import 'package:wger/models/nutrition/ingredient.dart'; import 'package:wger/models/nutrition/log.dart'; @@ -331,7 +332,7 @@ class NutritionPlansProvider with ChangeNotifier { } /// Searches for an ingredient - Future searchIngredient( + Future> searchIngredient( String name, { String languageCode = 'en', bool searchEnglish = false, @@ -352,7 +353,7 @@ class NutritionPlansProvider with ChangeNotifier { ); // Process the response - return response['suggestions']; + return IngredientApiSearch.fromJson(response).suggestions; } /// Searches for an ingredient with code diff --git a/lib/widgets/nutrition/widgets.dart b/lib/widgets/nutrition/widgets.dart index 1af9fc47..4800cf50 100644 --- a/lib/widgets/nutrition/widgets.dart +++ b/lib/widgets/nutrition/widgets.dart @@ -17,8 +17,7 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -//import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; +import 'package:flutter/services.dart'; //import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; @@ -27,6 +26,7 @@ import 'package:provider/provider.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/helpers/platform.dart'; import 'package:wger/helpers/ui.dart'; +import 'package:wger/models/exercises/ingredient_api.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/widgets/core/core.dart'; @@ -90,44 +90,56 @@ class _IngredientTypeaheadState extends State { Widget build(BuildContext context) { return Column( children: [ - TypeAheadFormField( - textFieldConfiguration: TextFieldConfiguration( - controller: widget._ingredientController, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - labelText: AppLocalizations.of(context).searchIngredient, - suffixIcon: (widget.showScanner && !isDesktop) ? scanButton() : null, - ), - ), - suggestionsCallback: (pattern) async { + TypeAheadField( + controller: widget._ingredientController, + builder: (context, controller, focusNode) { + return TextFormField( + controller: controller, + focusNode: focusNode, + autofocus: true, + validator: (value) { + if (value!.isEmpty) { + return AppLocalizations.of(context).selectIngredient; + } + return null; + }, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + labelText: AppLocalizations.of(context).searchIngredient, + suffixIcon: (widget.showScanner && !isDesktop) ? scanButton() : null, + ), + ); + }, + suggestionsCallback: (pattern) { + if (pattern == '') { + return null; + } + return Provider.of(context, listen: false).searchIngredient( pattern, languageCode: Localizations.localeOf(context).languageCode, searchEnglish: _searchEnglish, ); }, - itemBuilder: (context, dynamic suggestion) { + itemBuilder: (context, suggestion) { final url = context.read().baseProvider.auth.serverUrl; return ListTile( - leading: suggestion['data']['image'] != null - ? CircleAvatar(backgroundImage: NetworkImage(url! + suggestion['data']['image'])) + leading: suggestion.data.image != null + ? CircleAvatar(backgroundImage: NetworkImage(url! + suggestion.data.image!)) : const CircleIconAvatar(Icon(Icons.image, color: Colors.grey)), - title: Text(suggestion['value']), - subtitle: Text(suggestion['data']['id'].toString()), + title: Text(suggestion.value), + // subtitle: Text(suggestion.data.id.toString()), ); }, - transitionBuilder: (context, suggestionsBox, controller) { - return suggestionsBox; - }, - onSuggestionSelected: (dynamic suggestion) { - widget._ingredientIdController.text = suggestion['data']['id'].toString(); - widget._ingredientController.text = suggestion['value']; - }, - validator: (value) { - if (value!.isEmpty) { - return AppLocalizations.of(context).selectIngredient; - } - return null; + transitionBuilder: (context, animation, child) => FadeTransition( + opacity: CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn), + child: child, + ), + onSelected: (suggestion) { + //SuggestionsController.of(context).; + + widget._ingredientIdController.text = suggestion.data.id.toString(); + widget._ingredientController.text = suggestion.value; }, ), if (Localizations.localeOf(context).languageCode != LANGUAGE_SHORT_ENGLISH) diff --git a/lib/widgets/workouts/forms.dart b/lib/widgets/workouts/forms.dart index faf03f36..235cf2b8 100644 --- a/lib/widgets/workouts/forms.dart +++ b/lib/widgets/workouts/forms.dart @@ -363,106 +363,126 @@ class _SetFormWidgetState extends State { Card( child: Column( children: [ - TypeAheadFormField( + TypeAheadField( key: const Key('field-typeahead'), - textFieldConfiguration: TextFieldConfiguration( - controller: _exercisesController, - decoration: InputDecoration( - labelText: AppLocalizations.of(context).searchExercise, - prefixIcon: const Icon(Icons.search), - suffixIcon: IconButton( - icon: const Icon(Icons.help), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context).selectExercises), - const SizedBox(height: 10), - Text(AppLocalizations.of(context).sameRepetitions) + decorationBuilder: (context, child) { + return Material( + type: MaterialType.card, + elevation: 4, + borderRadius: BorderRadius.circular(8), + child: child, + ); + }, + controller: _exercisesController, + builder: (context, controller, focusNode) { + return TextFormField( + controller: controller, + focusNode: focusNode, + // autofocus: true, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).searchExercise, + prefixIcon: const Icon(Icons.search), + suffixIcon: IconButton( + icon: const Icon(Icons.help), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context).selectExercises), + const SizedBox(height: 10), + Text(AppLocalizations.of(context).sameRepetitions) + ], + ), + actions: [ + TextButton( + child: Text( + MaterialLocalizations.of(context).closeButtonLabel), + onPressed: () { + Navigator.of(context).pop(); + }, + ), ], ), - actions: [ - TextButton( - child: Text( - MaterialLocalizations.of(context).closeButtonLabel), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ), - ); - }, + ); + }, + ), + errorMaxLines: 2, + border: InputBorder.none, ), - errorMaxLines: 2, - border: InputBorder.none, - ), - ), + validator: (value) { + // At least one exercise must be selected + if (widget._set.exerciseBasesIds.isEmpty) { + return AppLocalizations.of(context).selectExercise; + } + + // At least one setting has to be filled in + if (widget._set.settings + .where((s) => s.weight == null && s.reps == null) + .length == + widget._set.settings.length) { + return AppLocalizations.of(context).enterRepetitionsOrWeight; + } + return null; + }, + ); + }, suggestionsCallback: (pattern) { + if (pattern == '') { + return null; + } return context.read().searchExercise( pattern, languageCode: Localizations.localeOf(context).languageCode, searchEnglish: _searchEnglish, ); }, - itemBuilder: (BuildContext context, Exercise exerciseSuggestion) { - return ListTile( - leading: SizedBox( - width: 45, - child: ExerciseImageWidget(image: exerciseSuggestion.getMainImage), - ), - title: Text( - exerciseSuggestion - .getExercise(Localizations.localeOf(context).languageCode) - .name, - ), - subtitle: Text( - '${exerciseSuggestion.category!.name} / ${exerciseSuggestion.equipment.map((e) => e.name).join(', ')}', - ), - ); - }, - noItemsFoundBuilder: (context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context).noMatchingExerciseFound), - TextButton( + itemBuilder: (BuildContext context, Exercise exerciseSuggestion) => + ListTile( + key: Key('exercise-${exerciseSuggestion.id}'), + leading: SizedBox( + width: 45, + child: ExerciseImageWidget(image: exerciseSuggestion.getMainImage), + ), + title: Text( + exerciseSuggestion + .getExercise(Localizations.localeOf(context).languageCode) + .name, + ), + subtitle: Text( + '${exerciseSuggestion.category!.name} / ${exerciseSuggestion.equipment.map((e) => e.name).join(', ')}', + ), + ), + emptyBuilder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text(AppLocalizations.of(context).noMatchingExerciseFound), + ), + ListTile( + title: OutlinedButton( onPressed: () { Navigator.of(context).pushNamed(AddExerciseScreen.routeName); }, child: Text(AppLocalizations.of(context).contributeExercise), ), - ], - ), + ), + ], ); }, - transitionBuilder: (context, suggestionsBox, controller) { - return suggestionsBox; - }, - onSuggestionSelected: (Exercise exerciseSuggestion) { + transitionBuilder: (context, animation, child) => FadeTransition( + opacity: CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn), + child: child, + ), + onSelected: (Exercise exerciseSuggestion) { + // SuggestionsController.of(context).select(exerciseSuggestion); + addExercise(exerciseSuggestion); _exercisesController.text = ''; }, - validator: (value) { - // At least one exercise must be selected - if (widget._set.exerciseBasesIds.isEmpty) { - return AppLocalizations.of(context).selectExercise; - } - - // At least one setting has to be filled in - if (widget._set.settings - .where((s) => s.weight == null && s.reps == null) - .length == - widget._set.settings.length) { - return AppLocalizations.of(context).enterRepetitionsOrWeight; - } - return null; - }, ), if (Localizations.localeOf(context).languageCode != LANGUAGE_SHORT_ENGLISH) SwitchListTile( diff --git a/pubspec.lock b/pubspec.lock index a4158b55..28c9b03a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -226,7 +226,7 @@ packages: source: hosted version: "0.4.1" clock: - dependency: transitive + dependency: "direct main" description: name: clock sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf @@ -537,10 +537,10 @@ packages: dependency: "direct main" description: name: flutter_typeahead - sha256: ef2dd5a505d2d95a5b4c571c81ad2d6e7955f583dddec49064fec57acffd7a96 + sha256: e2070dea278f09ae30885872138ccae75292b33b7af2c241fec5ceafd980c374 url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.0" flutter_web_plugins: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 989fa008..d16d29ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: equatable: ^2.0.5 flutter_calendar_carousel: ^2.4.1 flutter_html: ^3.0.0-beta.2 - flutter_typeahead: ^5.0.2 + flutter_typeahead: ^5.1.0 font_awesome_flutter: ^10.4.0 http: ^1.2.0 image_picker: ^1.0.6 diff --git a/test/auth/auth_screen_test.dart b/test/auth/auth_screen_test.dart index 50647673..565da38b 100644 --- a/test/auth/auth_screen_test.dart +++ b/test/auth/auth_screen_test.dart @@ -93,9 +93,7 @@ void main() { body: anyNamed('body'), )).thenAnswer((_) => Future(() => Response(json.encode(responseLoginOk), 200))); - when(mockClient.get( - any, - )).thenAnswer((_) => Future(() => Response('"1.2.3.4"', 200))); + when(mockClient.get(any)).thenAnswer((_) => Future(() => Response('"1.2.3.4"', 200))); when(mockClient.post( tRegistration, diff --git a/test/exercises/contribute_exercise_test.dart b/test/exercises/contribute_exercise_test.dart index 5730678b..b1c595ab 100644 --- a/test/exercises/contribute_exercise_test.dart +++ b/test/exercises/contribute_exercise_test.dart @@ -76,7 +76,7 @@ void main() { when(mockExerciseProvider.muscles).thenReturn(testMuscles); when(mockExerciseProvider.equipment).thenReturn(testEquipment); when(mockExerciseProvider.exerciseBasesByVariation).thenReturn({}); - when(mockExerciseProvider.exercises).thenReturn(getTestExerciseBases()); + when(mockExerciseProvider.exercises).thenReturn(getTestExercises()); when(mockExerciseProvider.languages).thenReturn(testLanguages); when(mockAddExerciseProvider.equipment).thenReturn([]); diff --git a/test/exercises/exercise_provider_load_test.dart b/test/exercises/exercise_provider_load_test.dart index d1f20288..baaef088 100644 --- a/test/exercises/exercise_provider_load_test.dart +++ b/test/exercises/exercise_provider_load_test.dart @@ -50,7 +50,7 @@ void main() { mockBaseProvider, database: ExerciseDatabase.inMemory(NativeDatabase.memory()), ); - provider.exercises = getTestExerciseBases(); + provider.exercises = getTestExercises(); provider.languages = [tLanguage1, tLanguage2, tLanguage3]; // Mock base info response diff --git a/test/exercises/exercise_provider_test.dart b/test/exercises/exercise_provider_test.dart index bb6ba072..dc2aa192 100644 --- a/test/exercises/exercise_provider_test.dart +++ b/test/exercises/exercise_provider_test.dart @@ -223,7 +223,7 @@ void main() { equipment: FilterCategory(title: 'Equipment', items: {}), ); - provider.exercises = data.getTestExerciseBases(); + provider.exercises = data.getTestExercises(); }); test('Nothing is selected with no search term', () async { @@ -238,7 +238,7 @@ void main() { expect( provider.filteredExercises, - data.getTestExerciseBases(), + data.getTestExercises(), ); }); @@ -253,7 +253,7 @@ void main() { // assert verifyNever(provider.baseProvider.fetch(tSearchByNameUri)); - expect(provider.filteredExercises, [data.getTestExerciseBases()[0]]); + expect(provider.filteredExercises, [data.getTestExercises()[0]]); }); test('A muscle is selected with no search term. Should not find results', () async { @@ -281,7 +281,7 @@ void main() { // assert verifyNever(provider.baseProvider.fetch(tSearchByNameUri)); - expect(provider.filteredExercises, [data.getTestExerciseBases()[0]]); + expect(provider.filteredExercises, [data.getTestExercises()[0]]); }); test('An equipment is selected with no search term. Should not find results', () async { @@ -310,7 +310,7 @@ void main() { // assert verifyNever(provider.baseProvider.fetch(tSearchByNameUri)); - expect(provider.filteredExercises, [data.getTestExerciseBases()[1]]); + expect(provider.filteredExercises, [data.getTestExercises()[1]]); }); test('A muscle and equipment is selected but no match', () async { @@ -365,7 +365,7 @@ void main() { verify(provider.baseProvider.fetch(tSearchByNameUri)).called(1); expect( provider.filteredExercises, - [data.getTestExerciseBases()[0], data.getTestExerciseBases()[1]], + [data.getTestExercises()[0], data.getTestExercises()[1]], ); }); test('Should find items from selection but should filter them by search term', () async { diff --git a/test/exercises/exercises_detail_widget_test.dart b/test/exercises/exercises_detail_widget_test.dart index b7725633..12e98ba0 100644 --- a/test/exercises/exercises_detail_widget_test.dart +++ b/test/exercises/exercises_detail_widget_test.dart @@ -38,7 +38,7 @@ void main() { supportedLocales: AppLocalizations.supportedLocales, navigatorKey: GlobalKey(), home: Scaffold( - body: ExerciseDetail(getTestExerciseBases()[0]), + body: ExerciseDetail(getTestExercises()[0]), ), ), ); diff --git a/test/exercises/model_exercise_test.dart b/test/exercises/model_exercise_test.dart index 4a06de28..96886cb0 100644 --- a/test/exercises/model_exercise_test.dart +++ b/test/exercises/model_exercise_test.dart @@ -14,7 +14,7 @@ void main() { group('Model tests', () { test('test getExercise', () async { // arrange and act - final base = getTestExerciseBases()[1]; + final base = getTestExercises()[1]; // assert expect(base.getExercise('en').id, 5); diff --git a/test/nutrition/nutritional_meal_form_test.mocks.dart b/test/nutrition/nutritional_meal_form_test.mocks.dart index fdb841d2..a9dd86b9 100644 --- a/test/nutrition/nutritional_meal_form_test.mocks.dart +++ b/test/nutrition/nutritional_meal_form_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i8; -import 'dart:ui' as _i9; +import 'dart:ui' as _i10; import 'package:mockito/mockito.dart' as _i1; +import 'package:wger/models/exercises/ingredient_api.dart' as _i9; import 'package:wger/models/nutrition/ingredient.dart' as _i6; import 'package:wger/models/nutrition/meal.dart' as _i4; import 'package:wger/models/nutrition/meal_item.dart' as _i5; @@ -341,7 +342,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ) as _i8.Future); @override - _i8.Future> searchIngredient( + _i8.Future> searchIngredient( String? name, { String? languageCode = r'en', bool? searchEnglish = false, @@ -355,8 +356,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP #searchEnglish: searchEnglish, }, ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: + _i8.Future>.value(<_i9.IngredientApiSearchEntry>[]), + ) as _i8.Future>); @override _i8.Future<_i6.Ingredient?> searchIngredientWithCode(String? code) => (super.noSuchMethod( @@ -424,7 +426,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -433,7 +435,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/nutrition/nutritional_meal_item_form_test.dart b/test/nutrition/nutritional_meal_item_form_test.dart index aa1eb9c3..50561722 100644 --- a/test/nutrition/nutritional_meal_item_form_test.dart +++ b/test/nutrition/nutritional_meal_item_form_test.dart @@ -9,6 +9,7 @@ import 'package:http/http.dart' as http; import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; import 'package:wger/helpers/consts.dart'; +import 'package:wger/models/exercises/ingredient_api.dart'; import 'package:wger/models/nutrition/ingredient.dart'; import 'package:wger/models/nutrition/meal.dart'; import 'package:wger/models/nutrition/meal_item.dart'; @@ -76,8 +77,13 @@ void main() { when(mockNutrition.searchIngredientWithCode('123')).thenAnswer((_) => Future.value(ingredient)); when(mockNutrition.searchIngredientWithCode('')).thenAnswer((_) => Future.value(null)); when(mockNutrition.searchIngredientWithCode('222')).thenAnswer((_) => Future.value(null)); - when(mockNutrition.searchIngredient(any)).thenAnswer((_) => - Future.value(json.decode(fixture('nutrition/ingredient_suggestions')) as List)); + when(mockNutrition.searchIngredient(any, + languageCode: anyNamed('languageCode'), searchEnglish: anyNamed('searchEnglish'))) + .thenAnswer( + (_) => Future.value( + IngredientApiSearch.fromJson(json.decode(fixture('nutrition/ingredient_suggestions'))) + .suggestions), + ); when(mockNutrition.addMealItem(any, meal1)).thenAnswer((_) => Future.value(mealItem)); }); @@ -109,8 +115,8 @@ void main() { await tester.pumpWidget(createMealItemFormScreen(meal1, '', true)); await tester.pumpAndSettle(); - expect(find.byType(TypeAheadFormField), findsOneWidget); - expect(find.byType(TextFormField), findsOneWidget); + expect(find.byType(TypeAheadField), findsOneWidget); + expect(find.byType(TextFormField), findsWidgets); expect(find.byKey(const Key('scan-button')), findsOneWidget); expect(find.byKey(const Key(SUBMIT_BUTTON_KEY_NAME)), findsOneWidget); @@ -248,6 +254,8 @@ void main() { }); testWidgets('save ingredient without weight', (WidgetTester tester) async { + await tester.binding.setSurfaceSize(const Size(1080, 1920)); + tester.view.devicePixelRatio = 1.0; await tester.pumpWidget(createMealItemFormScreen(meal1, '123', true)); await tester.tap(find.byKey(const Key('scan-button'))); diff --git a/test/nutrition/nutritional_plan_form_test.mocks.dart b/test/nutrition/nutritional_plan_form_test.mocks.dart index 35b25878..32a1c32e 100644 --- a/test/nutrition/nutritional_plan_form_test.mocks.dart +++ b/test/nutrition/nutritional_plan_form_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i8; -import 'dart:ui' as _i9; +import 'dart:ui' as _i10; import 'package:mockito/mockito.dart' as _i1; +import 'package:wger/models/exercises/ingredient_api.dart' as _i9; import 'package:wger/models/nutrition/ingredient.dart' as _i6; import 'package:wger/models/nutrition/meal.dart' as _i4; import 'package:wger/models/nutrition/meal_item.dart' as _i5; @@ -341,7 +342,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ) as _i8.Future); @override - _i8.Future> searchIngredient( + _i8.Future> searchIngredient( String? name, { String? languageCode = r'en', bool? searchEnglish = false, @@ -355,8 +356,9 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP #searchEnglish: searchEnglish, }, ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: + _i8.Future>.value(<_i9.IngredientApiSearchEntry>[]), + ) as _i8.Future>); @override _i8.Future<_i6.Ingredient?> searchIngredientWithCode(String? code) => (super.noSuchMethod( @@ -424,7 +426,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -433,7 +435,7 @@ class MockNutritionPlansProvider extends _i1.Mock implements _i7.NutritionPlansP ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/workout/gym_mode_screen_test.dart b/test/workout/gym_mode_screen_test.dart index 61a5d4b6..db0ccfe9 100644 --- a/test/workout/gym_mode_screen_test.dart +++ b/test/workout/gym_mode_screen_test.dart @@ -41,7 +41,7 @@ void main() { final mockExerciseProvider = MockExercisesProvider(); final workoutPlan = getWorkout(); - final bases = getTestExerciseBases(); + final bases = getTestExercises(); Widget createHomeScreen({locale = 'en'}) { return ChangeNotifierProvider( diff --git a/test/workout/workout_plan_model_test.dart b/test/workout/workout_plan_model_test.dart index 9ab1a588..57be4bb3 100644 --- a/test/workout/workout_plan_model_test.dart +++ b/test/workout/workout_plan_model_test.dart @@ -27,16 +27,16 @@ void main() { final workout = getWorkout(); expect(workout.logs.length, 3); - final logExercise1 = workout.filterLogsByExerciseBase(getTestExerciseBases()[0]); + final logExercise1 = workout.filterLogsByExerciseBase(getTestExercises()[0]); expect(logExercise1.length, 2); expect(logExercise1[0].id, 1); expect(logExercise1[1].id, 2); - final logExercise2 = workout.filterLogsByExerciseBase(getTestExerciseBases()[1]); + final logExercise2 = workout.filterLogsByExerciseBase(getTestExercises()[1]); expect(logExercise2.length, 1); expect(logExercise2[0].id, 3); - expect(workout.filterLogsByExerciseBase(getTestExerciseBases()[2]).length, 0); + expect(workout.filterLogsByExerciseBase(getTestExercises()[2]).length, 0); }); }); } diff --git a/test/workout/workout_set_form_test.dart b/test/workout/workout_set_form_test.dart index accc91f4..3d298007 100644 --- a/test/workout/workout_set_form_test.dart +++ b/test/workout/workout_set_form_test.dart @@ -31,6 +31,7 @@ import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/workout_plans.dart'; import 'package:wger/widgets/workouts/forms.dart'; +import '../../test_data/exercises.dart'; import '../../test_data/workouts.dart'; import 'workout_set_form_test.mocks.dart'; @@ -84,6 +85,13 @@ void main() { when(mockWorkoutPlans.addSet(any)).thenAnswer((_) => Future.value(Set.empty())); when(mockWorkoutPlans.addSetting(any)).thenAnswer((_) => Future.value(Setting.empty())); when(mockWorkoutPlans.fetchSmartText(any, any)).thenAnswer((_) => Future.value('2 x 10')); + when(mockExerciseProvider.searchExercise( + any, + languageCode: anyNamed('languageCode'), + searchEnglish: anyNamed('searchEnglish'), + )).thenAnswer( + (_) => Future.value([getTestExercises().first]), + ); await tester.pumpWidget(createHomeScreen()); await tester.pumpAndSettle(); @@ -91,6 +99,7 @@ void main() { await tester.enterText(find.byKey(const Key('field-typeahead')), 'exercise'); await tester.pumpAndSettle(); + //await tester.tap(find.byKey(const Key('exercise-1'))); await tester.tap(find.byKey(const Key(SUBMIT_BUTTON_KEY_NAME))); //verify(mockWorkoutPlans.addSet(any)); diff --git a/test_data/exercises.dart b/test_data/exercises.dart index f7e83f98..f9ea5789 100644 --- a/test_data/exercises.dart +++ b/test_data/exercises.dart @@ -203,7 +203,7 @@ final sideRaisesEn = Translation( language: tLanguage2, ); -List getTestExerciseBases() { +List getTestExercises() { benchPress.translations = [benchPressEn, benchPressDe]; crunches.translations = [crunchesEn, crunchesDe, crunchesFr]; deadLift.translations = [deadLiftEn]; diff --git a/test_data/screenshots_exercises.dart b/test_data/screenshots_exercises.dart index 3630108b..c9663f5d 100644 --- a/test_data/screenshots_exercises.dart +++ b/test_data/screenshots_exercises.dart @@ -210,8 +210,7 @@ final benchPressDE = Translation( uuid: '198dcb2e-e35f-4b69-ae8b-e1124d438eae', created: DateTime(2021, 1, 15), name: 'Bankdrücken LH', - description: - ''' + description: '''

Lege dich auf die Bank, die Stange direkt über die Augen, die Knie etwas angewinkelt und die Füße fest auf dem Boden. Greife die Stange breit und lasse sie langsam und kontrolliert runter, dabei sollte die Stange kurz auf Brustwarzenhöhe den Körper berühren. Dann das Gewicht wieder hochdrücken bis die Arme durchgestreckt sind.

Bei hohem Gewicht, empfielt sich natürlich einen Spotter zu haben, der einen hilft falls man die Stange nicht alleine hochdrücken kann.

Mit der Breite des Griffs kann außerdem kontrolliert werden, welcher Bereich der Brust stärker belastet wird:

@@ -228,8 +227,7 @@ final benchPressEN = Translation( uuid: '5da6340b-22ec-4c1b-a443-eef2f59f92f0', created: DateTime(2021, 1, 15), name: 'Bench Press', - description: - ''' + description: '''

Lay down on a bench, the bar should be directly above your eyes, the knees are somewhat angled and the feet are firmly on the floor. Concentrate, breath deeply and grab the bar more than shoulder wide. Bring it slowly down till it briefly touches your chest at the height of your nipples. Push the bar up.

If you train with a high weight it is advisable to have a spotter that can help you up if you can't lift the weight on your own.

With the width of the grip you can also control which part of the chest is trained more:

@@ -258,8 +256,7 @@ final deadLiftEN = Translation( uuid: '22cca8fc-cfaf-4941-b0f7-faf9f2937c52', created: DateTime(2021, 1, 15), name: 'Deadlifts', - description: - ''' + description: '''

Stand firmly, with your feet slightly more than shoulder wide apart. Stand directly behind the bar where it should barely touch your shin, your feet pointing a bit out. Bend down with a straight back, the knees also pointing somewhat out. Grab the bar with a shoulder wide grip, one underhand, one reverse grip.

Pull the weight up. At the highest point make a slight hollow back and pull the bar back. Hold 1 or 2 seconds that position. Go down, making sure the back is not bent. Once down you can either go back again as soon as the weights touch the floor, or make a pause, depending on the weight.

''', exerciseId: 184, @@ -271,8 +268,7 @@ final deadLiftDE = Translation( uuid: '521a5e4f-6f35-43e5-9d1c-6e75c4956e96', created: DateTime(2021, 1, 15), name: 'Kreuzheben', - description: - ''' + description: '''

Stelle dich mit etwas mehr als schulterbreitem Stand vor der Stange, die Füße zeigen leicht nach außen, die Stange ist direkt darüber und sehr nahe am Schienbein. Beuge die Knie (zeigen ebenfalls etwas nach außen) und neige den Oberkörper (bleibt während der ganzen Übung gerade). Greife die Stange schulterbreit mit einem Unter- und einem Obergriff.

Ziehe nun die Stange nach oben. An der höchsten Stelle mache ein leichtes Hohlkreuz und drücke die Schultern nach hinten. Gehe wieder runter, wobei du darauf achtest, dass der Rücken gerade bleibt und sich nicht krümmt. Du kannst unten angekommen eine kleine Pause einlegen oder sofort weitermachen.

''', exerciseId: 184, @@ -284,8 +280,7 @@ final deadLiftPT = Translation( uuid: 'e570cd82-5a8f-4768-a2f3-8d60bad5c6e8', created: DateTime(2021, 1, 15), name: 'Levantamento terra', - description: - ''' + description: ''' Fique firme, com os pés ligeiramente mais afastados do que os ombros. Fique diretamente atrás da barra, onde ela mal deve tocar sua canela, com os pés apontando um pouco para fora. Curve-se com as costas retas, os joelhos também apontando um pouco para fora. Agarre a barra com uma pegada na largura dos ombros, uma pegada por baixo e uma pegada reversa. Puxe o peso para cima. No ponto mais alto, faça uma leve depressão para trás e puxe a barra para trás. Segure 1 ou 2 segundos nessa posição. Desça, certificando-se de que as costas não estão dobradas. Depois de descer, você pode voltar assim que os pesos tocarem o chão ou fazer uma pausa, dependendo do peso.''', @@ -378,8 +373,7 @@ final crunchesDE = Translation( uuid: '0e10ac9b-ed1d-42c9-b8cc-123c22ccc5d5', created: DateTime(2021, 1, 15), name: 'Crunches', - description: - ''' + description: '''

Lege dich auf eine Matte mit angewinkelten Beinen. Die Füße werden irgendwie festgehalten (Partner, Lanhghantel, o.Ä.) und die Hände werden hinter dem Nacken verschränkt. Aus dieser Position führe den Oberkörper so weit nach oben, bis Kopf oder Ellenbogen die angewinkelten Beine berühren.

Es ist wichtig, dass dieser Vorgang mit einer rollenden Bewegung durchgeführt wird: die Wirbelsäule sollte sich Wirbel für Wirbel von der Matte lösen. Ein Hohlkreuz ist stets zu vermeiden.

''', exerciseId: 167, @@ -506,8 +500,7 @@ final curlsEN = Translation( uuid: '48a59aa8-4568-409c-8afe-f8cb99c558ea', created: DateTime(2021, 1, 15), name: 'Biceps Curls With Dumbbell', - description: - ''' + description: '''

Hold two barbells, the arms are streched, the hands are on your side, the palms face inwards. Bend the arms and bring the weight with a fast movement up. At the same time, rotate your arms by 90 degrees at the very beginning of the movement. At the highest point, rotate a little the weights further outwards. Without a pause, bring them down, slowly.

Don't allow your body to swing during the exercise, all work is done by the biceps, which are the only mucles that should move (pay attention to the elbows).

''', exerciseId: 92, @@ -519,8 +512,7 @@ final curlsDE = Translation( uuid: '8cbbffcc-1989-43de-9200-03869480398c', created: DateTime(2021, 1, 15), name: 'Bizeps KH-Curls', - description: - ''' + description: '''

Halte zwei Kurzhantel mit ausgestreckten Armen neben dem Körper, die Handflächen zeigen nach innen. Beuge die Arme und brige die Hanteln mit einer schnellen Bewegung nach oben wobei sie gleichzeitig um 90 Grad gedreht werden. Am höchsten Punkt kann man die Hanteln ganz leicht weiter nach außen drehen. Ohne Pause wird das Gewicht nun kontrolliert nach unten gebracht. Beachte, dass die Bewegung nach oben schneller ist als nach unten.

Während des Bewegungablaufs darf der Körper nicht mitschwingen. Die Ellenbogen bleiben dabei immer an der Stelle.

''', exerciseId: 92, diff --git a/test_data/workouts.dart b/test_data/workouts.dart index d9bf7f92..ecd36a31 100644 --- a/test_data/workouts.dart +++ b/test_data/workouts.dart @@ -34,7 +34,7 @@ const RepetitionUnit repetitionUnit1 = RepetitionUnit(id: 1, name: 'Repetitions' const RepetitionUnit repetitionUnit2 = RepetitionUnit(id: 2, name: 'Hours'); WorkoutPlan getWorkout({List? exercises}) { - final testBases = exercises ?? getTestExerciseBases(); + final testBases = exercises ?? getTestExercises(); final log1 = Log.empty() ..id = 1