diff --git a/assets/icons/revert_product.svg b/assets/icons/revert_product.svg new file mode 100644 index 00000000..fa80931d --- /dev/null +++ b/assets/icons/revert_product.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/features/diary/models/diary_day_model.dart b/lib/features/diary/models/diary_day_model.dart index a9a6cfff..e9e129d9 100644 --- a/lib/features/diary/models/diary_day_model.dart +++ b/lib/features/diary/models/diary_day_model.dart @@ -4,6 +4,17 @@ part 'diary_day_model.freezed.dart'; part 'diary_day_model.g.dart'; +enum PortionState { + active, + filled_diet, + eaten_from_diet, + eaten, + eaten_deleted, + eaten_edited, + commented, + reviewed_by_user, +} + @freezed class DiaryDayWrapperModel with _$DiaryDayWrapperModel { const factory DiaryDayWrapperModel({ @@ -37,7 +48,7 @@ class MealModel with _$MealModel { @JsonKey(name: "type_no") required int typeNumber, @JsonKey(name: "satiety") required int satiety, @JsonKey(name: "mood") required int mood, - @JsonKey(name: "portions") required List portions, + @JsonKey(name: "portions") required List portions, @JsonKey(name: "photos") required List photos, }) = _MealModel; @@ -57,16 +68,18 @@ class DiaryPhotoModel with _$DiaryPhotoModel { } @freezed -class Portion with _$Portion { - const factory Portion({ +class PortionModel with _$PortionModel { + const factory PortionModel({ @JsonKey(name: "id") required int id, + @JsonKey(name: "product_nutritional_value_id") int? productNutritionalValueId, @JsonKey(name: "product_name") required String productName, @JsonKey(name: "quantity") required int quantity, @JsonKey(name: "portion") required String portion, @JsonKey(name: "from_diet") required bool fromDiet, - }) = _Portion; + @JsonKey(name: "state") required PortionState portionState, + }) = _PortionModel; - factory Portion.fromJson(Map json) => _$PortionFromJson(json); + factory PortionModel.fromJson(Map json) => _$PortionModelFromJson(json); } @freezed diff --git a/lib/features/diary/models/diary_day_model.freezed.dart b/lib/features/diary/models/diary_day_model.freezed.dart index 2e87e426..38983ff8 100644 --- a/lib/features/diary/models/diary_day_model.freezed.dart +++ b/lib/features/diary/models/diary_day_model.freezed.dart @@ -545,7 +545,7 @@ mixin _$MealModel { @JsonKey(name: "mood") int get mood => throw _privateConstructorUsedError; @JsonKey(name: "portions") - List get portions => throw _privateConstructorUsedError; + List get portions => throw _privateConstructorUsedError; @JsonKey(name: "photos") List get photos => throw _privateConstructorUsedError; @@ -570,7 +570,7 @@ abstract class $MealModelCopyWith<$Res> { @JsonKey(name: "type_no") int typeNumber, @JsonKey(name: "satiety") int satiety, @JsonKey(name: "mood") int mood, - @JsonKey(name: "portions") List portions, + @JsonKey(name: "portions") List portions, @JsonKey(name: "photos") List photos}); } @@ -621,7 +621,7 @@ class _$MealModelCopyWithImpl<$Res, $Val extends MealModel> portions: null == portions ? _value.portions : portions // ignore: cast_nullable_to_non_nullable - as List, + as List, photos: null == photos ? _value.photos : photos // ignore: cast_nullable_to_non_nullable @@ -644,7 +644,7 @@ abstract class _$$MealModelImplCopyWith<$Res> @JsonKey(name: "type_no") int typeNumber, @JsonKey(name: "satiety") int satiety, @JsonKey(name: "mood") int mood, - @JsonKey(name: "portions") List portions, + @JsonKey(name: "portions") List portions, @JsonKey(name: "photos") List photos}); } @@ -693,7 +693,7 @@ class __$$MealModelImplCopyWithImpl<$Res> portions: null == portions ? _value._portions : portions // ignore: cast_nullable_to_non_nullable - as List, + as List, photos: null == photos ? _value._photos : photos // ignore: cast_nullable_to_non_nullable @@ -711,7 +711,7 @@ class _$MealModelImpl implements _MealModel { @JsonKey(name: "type_no") required this.typeNumber, @JsonKey(name: "satiety") required this.satiety, @JsonKey(name: "mood") required this.mood, - @JsonKey(name: "portions") required final List portions, + @JsonKey(name: "portions") required final List portions, @JsonKey(name: "photos") required final List photos}) : _portions = portions, _photos = photos; @@ -734,10 +734,10 @@ class _$MealModelImpl implements _MealModel { @override @JsonKey(name: "mood") final int mood; - final List _portions; + final List _portions; @override @JsonKey(name: "portions") - List get portions { + List get portions { if (_portions is EqualUnmodifiableListView) return _portions; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_portions); @@ -807,7 +807,7 @@ abstract class _MealModel implements MealModel { @JsonKey(name: "type_no") required final int typeNumber, @JsonKey(name: "satiety") required final int satiety, @JsonKey(name: "mood") required final int mood, - @JsonKey(name: "portions") required final List portions, + @JsonKey(name: "portions") required final List portions, @JsonKey(name: "photos") required final List photos}) = _$MealModelImpl; @@ -831,7 +831,7 @@ abstract class _MealModel implements MealModel { int get mood; @override @JsonKey(name: "portions") - List get portions; + List get portions; @override @JsonKey(name: "photos") List get photos; @@ -1071,14 +1071,16 @@ abstract class _DiaryPhotoModel implements DiaryPhotoModel { throw _privateConstructorUsedError; } -Portion _$PortionFromJson(Map json) { - return _Portion.fromJson(json); +PortionModel _$PortionModelFromJson(Map json) { + return _PortionModel.fromJson(json); } /// @nodoc -mixin _$Portion { +mixin _$PortionModel { @JsonKey(name: "id") int get id => throw _privateConstructorUsedError; + @JsonKey(name: "product_nutritional_value_id") + int? get productNutritionalValueId => throw _privateConstructorUsedError; @JsonKey(name: "product_name") String get productName => throw _privateConstructorUsedError; @JsonKey(name: "quantity") @@ -1087,55 +1089,68 @@ mixin _$Portion { String get portion => throw _privateConstructorUsedError; @JsonKey(name: "from_diet") bool get fromDiet => throw _privateConstructorUsedError; + @JsonKey(name: "state") + PortionState get portionState => throw _privateConstructorUsedError; - /// Serializes this Portion to a JSON map. + /// Serializes this PortionModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of Portion + /// Create a copy of PortionModel /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $PortionCopyWith get copyWith => throw _privateConstructorUsedError; + $PortionModelCopyWith get copyWith => + throw _privateConstructorUsedError; } /// @nodoc -abstract class $PortionCopyWith<$Res> { - factory $PortionCopyWith(Portion value, $Res Function(Portion) then) = - _$PortionCopyWithImpl<$Res, Portion>; +abstract class $PortionModelCopyWith<$Res> { + factory $PortionModelCopyWith( + PortionModel value, $Res Function(PortionModel) then) = + _$PortionModelCopyWithImpl<$Res, PortionModel>; @useResult $Res call( {@JsonKey(name: "id") int id, + @JsonKey(name: "product_nutritional_value_id") + int? productNutritionalValueId, @JsonKey(name: "product_name") String productName, @JsonKey(name: "quantity") int quantity, @JsonKey(name: "portion") String portion, - @JsonKey(name: "from_diet") bool fromDiet}); + @JsonKey(name: "from_diet") bool fromDiet, + @JsonKey(name: "state") PortionState portionState}); } /// @nodoc -class _$PortionCopyWithImpl<$Res, $Val extends Portion> - implements $PortionCopyWith<$Res> { - _$PortionCopyWithImpl(this._value, this._then); +class _$PortionModelCopyWithImpl<$Res, $Val extends PortionModel> + implements $PortionModelCopyWith<$Res> { + _$PortionModelCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of Portion + /// Create a copy of PortionModel /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? id = null, + Object? productNutritionalValueId = freezed, Object? productName = null, Object? quantity = null, Object? portion = null, Object? fromDiet = null, + Object? portionState = null, }) { return _then(_value.copyWith( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, + productNutritionalValueId: freezed == productNutritionalValueId + ? _value.productNutritionalValueId + : productNutritionalValueId // ignore: cast_nullable_to_non_nullable + as int?, productName: null == productName ? _value.productName : productName // ignore: cast_nullable_to_non_nullable @@ -1152,49 +1167,63 @@ class _$PortionCopyWithImpl<$Res, $Val extends Portion> ? _value.fromDiet : fromDiet // ignore: cast_nullable_to_non_nullable as bool, + portionState: null == portionState + ? _value.portionState + : portionState // ignore: cast_nullable_to_non_nullable + as PortionState, ) as $Val); } } /// @nodoc -abstract class _$$PortionImplCopyWith<$Res> implements $PortionCopyWith<$Res> { - factory _$$PortionImplCopyWith( - _$PortionImpl value, $Res Function(_$PortionImpl) then) = - __$$PortionImplCopyWithImpl<$Res>; +abstract class _$$PortionModelImplCopyWith<$Res> + implements $PortionModelCopyWith<$Res> { + factory _$$PortionModelImplCopyWith( + _$PortionModelImpl value, $Res Function(_$PortionModelImpl) then) = + __$$PortionModelImplCopyWithImpl<$Res>; @override @useResult $Res call( {@JsonKey(name: "id") int id, + @JsonKey(name: "product_nutritional_value_id") + int? productNutritionalValueId, @JsonKey(name: "product_name") String productName, @JsonKey(name: "quantity") int quantity, @JsonKey(name: "portion") String portion, - @JsonKey(name: "from_diet") bool fromDiet}); + @JsonKey(name: "from_diet") bool fromDiet, + @JsonKey(name: "state") PortionState portionState}); } /// @nodoc -class __$$PortionImplCopyWithImpl<$Res> - extends _$PortionCopyWithImpl<$Res, _$PortionImpl> - implements _$$PortionImplCopyWith<$Res> { - __$$PortionImplCopyWithImpl( - _$PortionImpl _value, $Res Function(_$PortionImpl) _then) +class __$$PortionModelImplCopyWithImpl<$Res> + extends _$PortionModelCopyWithImpl<$Res, _$PortionModelImpl> + implements _$$PortionModelImplCopyWith<$Res> { + __$$PortionModelImplCopyWithImpl( + _$PortionModelImpl _value, $Res Function(_$PortionModelImpl) _then) : super(_value, _then); - /// Create a copy of Portion + /// Create a copy of PortionModel /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? id = null, + Object? productNutritionalValueId = freezed, Object? productName = null, Object? quantity = null, Object? portion = null, Object? fromDiet = null, + Object? portionState = null, }) { - return _then(_$PortionImpl( + return _then(_$PortionModelImpl( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable as int, + productNutritionalValueId: freezed == productNutritionalValueId + ? _value.productNutritionalValueId + : productNutritionalValueId // ignore: cast_nullable_to_non_nullable + as int?, productName: null == productName ? _value.productName : productName // ignore: cast_nullable_to_non_nullable @@ -1211,27 +1240,37 @@ class __$$PortionImplCopyWithImpl<$Res> ? _value.fromDiet : fromDiet // ignore: cast_nullable_to_non_nullable as bool, + portionState: null == portionState + ? _value.portionState + : portionState // ignore: cast_nullable_to_non_nullable + as PortionState, )); } } /// @nodoc @JsonSerializable() -class _$PortionImpl implements _Portion { - const _$PortionImpl( +class _$PortionModelImpl implements _PortionModel { + const _$PortionModelImpl( {@JsonKey(name: "id") required this.id, + @JsonKey(name: "product_nutritional_value_id") + this.productNutritionalValueId, @JsonKey(name: "product_name") required this.productName, @JsonKey(name: "quantity") required this.quantity, @JsonKey(name: "portion") required this.portion, - @JsonKey(name: "from_diet") required this.fromDiet}); + @JsonKey(name: "from_diet") required this.fromDiet, + @JsonKey(name: "state") required this.portionState}); - factory _$PortionImpl.fromJson(Map json) => - _$$PortionImplFromJson(json); + factory _$PortionModelImpl.fromJson(Map json) => + _$$PortionModelImplFromJson(json); @override @JsonKey(name: "id") final int id; @override + @JsonKey(name: "product_nutritional_value_id") + final int? productNutritionalValueId; + @override @JsonKey(name: "product_name") final String productName; @override @@ -1243,63 +1282,78 @@ class _$PortionImpl implements _Portion { @override @JsonKey(name: "from_diet") final bool fromDiet; + @override + @JsonKey(name: "state") + final PortionState portionState; @override String toString() { - return 'Portion(id: $id, productName: $productName, quantity: $quantity, portion: $portion, fromDiet: $fromDiet)'; + return 'PortionModel(id: $id, productNutritionalValueId: $productNutritionalValueId, productName: $productName, quantity: $quantity, portion: $portion, fromDiet: $fromDiet, portionState: $portionState)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$PortionImpl && + other is _$PortionModelImpl && (identical(other.id, id) || other.id == id) && + (identical(other.productNutritionalValueId, + productNutritionalValueId) || + other.productNutritionalValueId == productNutritionalValueId) && (identical(other.productName, productName) || other.productName == productName) && (identical(other.quantity, quantity) || other.quantity == quantity) && (identical(other.portion, portion) || other.portion == portion) && (identical(other.fromDiet, fromDiet) || - other.fromDiet == fromDiet)); + other.fromDiet == fromDiet) && + (identical(other.portionState, portionState) || + other.portionState == portionState)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, id, productName, quantity, portion, fromDiet); + int get hashCode => Object.hash(runtimeType, id, productNutritionalValueId, + productName, quantity, portion, fromDiet, portionState); - /// Create a copy of Portion + /// Create a copy of PortionModel /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$PortionImplCopyWith<_$PortionImpl> get copyWith => - __$$PortionImplCopyWithImpl<_$PortionImpl>(this, _$identity); + _$$PortionModelImplCopyWith<_$PortionModelImpl> get copyWith => + __$$PortionModelImplCopyWithImpl<_$PortionModelImpl>(this, _$identity); @override Map toJson() { - return _$$PortionImplToJson( + return _$$PortionModelImplToJson( this, ); } } -abstract class _Portion implements Portion { - const factory _Portion( +abstract class _PortionModel implements PortionModel { + const factory _PortionModel( {@JsonKey(name: "id") required final int id, + @JsonKey(name: "product_nutritional_value_id") + final int? productNutritionalValueId, @JsonKey(name: "product_name") required final String productName, @JsonKey(name: "quantity") required final int quantity, @JsonKey(name: "portion") required final String portion, - @JsonKey(name: "from_diet") required final bool fromDiet}) = - _$PortionImpl; + @JsonKey(name: "from_diet") required final bool fromDiet, + @JsonKey(name: "state") required final PortionState portionState}) = + _$PortionModelImpl; - factory _Portion.fromJson(Map json) = _$PortionImpl.fromJson; + factory _PortionModel.fromJson(Map json) = + _$PortionModelImpl.fromJson; @override @JsonKey(name: "id") int get id; @override + @JsonKey(name: "product_nutritional_value_id") + int? get productNutritionalValueId; + @override @JsonKey(name: "product_name") String get productName; @override @@ -1311,12 +1365,15 @@ abstract class _Portion implements Portion { @override @JsonKey(name: "from_diet") bool get fromDiet; + @override + @JsonKey(name: "state") + PortionState get portionState; - /// Create a copy of Portion + /// Create a copy of PortionModel /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$PortionImplCopyWith<_$PortionImpl> get copyWith => + _$$PortionModelImplCopyWith<_$PortionModelImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/features/diary/models/diary_day_model.g.dart b/lib/features/diary/models/diary_day_model.g.dart index b055fcfc..9e4d7520 100644 --- a/lib/features/diary/models/diary_day_model.g.dart +++ b/lib/features/diary/models/diary_day_model.g.dart @@ -53,7 +53,7 @@ _$MealModelImpl _$$MealModelImplFromJson(Map json) => satiety: (json['satiety'] as num).toInt(), mood: (json['mood'] as num).toInt(), portions: (json['portions'] as List) - .map((e) => Portion.fromJson(e as Map)) + .map((e) => PortionModel.fromJson(e as Map)) .toList(), photos: (json['photos'] as List) .map((e) => DiaryPhotoModel.fromJson(e as Map)) @@ -89,24 +89,40 @@ Map _$$DiaryPhotoModelImplToJson( 'size': instance.size, }; -_$PortionImpl _$$PortionImplFromJson(Map json) => - _$PortionImpl( +_$PortionModelImpl _$$PortionModelImplFromJson(Map json) => + _$PortionModelImpl( id: (json['id'] as num).toInt(), + productNutritionalValueId: + (json['product_nutritional_value_id'] as num?)?.toInt(), productName: json['product_name'] as String, quantity: (json['quantity'] as num).toInt(), portion: json['portion'] as String, fromDiet: json['from_diet'] as bool, + portionState: $enumDecode(_$PortionStateEnumMap, json['state']), ); -Map _$$PortionImplToJson(_$PortionImpl instance) => +Map _$$PortionModelImplToJson(_$PortionModelImpl instance) => { 'id': instance.id, + 'product_nutritional_value_id': instance.productNutritionalValueId, 'product_name': instance.productName, 'quantity': instance.quantity, 'portion': instance.portion, 'from_diet': instance.fromDiet, + 'state': _$PortionStateEnumMap[instance.portionState]!, }; +const _$PortionStateEnumMap = { + PortionState.active: 'active', + PortionState.filled_diet: 'filled_diet', + PortionState.eaten_from_diet: 'eaten_from_diet', + PortionState.eaten: 'eaten', + PortionState.eaten_deleted: 'eaten_deleted', + PortionState.eaten_edited: 'eaten_edited', + PortionState.commented: 'commented', + PortionState.reviewed_by_user: 'reviewed_by_user', +}; + _$TypeOfDiaryModelImpl _$$TypeOfDiaryModelImplFromJson( Map json) => _$TypeOfDiaryModelImpl( diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 4f5c7f31..542240e2 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -102,15 +102,73 @@ class DiaryCubit extends Cubit { required String portion, required int quantity, int? productNutritionalValueId, + required PortionState portionState, }) async { - await diaryRepository.updateProductInMeal( + if (portionState == PortionState.filled_diet || portionState == PortionState.eaten_from_diet) { + // await diaryRepository.deleteProductFromMeal( + // diaryId: diaryId, + // mealId: mealId, + // portionId: portionId, + // ); + // todo delete product from diet + await diaryRepository.addPortionToMeal( + diaryId: diaryId, + mealId: mealId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + ); + } else { + await diaryRepository.updateProductInMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + portionState: portionState, + ); + } + await fetchDiary(); + } + + markEaten({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + await diaryRepository.markEaten( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + ); + await fetchDiary(); + } + + unmarkEaten({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + await diaryRepository.unmarkEaten( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + ); + await fetchDiary(); + } + + restore({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + await diaryRepository.restore( diaryId: diaryId, mealId: mealId, portionId: portionId, - productName: productName, - portion: portion, - quantity: quantity, - productNutritionalValueId: productNutritionalValueId, ); await fetchDiary(); } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 24f916ac..c6c8e291 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -35,7 +35,6 @@ class _DiaryScreenState extends State { CalendarWidget(), if (state is DiaryStateLoading) const Loader(), if (state is DiaryStateLoaded) MealWidget(), - const SizedBox(height: 12), CaloriesChartWidget(), ], ), diff --git a/lib/features/diary/presentation/widgets/add_product_modal.dart b/lib/features/diary/presentation/widgets/add_product_modal.dart index 563beeb4..01c5a64d 100644 --- a/lib/features/diary/presentation/widgets/add_product_modal.dart +++ b/lib/features/diary/presentation/widgets/add_product_modal.dart @@ -10,7 +10,7 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:flutter/material.dart'; class AddProductModal extends StatefulWidget { - final Portion? portion; + final PortionModel? portion; final DiaryStateLoaded state; final DiaryCubit diaryCubit; @@ -221,6 +221,7 @@ class _AddProductModalState extends State { : 'pcs', quantity: int.parse(quantityController.text), productNutritionalValueId: selectedGlossary?.id, + portionState: widget.portion!.portionState, ); } Navigator.of(context).pop(); diff --git a/lib/features/diary/presentation/widgets/product_widget.dart b/lib/features/diary/presentation/widgets/product_widget.dart index 05e0c2b6..3c6e01df 100644 --- a/lib/features/diary/presentation/widgets/product_widget.dart +++ b/lib/features/diary/presentation/widgets/product_widget.dart @@ -1,12 +1,15 @@ +import 'package:dotted_border/dotted_border.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/presentation/widgets/add_product_modal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class ProductWidget extends StatefulWidget { - final Portion portion; + final PortionModel portion; const ProductWidget({ super.key, @@ -20,15 +23,17 @@ class ProductWidget extends StatefulWidget { class _ProductWidgetState extends State { late final DiaryCubit diaryCubit; late String portionName; + late bool isEaten; @override void initState() { super.initState(); diaryCubit = context.read(); portionName = widget.portion == 'gr' ? 'гр' : 'шт'; + isEaten = widget.portion.portionState == PortionState.eaten_from_diet ? true : false; } - updateProductInMeal(DiaryStateLoaded state, Portion portion) { + updateProductInMeal(DiaryStateLoaded state, PortionModel portion) { return showModalBottomSheet( useRootNavigator: true, context: context, @@ -49,16 +54,80 @@ class _ProductWidgetState extends State { return BlocBuilder( builder: (context, state) { if (state is DiaryStateLoaded) { - return ListTile( - onTap: () => updateProductInMeal(state, widget.portion), - trailing: Icon(Icons.more_vert_rounded), - title: Text(widget.portion.productName), - subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - tileColor: Color(0xFFF6FAFF), - ); + if (widget.portion.portionState == PortionState.eaten_deleted) { + return DottedBorder( + dashPattern: [6, 6], + padding: EdgeInsets.zero, + borderPadding: EdgeInsets.zero, + borderType: BorderType.RRect, + radius: Radius.circular(20), + color: Color(0xFF6D6F71), + child: ListTile( + onTap: () => diaryCubit.restore( + diaryId: state.diary.id, + mealId: state.selectedMeal, + portionId: widget.portion.id, + ), + trailing: SvgPicture.asset( + 'assets/icons/revert_product.svg', + ), + horizontalTitleGap: 0, + contentPadding: widget.portion.fromDiet ? EdgeInsets.only(right: 24, left: 4) : null, + title: Text(widget.portion.productName), + subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + tileColor: Color(0xFFF6FAFF03).withValues(alpha: 0.01), + ), + ); + } else { + return ListTile( + onTap: () => updateProductInMeal(state, widget.portion), + trailing: Icon(Icons.more_vert_rounded), + leading: widget.portion.fromDiet + ? Transform.scale( + scale: 1.2, + child: Checkbox( + value: isEaten, + onChanged: (value) { + setState(() { + isEaten = value!; + }); + if (isEaten) { + diaryCubit.markEaten( + diaryId: state.diary.id, + mealId: state.selectedMeal, + portionId: widget.portion.id, + ); + } else { + diaryCubit.unmarkEaten( + diaryId: state.diary.id, + mealId: state.selectedMeal, + portionId: widget.portion.id, + ); + } + }, + activeColor: appTheme.blueA400, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + side: BorderSide( + color: Color(0xFF809CB9), + ), + ), + ) + : null, + horizontalTitleGap: 0, + contentPadding: widget.portion.fromDiet ? EdgeInsets.only(right: 24, left: 4) : null, + title: Text(widget.portion.productName), + subtitle: Text('${widget.portion.quantity} ${widget.portion.portion == 'gr' ? 'гр' : 'шт'}'), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + tileColor: Color(0xFFF6FAFF), + ); + } } return const SizedBox.shrink(); }, diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index d5b696a6..b6d41427 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -51,6 +51,7 @@ class DiaryRepository { required String portion, required int quantity, int? productNutritionalValueId, + required PortionState portionState, }) async { Map data = ProductInMeal( productName: productName, @@ -58,6 +59,7 @@ class DiaryRepository { quantity: quantity, ).toJson(); data['product_nutritional_value_id'] = productNutritionalValueId; + data['state'] = portionState.name; return await diaryService.updateProductInMeal( diary: diaryId, typeNo: mealId, @@ -66,6 +68,42 @@ class DiaryRepository { ); } + Future markEaten({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + return await diaryService.markEaten( + diary: diaryId, + typeNo: mealId, + portionModel: portionId, + ); + } + + Future unmarkEaten({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + return await diaryService.unmarkEaten( + diary: diaryId, + typeNo: mealId, + portionModel: portionId, + ); + } + + Future restore({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + return await diaryService.restore( + diary: diaryId, + typeNo: mealId, + portionModel: portionId, + ); + } + Future deleteProductFromMeal({ required int diaryId, required int mealId, diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index bb14e8cf..6a519b71 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -60,6 +60,27 @@ abstract class DiaryService { @Body() required Map productInMeal, }); + @POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/mark_eaten') + Future markEaten({ + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Path('portionModel') required int portionModel, + }); + + @POST('/api/diary/{diary}/meals/{type_no}/{portionModel}/unmark_eaten') + Future unmarkEaten({ + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Path('portionModel') required int portionModel, + }); + + @POST('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/restore') + Future restore({ + @Path('diary') required int diary, + @Path('type_no') required int typeNo, + @Path('portionModel') required int portionModel, + }); + @DELETE('/api/diary/{diary}/meals/{type_no}/portions/{portionModel}/delete') Future deleteProductFromMeal({ @Path('diary') required int diary, diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 13951947..4544f6bf 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -271,6 +271,93 @@ class _DiaryService implements DiaryService { await _dio.fetch(_options); } + @override + Future markEaten({ + required int diary, + required int typeNo, + required int portionModel, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/${portionModel}/mark_eaten', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future unmarkEaten({ + required int diary, + required int typeNo, + required int portionModel, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/${portionModel}/unmark_eaten', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future restore({ + required int diary, + required int typeNo, + required int portionModel, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diary}/meals/${typeNo}/portions/${portionModel}/restore', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + @override Future deleteProductFromMeal({ required int diary, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 488322fb..6ee797a3 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -907,10 +907,12 @@ class _ProfileScreenState extends State { value: 'view', child: Text('Посмотреть'), ), + if (widget.author == null) PopupMenuItem( value: 'replace', child: Text('Заменить'), ), + if (widget.author == null) PopupMenuItem( value: 'delete', child: Text('Удалить'), diff --git a/test/features/profile/services/profile_service_test.mocks.dart b/test/features/profile/services/profile_service_test.mocks.dart index bb0c633a..c853a94e 100644 --- a/test/features/profile/services/profile_service_test.mocks.dart +++ b/test/features/profile/services/profile_service_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.5 from annotations // in eatvio_app/test/features/profile/services/profile_service_test.dart. // Do not manually edit this file. @@ -25,6 +25,7 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types