From 637ad7cf704bff509b12254c2b48045501804484 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 14:35:06 +0300 Subject: [PATCH 1/9] feat(diary): delete and edit product in meal --- android/app/build.gradle | 8 +- .../diary/presentation/bloc/diary_cubit.dart | 34 +++ .../presentation/pages/diary_screen.dart | 268 +++++++++++------- .../diary/repositories/diary_repository.dart | 32 +++ .../diary/services/diary_service.dart | 28 ++ .../diary/services/diary_service.freezed.dart | 220 ++++++++++++++ .../diary/services/diary_service.g.dart | 77 +++++ .../presentation/pages/profile_screen.dart | 72 ++--- 8 files changed, 595 insertions(+), 144 deletions(-) create mode 100644 lib/features/diary/services/diary_service.freezed.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 8a009c79..e5218dce 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,15 +7,15 @@ plugins { android { namespace = "com.eatvioapp.app" compileSdk = flutter.compileSdkVersion - ndkVersion = "25.1.8937393" + ndkVersion = "28.0.12433566" compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } kotlinOptions { - jvmTarget = 17 + jvmTarget = 21 } defaultConfig { diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index d5b4fede..8d48da04 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -41,6 +41,40 @@ class DiaryCubit extends Cubit { await getDiaryDay(date, false); } + updateProductInMeal({ + required int diaryId, + required int mealId, + required int portionId, + required String productName, + required String portion, + required int quantity, + required String date, + }) async { + await diaryRepository.updateProductInMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + productName: productName, + portion: portion, + quantity: quantity, + ); + await getDiaryDay(date, false); + } + + deleteProductFromMeal({ + required int diaryId, + required int mealId, + required int portionId, + required String date, + }) async { + await diaryRepository.deleteProductFromMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + ); + await getDiaryDay(date, false); + } + Future> getGlossaryListByQuery(String query) async { return await glossaryRepository.getGlossaryListByQuery(query); } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 32b70516..87394b16 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -85,12 +85,14 @@ class _DiaryScreenState extends State { ); } - _products(List portions) { + _products(List portions, DiaryDayModel diaryDay) { return Column( mainAxisSize: MainAxisSize.min, children: [ for (int i = 0; i < portions.length; i++) ...[ _product( + diaryDay, + portions[i].id, portions[i].productName, portions[i].quantity, portions[i].portion, @@ -101,10 +103,16 @@ class _DiaryScreenState extends State { ); } - _product(String product, int quantity, String portion) { + _product(DiaryDayModel diaryDay, int id, String product, int quantity, String portion) { portion == 'gr' ? portion = 'г' : portion = 'шт'; return ListTile( - onTap: () {}, + onTap: () { + setState(() { + productNameController.text = product; + quantityController.text = quantity.toString(); + }); + _modalBottomSheet(diaryDay, id); + }, trailing: Icon(Icons.more_vert_rounded), title: Text(product), subtitle: Text('$quantity $portion'), @@ -157,7 +165,7 @@ class _DiaryScreenState extends State { const SizedBox(height: 12), _divider(context), const SizedBox(height: 12), - _products(diaryDay.meals[currentSelectedMealNumber - 1].portions), + _products(diaryDay.meals[currentSelectedMealNumber - 1].portions, diaryDay), const SizedBox(height: 16), _addProductButton(diaryDay), ], @@ -285,117 +293,156 @@ class _DiaryScreenState extends State { ); } - _addProductButton(DiaryDayModel diaryDay) { - return InkWell( - onTap: () { - setState(() { - selectedGlossary = null; - productNameController.clear(); - quantityController.clear(); - }); - showModalBottomSheet( - useRootNavigator: true, - context: context, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(12)), - ), - isScrollControlled: true, - builder: (context) { - return StatefulBuilder( - builder: (BuildContext context, void Function(void Function()) setState) { - return Padding( - padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Добавление блюда', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Color(0xFF323F4B), - ), - ), - const SizedBox(height: 16), - TypeAheadField( - controller: productNameController, - itemBuilder: (context, glossary) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - glossary.name, - style: TextStyle( - height: 1, - fontSize: 16, - ), - ), - ); - }, - itemSeparatorBuilder: (context, index) => _divider(context), - onSelected: (newSelectedGlossary) { - setState(() { - selectedGlossary = newSelectedGlossary; - productNameController.text = selectedGlossary!.name; - }); - }, - suggestionsCallback: (query) async { - if (query.length < 1) return []; - return await diaryCubit.getGlossaryListByQuery(query); - }, - builder: (context, controller, focusNode) { - return CustomTextFormField( - controller: controller, - focusNode: focusNode, - autofocus: true, - hintText: 'Введите название продукта/блюда', - ); - }, - emptyBuilder: (context) => const SizedBox.shrink(), - errorBuilder: (context, object) => Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Нет подходящих вариантов'), - ), - ), - if (selectedGlossary != null) - Padding( - padding: const EdgeInsets.only(top: 8), - child: CustomTextFormField( - controller: quantityController, - keyboardType: TextInputType.number, - hintText: selectedGlossary!.grInPortion == 0 ? '100' : '1', - suffixText: selectedGlossary != null - ? selectedGlossary!.grInPortion == 0 - ? 'гр' - : 'шт' - : null, + _modalBottomSheet(DiaryDayModel diaryDay, [int? portionId]) { + return showModalBottomSheet( + useRootNavigator: true, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + isScrollControlled: true, + builder: (context) { + return StatefulBuilder( + builder: (BuildContext context, void Function(void Function()) setState) { + return Padding( + padding: const EdgeInsets.all(16) + EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Добавление блюда', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Color(0xFF323F4B), + ), + ), + const SizedBox(height: 16), + TypeAheadField( + controller: productNameController, + itemBuilder: (context, glossary) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + glossary.name, + style: TextStyle( + height: 1, + fontSize: 16, ), ), - const SizedBox(height: 16), + ); + }, + itemSeparatorBuilder: (context, index) => _divider(context), + onSelected: (newSelectedGlossary) { + setState(() { + selectedGlossary = newSelectedGlossary; + productNameController.text = selectedGlossary!.name; + }); + }, + suggestionsCallback: (query) async { + if (query.length < 1) return []; + final suggestionsList = await diaryCubit.getGlossaryListByQuery(query); + if (suggestionsList.length == 1) { + setState(() { + selectedGlossary = suggestionsList.first; + }); + } + return suggestionsList; + }, + builder: (context, controller, focusNode) { + return CustomTextFormField( + controller: controller, + autofocus: true, + focusNode: focusNode, + hintText: 'Введите название продукта/блюда', + ); + }, + emptyBuilder: (context) => const SizedBox.shrink(), + errorBuilder: (context, object) => Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Нет подходящих вариантов'), + ), + ), + if (selectedGlossary != null) + Padding( + padding: const EdgeInsets.only(top: 8), + child: CustomTextFormField( + controller: quantityController, + keyboardType: TextInputType.number, + hintText: selectedGlossary!.grInPortion == 0 ? '100' : '1', + autofocus: true, + suffixText: selectedGlossary != null + ? selectedGlossary!.grInPortion == 0 + ? 'гр' + : 'шт' + : null, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + if (portionId != null) + InkWell( + onTap: () { + diaryCubit.deleteProductFromMeal( + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + portionId: portionId!, + date: _selectedDay.toString(), + ); + Navigator.of(context).pop(); + }, + borderRadius: BorderRadius.circular(20), + child: Ink( + width: 52, + height: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: Color(0xFFF6F6F6), + ), + child: Icon( + Icons.delete_outline_rounded, + color: Colors.red, + ), + ), + ), + if (portionId != null) const SizedBox(width: 8), InkWell( onTap: () { - diaryCubit.addProductToMeal( - diaryId: diaryDay.id, - mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, - productName: productNameController.text, - portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs', - quantity: int.parse(quantityController.text), - date: _selectedDay.toString(), - ); + if (portionId == null) { + diaryCubit.addProductToMeal( + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + productName: productNameController.text, + portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs', + quantity: int.parse(quantityController.text), + date: _selectedDay.toString(), + ); + } else { + diaryCubit.updateProductInMeal( + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + portionId: portionId, + productName: productNameController.text, + portion: selectedGlossary!.grInPortion == 0 ? 'gr' : 'pcs', + quantity: int.parse(quantityController.text), + date: _selectedDay.toString(), + ); + } Navigator.of(context).pop(); }, borderRadius: BorderRadius.circular(20), child: Ink( + width: MediaQuery.of(context).size.width - (portionId == null ? 32 : 92), + height: 52, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: appTheme.blueA400, ), - child: Container( - width: MediaQuery.of(context).size.width - 32, - padding: const EdgeInsets.all(16), - alignment: Alignment.center, + child: Center( child: Text( - 'Добавить', + portionId == null ? 'Добавить' : 'Сохранить', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, @@ -407,12 +454,25 @@ class _DiaryScreenState extends State { ), ], ), - ); - }, + ], + ), ); }, ); }, + ); + } + + _addProductButton(DiaryDayModel diaryDay) { + return InkWell( + onTap: () { + setState(() { + selectedGlossary = null; + productNameController.clear(); + quantityController.clear(); + }); + _modalBottomSheet(diaryDay); + }, borderRadius: BorderRadius.circular(20), child: Ink( padding: EdgeInsets.symmetric(horizontal: 16), diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index dde8a702..d76219b5 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -34,4 +34,36 @@ class DiaryRepository { quantity: quantity, ); } + + Future updateProductInMeal({ + required int diaryId, + required int mealId, + required int portionId, + required String productName, + required String portion, + required int quantity, + }) async { + return await diaryService.updateProductInMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + productInMeal: ProductInMeal( + productName: productName, + portion: portion, + quantity: quantity, + ), + ); + } + + Future deleteProductFromMeal({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + return await diaryService.deleteProductFromMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + ); + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index cdce2dd4..0b16782c 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -1,10 +1,23 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/diary_index_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:retrofit/retrofit.dart'; +part 'diary_service.freezed.dart'; part 'diary_service.g.dart'; +@freezed +class ProductInMeal with _$ProductInMeal { + const factory ProductInMeal({ + @JsonKey(name: 'product_name') required String productName, + @JsonKey(name: 'portion') required String portion, + @JsonKey(name: 'quantity') required int quantity, + }) = _ProductInMeal; + + factory ProductInMeal.fromJson(Map json) => _$ProductInMealFromJson(json); +} + @RestApi() abstract class DiaryService { factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService; @@ -27,4 +40,19 @@ abstract class DiaryService { @Part(name: 'portion') required String portion, @Part(name: 'quantity') required int quantity, }); + + @PUT('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/update') + Future updateProductInMeal({ + @Path('diary_id') required int diaryId, + @Path('meal_id') required int mealId, + @Path('portion_id') required int portionId, + @Body() required ProductInMeal productInMeal, + }); + + @DELETE('/api/diary/{diary_id}/meal/{meal_id}/portions/{portion_id}/delete') + Future deleteProductFromMeal({ + @Path('diary_id') required int diaryId, + @Path('meal_id') required int mealId, + @Path('portion_id') required int portionId, + }); } diff --git a/lib/features/diary/services/diary_service.freezed.dart b/lib/features/diary/services/diary_service.freezed.dart new file mode 100644 index 00000000..258040d1 --- /dev/null +++ b/lib/features/diary/services/diary_service.freezed.dart @@ -0,0 +1,220 @@ +// 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 'diary_service.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#adding-getters-and-methods-to-our-models'); + +ProductInMeal _$ProductInMealFromJson(Map json) { + return _ProductInMeal.fromJson(json); +} + +/// @nodoc +mixin _$ProductInMeal { + @JsonKey(name: 'product_name') + String get productName => throw _privateConstructorUsedError; + @JsonKey(name: 'portion') + String get portion => throw _privateConstructorUsedError; + @JsonKey(name: 'quantity') + int get quantity => throw _privateConstructorUsedError; + + /// Serializes this ProductInMeal to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductInMealCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductInMealCopyWith<$Res> { + factory $ProductInMealCopyWith( + ProductInMeal value, $Res Function(ProductInMeal) then) = + _$ProductInMealCopyWithImpl<$Res, ProductInMeal>; + @useResult + $Res call( + {@JsonKey(name: 'product_name') String productName, + @JsonKey(name: 'portion') String portion, + @JsonKey(name: 'quantity') int quantity}); +} + +/// @nodoc +class _$ProductInMealCopyWithImpl<$Res, $Val extends ProductInMeal> + implements $ProductInMealCopyWith<$Res> { + _$ProductInMealCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? productName = null, + Object? portion = null, + Object? quantity = null, + }) { + return _then(_value.copyWith( + productName: null == productName + ? _value.productName + : productName // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProductInMealImplCopyWith<$Res> + implements $ProductInMealCopyWith<$Res> { + factory _$$ProductInMealImplCopyWith( + _$ProductInMealImpl value, $Res Function(_$ProductInMealImpl) then) = + __$$ProductInMealImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: 'product_name') String productName, + @JsonKey(name: 'portion') String portion, + @JsonKey(name: 'quantity') int quantity}); +} + +/// @nodoc +class __$$ProductInMealImplCopyWithImpl<$Res> + extends _$ProductInMealCopyWithImpl<$Res, _$ProductInMealImpl> + implements _$$ProductInMealImplCopyWith<$Res> { + __$$ProductInMealImplCopyWithImpl( + _$ProductInMealImpl _value, $Res Function(_$ProductInMealImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? productName = null, + Object? portion = null, + Object? quantity = null, + }) { + return _then(_$ProductInMealImpl( + productName: null == productName + ? _value.productName + : productName // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductInMealImpl implements _ProductInMeal { + const _$ProductInMealImpl( + {@JsonKey(name: 'product_name') required this.productName, + @JsonKey(name: 'portion') required this.portion, + @JsonKey(name: 'quantity') required this.quantity}); + + factory _$ProductInMealImpl.fromJson(Map json) => + _$$ProductInMealImplFromJson(json); + + @override + @JsonKey(name: 'product_name') + final String productName; + @override + @JsonKey(name: 'portion') + final String portion; + @override + @JsonKey(name: 'quantity') + final int quantity; + + @override + String toString() { + return 'ProductInMeal(productName: $productName, portion: $portion, quantity: $quantity)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductInMealImpl && + (identical(other.productName, productName) || + other.productName == productName) && + (identical(other.portion, portion) || other.portion == portion) && + (identical(other.quantity, quantity) || + other.quantity == quantity)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, productName, portion, quantity); + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductInMealImplCopyWith<_$ProductInMealImpl> get copyWith => + __$$ProductInMealImplCopyWithImpl<_$ProductInMealImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProductInMealImplToJson( + this, + ); + } +} + +abstract class _ProductInMeal implements ProductInMeal { + const factory _ProductInMeal( + {@JsonKey(name: 'product_name') required final String productName, + @JsonKey(name: 'portion') required final String portion, + @JsonKey(name: 'quantity') required final int quantity}) = + _$ProductInMealImpl; + + factory _ProductInMeal.fromJson(Map json) = + _$ProductInMealImpl.fromJson; + + @override + @JsonKey(name: 'product_name') + String get productName; + @override + @JsonKey(name: 'portion') + String get portion; + @override + @JsonKey(name: 'quantity') + int get quantity; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductInMealImplCopyWith<_$ProductInMealImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index b145757c..177dd20f 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -2,6 +2,24 @@ part of 'diary_service.dart'; +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProductInMealImpl _$$ProductInMealImplFromJson(Map json) => + _$ProductInMealImpl( + productName: json['product_name'] as String, + portion: json['portion'] as String, + quantity: (json['quantity'] as num).toInt(), + ); + +Map _$$ProductInMealImplToJson(_$ProductInMealImpl instance) => + { + 'product_name': instance.productName, + 'portion': instance.portion, + 'quantity': instance.quantity, + }; + // ************************************************************************** // RetrofitGenerator // ************************************************************************** @@ -130,6 +148,65 @@ class _DiaryService implements DiaryService { await _dio.fetch(_options); } + @override + Future updateProductInMeal({ + required int diaryId, + required int mealId, + required int portionId, + required ProductInMeal productInMeal, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = productInMeal; + final _options = _setStreamType(Options( + method: 'PUT', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/update', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future deleteProductFromMeal({ + required int diaryId, + required int mealId, + required int portionId, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'DELETE', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diaryId}/meal/${mealId}/portions/${portionId}/delete', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index b94f2f71..380d37c4 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -112,7 +112,7 @@ class _ProfileScreenState extends State { ), ), _subscriptionsAndSubscribers(state), - _badges(state), + if(state.profile.badges.isNotEmpty) _badges(state), if (widget.author == null) const SizedBox(height: 16), _socialLinks(state), if (widget.author == null) const SizedBox(height: 16), @@ -201,42 +201,39 @@ class _ProfileScreenState extends State { } _newBlogPostButton() { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Material( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: InkWell( - onTap: () { - GoRouter.of(context).pushNamed('blogPostCreateScreen'); - }, - borderRadius: BorderRadius.circular(8), - child: Ink( - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - color: Color(0xFFF6FAFF), - borderRadius: BorderRadius.circular(8), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.add_circle_outline_rounded, + return Material( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + onTap: () { + GoRouter.of(context).pushNamed('blogPostCreateScreen'); + }, + borderRadius: BorderRadius.circular(8), + child: Ink( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Color(0xFFF6FAFF), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_circle_outline_rounded, + color: Color(0xFF2F80ED), + ), + const SizedBox(width: 12), + Text( + 'Новая запись', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, color: Color(0xFF2F80ED), ), - const SizedBox(width: 12), - Text( - 'Новая запись', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Color(0xFF2F80ED), - ), - ), - ], - ), + ), + ], ), ), ), @@ -672,7 +669,8 @@ class _ProfileScreenState extends State { state.profile.name, style: TextStyle( color: Colors.black, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w600, + fontSize: 16, height: 1, ), ), @@ -689,6 +687,8 @@ class _ProfileScreenState extends State { 'Дополнительно', style: TextStyle( color: Color(0xFF6D6F71), + fontSize: 14, + fontWeight: FontWeight.w500, ), ), ], From ce4eb0a92e6e0072b423f59b9dddcfcd701685c6 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 15:23:45 +0300 Subject: [PATCH 2/9] feat(diary): fix dart analyze, add photo placeholder --- assets/no_one_image_was_added.svg | 8 ++++++++ .../diary/presentation/pages/diary_screen.dart | 14 ++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 assets/no_one_image_was_added.svg diff --git a/assets/no_one_image_was_added.svg b/assets/no_one_image_was_added.svg new file mode 100644 index 00000000..dc20c61c --- /dev/null +++ b/assets/no_one_image_was_added.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 87394b16..1d1054b3 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -7,6 +7,7 @@ import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:rive/rive.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -168,6 +169,11 @@ class _DiaryScreenState extends State { _products(diaryDay.meals[currentSelectedMealNumber - 1].portions, diaryDay), const SizedBox(height: 16), _addProductButton(diaryDay), + const SizedBox(height: 24), + SvgPicture.asset( + width: MediaQuery.of(context).size.width - 32, + 'assets/no_one_image_was_added.svg', + ), ], ), ), @@ -372,11 +378,7 @@ class _DiaryScreenState extends State { keyboardType: TextInputType.number, hintText: selectedGlossary!.grInPortion == 0 ? '100' : '1', autofocus: true, - suffixText: selectedGlossary != null - ? selectedGlossary!.grInPortion == 0 - ? 'гр' - : 'шт' - : null, + suffixText: selectedGlossary!.grInPortion == 0 ? 'гр' : 'шт', ), ), const SizedBox(height: 16), @@ -388,7 +390,7 @@ class _DiaryScreenState extends State { diaryCubit.deleteProductFromMeal( diaryId: diaryDay.id, mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, - portionId: portionId!, + portionId: portionId, date: _selectedDay.toString(), ); Navigator.of(context).pop(); From c7707c09196ba3c59fc31b694afcfc1f164005d5 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 15:40:38 +0300 Subject: [PATCH 3/9] feat(diary): add image pick --- .gitea/workflows/test.yml | 6 +++--- .../diary/presentation/pages/diary_screen.dart | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 3ab485dc..883dee6a 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -3,8 +3,8 @@ name: Eatvio App Android Test on: [ pull_request ] env: - flutter_version: "3.24.2" - java_version: "17.x" + flutter_version: "3.24.4" + java_version: "21.x" flutter_channel: "stable" jobs: @@ -24,7 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt-get install -y openjdk-11-jdk wget + apt-get install -y openjdk-21-jdk wget wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 1d1054b3..8998791f 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:rive/rive.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -170,9 +171,15 @@ class _DiaryScreenState extends State { const SizedBox(height: 16), _addProductButton(diaryDay), const SizedBox(height: 24), - SvgPicture.asset( - width: MediaQuery.of(context).size.width - 32, - 'assets/no_one_image_was_added.svg', + GestureDetector( + onTap: () async { + final picker = ImagePicker(); + List? images = await picker.pickMultiImage(); + }, + child: SvgPicture.asset( + width: MediaQuery.of(context).size.width - 32, + 'assets/no_one_image_was_added.svg', + ), ), ], ), From b0c687e5fe263384988752f44811d4a416e1aba0 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 15:50:58 +0300 Subject: [PATCH 4/9] feat(diary): fix analzye --- .../presentation/pages/diary_screen.dart | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 8998791f..5b329b80 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -5,11 +5,12 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:eatvio_app/widgets/custom_text_form_field.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; -import 'package:image_picker/image_picker.dart'; +// import 'package:image_picker/image_picker.dart'; import 'package:rive/rive.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -172,10 +173,7 @@ class _DiaryScreenState extends State { _addProductButton(diaryDay), const SizedBox(height: 24), GestureDetector( - onTap: () async { - final picker = ImagePicker(); - List? images = await picker.pickMultiImage(); - }, + onTap: () => showCupertinoActionSheet(), child: SvgPicture.asset( width: MediaQuery.of(context).size.width - 32, 'assets/no_one_image_was_added.svg', @@ -187,6 +185,47 @@ class _DiaryScreenState extends State { ); } + showCupertinoActionSheet() { + showCupertinoModalPopup( + context: context, + builder: (_) => CupertinoActionSheet( + actions: [ + CupertinoActionSheetAction( + onPressed: () async { + // final picker = ImagePicker(); + // List? images = await picker.pickMultiImage(); + }, + child: Row( + children: [ + const Icon(Icons.photo), + const SizedBox(width: 8), + const Text('Галерея'), + ], + ), + ), + CupertinoActionSheetAction( + onPressed: () async { + // final picker = ImagePicker(); + // XFile? image = await picker.pickImage(source: ImageSource.camera); + }, + child: Row( + children: [ + const Icon(Icons.photo_camera), + const SizedBox(width: 8), + const Text('Фото'), + ], + ), + ), + ], + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + isDestructiveAction: true, + child: const Text('Отмена'), + ), + ), + ); + } + _mealButton(int mealNumber) { bool isActive = currentSelectedMealNumber == mealNumber; From f9d5f592e3b8f8c8d64d0297df4f60ba63fed6dc Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 12:57:10 +0000 Subject: [PATCH 5/9] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20.gitea/workflows/test.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 883dee6a..86658705 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt-get install -y openjdk-21-jdk wget + apt install openjdk-21-jdk wget wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest From 6ae79c654d147517d2a31366b4027c2937f20b53 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 30 Oct 2024 12:58:08 +0000 Subject: [PATCH 6/9] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20.gitea/workflows/test.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 86658705..ed756ee9 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt install openjdk-21-jdk wget + apt install -y openjdk-21-jdk wget wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest From 972ee605be6db19a0e5f4d48c87cbe1e41b162b6 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 1 Nov 2024 05:24:08 +0300 Subject: [PATCH 7/9] feat(diary): add gallery --- .gitea/workflows/test.yml | 2 +- .../diary/models/diary_day_model.dart | 15 +- .../diary/models/diary_day_model.freezed.dart | 242 ++++++++++++++++-- .../diary/models/diary_day_model.g.dart | 18 +- .../diary/presentation/bloc/diary_cubit.dart | 31 +++ .../presentation/pages/diary_screen.dart | 172 +++++++++++-- .../diary/repositories/diary_repository.dart | 36 +++ .../diary/services/diary_service.dart | 30 ++- .../diary/services/diary_service.freezed.dart | 220 ---------------- .../diary/services/diary_service.g.dart | 88 +++++-- 10 files changed, 555 insertions(+), 299 deletions(-) delete mode 100644 lib/features/diary/services/diary_service.freezed.dart diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 883dee6a..ed756ee9 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt-get install -y openjdk-21-jdk wget + apt install -y openjdk-21-jdk wget wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest diff --git a/lib/features/diary/models/diary_day_model.dart b/lib/features/diary/models/diary_day_model.dart index 773920dc..e4d8a26f 100644 --- a/lib/features/diary/models/diary_day_model.dart +++ b/lib/features/diary/models/diary_day_model.dart @@ -47,10 +47,10 @@ class MealModel with _$MealModel { @freezed class DiaryPhotoModel with _$DiaryPhotoModel { const factory DiaryPhotoModel({ - @JsonKey(name: "id") required String id, + @JsonKey(name: "id") required int id, @JsonKey(name: "original_name") required String originalName, @JsonKey(name: "path") required String path, - @JsonKey(name: "size") required String size, + @JsonKey(name: "size") required int size, }) = _DiaryPhotoModel; factory DiaryPhotoModel.fromJson(Map json) => _$DiaryPhotoModelFromJson(json); @@ -78,3 +78,14 @@ class TypeOfDiaryModel with _$TypeOfDiaryModel { factory TypeOfDiaryModel.fromJson(Map json) => _$TypeOfDiaryModelFromJson(json); } + +@freezed +class ProductInMeal with _$ProductInMeal { + const factory ProductInMeal({ + @JsonKey(name: 'product_name') required String productName, + @JsonKey(name: 'portion') required String portion, + @JsonKey(name: 'quantity') required int quantity, + }) = _ProductInMeal; + + factory ProductInMeal.fromJson(Map json) => _$ProductInMealFromJson(json); +} diff --git a/lib/features/diary/models/diary_day_model.freezed.dart b/lib/features/diary/models/diary_day_model.freezed.dart index dc780145..72b0d70b 100644 --- a/lib/features/diary/models/diary_day_model.freezed.dart +++ b/lib/features/diary/models/diary_day_model.freezed.dart @@ -850,13 +850,13 @@ DiaryPhotoModel _$DiaryPhotoModelFromJson(Map json) { /// @nodoc mixin _$DiaryPhotoModel { @JsonKey(name: "id") - String get id => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; @JsonKey(name: "original_name") String get originalName => throw _privateConstructorUsedError; @JsonKey(name: "path") String get path => throw _privateConstructorUsedError; @JsonKey(name: "size") - String get size => throw _privateConstructorUsedError; + int get size => throw _privateConstructorUsedError; /// Serializes this DiaryPhotoModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -875,10 +875,10 @@ abstract class $DiaryPhotoModelCopyWith<$Res> { _$DiaryPhotoModelCopyWithImpl<$Res, DiaryPhotoModel>; @useResult $Res call( - {@JsonKey(name: "id") String id, + {@JsonKey(name: "id") int id, @JsonKey(name: "original_name") String originalName, @JsonKey(name: "path") String path, - @JsonKey(name: "size") String size}); + @JsonKey(name: "size") int size}); } /// @nodoc @@ -905,7 +905,7 @@ class _$DiaryPhotoModelCopyWithImpl<$Res, $Val extends DiaryPhotoModel> id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as String, + as int, originalName: null == originalName ? _value.originalName : originalName // ignore: cast_nullable_to_non_nullable @@ -917,7 +917,7 @@ class _$DiaryPhotoModelCopyWithImpl<$Res, $Val extends DiaryPhotoModel> size: null == size ? _value.size : size // ignore: cast_nullable_to_non_nullable - as String, + as int, ) as $Val); } } @@ -931,10 +931,10 @@ abstract class _$$DiaryPhotoModelImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: "id") String id, + {@JsonKey(name: "id") int id, @JsonKey(name: "original_name") String originalName, @JsonKey(name: "path") String path, - @JsonKey(name: "size") String size}); + @JsonKey(name: "size") int size}); } /// @nodoc @@ -959,7 +959,7 @@ class __$$DiaryPhotoModelImplCopyWithImpl<$Res> id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable - as String, + as int, originalName: null == originalName ? _value.originalName : originalName // ignore: cast_nullable_to_non_nullable @@ -971,7 +971,7 @@ class __$$DiaryPhotoModelImplCopyWithImpl<$Res> size: null == size ? _value.size : size // ignore: cast_nullable_to_non_nullable - as String, + as int, )); } } @@ -990,7 +990,7 @@ class _$DiaryPhotoModelImpl implements _DiaryPhotoModel { @override @JsonKey(name: "id") - final String id; + final int id; @override @JsonKey(name: "original_name") final String originalName; @@ -999,7 +999,7 @@ class _$DiaryPhotoModelImpl implements _DiaryPhotoModel { final String path; @override @JsonKey(name: "size") - final String size; + final int size; @override String toString() { @@ -1041,18 +1041,17 @@ class _$DiaryPhotoModelImpl implements _DiaryPhotoModel { abstract class _DiaryPhotoModel implements DiaryPhotoModel { const factory _DiaryPhotoModel( - {@JsonKey(name: "id") required final String id, - @JsonKey(name: "original_name") required final String originalName, - @JsonKey(name: "path") required final String path, - @JsonKey(name: "size") required final String size}) = - _$DiaryPhotoModelImpl; + {@JsonKey(name: "id") required final int id, + @JsonKey(name: "original_name") required final String originalName, + @JsonKey(name: "path") required final String path, + @JsonKey(name: "size") required final int size}) = _$DiaryPhotoModelImpl; factory _DiaryPhotoModel.fromJson(Map json) = _$DiaryPhotoModelImpl.fromJson; @override @JsonKey(name: "id") - String get id; + int get id; @override @JsonKey(name: "original_name") String get originalName; @@ -1061,7 +1060,7 @@ abstract class _DiaryPhotoModel implements DiaryPhotoModel { String get path; @override @JsonKey(name: "size") - String get size; + int get size; /// Create a copy of DiaryPhotoModel /// with the given fields replaced by the non-null parameter values. @@ -1502,3 +1501,208 @@ abstract class _TypeOfDiaryModel implements TypeOfDiaryModel { _$$TypeOfDiaryModelImplCopyWith<_$TypeOfDiaryModelImpl> get copyWith => throw _privateConstructorUsedError; } + +ProductInMeal _$ProductInMealFromJson(Map json) { + return _ProductInMeal.fromJson(json); +} + +/// @nodoc +mixin _$ProductInMeal { + @JsonKey(name: 'product_name') + String get productName => throw _privateConstructorUsedError; + @JsonKey(name: 'portion') + String get portion => throw _privateConstructorUsedError; + @JsonKey(name: 'quantity') + int get quantity => throw _privateConstructorUsedError; + + /// Serializes this ProductInMeal to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductInMealCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductInMealCopyWith<$Res> { + factory $ProductInMealCopyWith( + ProductInMeal value, $Res Function(ProductInMeal) then) = + _$ProductInMealCopyWithImpl<$Res, ProductInMeal>; + @useResult + $Res call( + {@JsonKey(name: 'product_name') String productName, + @JsonKey(name: 'portion') String portion, + @JsonKey(name: 'quantity') int quantity}); +} + +/// @nodoc +class _$ProductInMealCopyWithImpl<$Res, $Val extends ProductInMeal> + implements $ProductInMealCopyWith<$Res> { + _$ProductInMealCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? productName = null, + Object? portion = null, + Object? quantity = null, + }) { + return _then(_value.copyWith( + productName: null == productName + ? _value.productName + : productName // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProductInMealImplCopyWith<$Res> + implements $ProductInMealCopyWith<$Res> { + factory _$$ProductInMealImplCopyWith( + _$ProductInMealImpl value, $Res Function(_$ProductInMealImpl) then) = + __$$ProductInMealImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: 'product_name') String productName, + @JsonKey(name: 'portion') String portion, + @JsonKey(name: 'quantity') int quantity}); +} + +/// @nodoc +class __$$ProductInMealImplCopyWithImpl<$Res> + extends _$ProductInMealCopyWithImpl<$Res, _$ProductInMealImpl> + implements _$$ProductInMealImplCopyWith<$Res> { + __$$ProductInMealImplCopyWithImpl( + _$ProductInMealImpl _value, $Res Function(_$ProductInMealImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? productName = null, + Object? portion = null, + Object? quantity = null, + }) { + return _then(_$ProductInMealImpl( + productName: null == productName + ? _value.productName + : productName // ignore: cast_nullable_to_non_nullable + as String, + portion: null == portion + ? _value.portion + : portion // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductInMealImpl implements _ProductInMeal { + const _$ProductInMealImpl( + {@JsonKey(name: 'product_name') required this.productName, + @JsonKey(name: 'portion') required this.portion, + @JsonKey(name: 'quantity') required this.quantity}); + + factory _$ProductInMealImpl.fromJson(Map json) => + _$$ProductInMealImplFromJson(json); + + @override + @JsonKey(name: 'product_name') + final String productName; + @override + @JsonKey(name: 'portion') + final String portion; + @override + @JsonKey(name: 'quantity') + final int quantity; + + @override + String toString() { + return 'ProductInMeal(productName: $productName, portion: $portion, quantity: $quantity)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductInMealImpl && + (identical(other.productName, productName) || + other.productName == productName) && + (identical(other.portion, portion) || other.portion == portion) && + (identical(other.quantity, quantity) || + other.quantity == quantity)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, productName, portion, quantity); + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductInMealImplCopyWith<_$ProductInMealImpl> get copyWith => + __$$ProductInMealImplCopyWithImpl<_$ProductInMealImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProductInMealImplToJson( + this, + ); + } +} + +abstract class _ProductInMeal implements ProductInMeal { + const factory _ProductInMeal( + {@JsonKey(name: 'product_name') required final String productName, + @JsonKey(name: 'portion') required final String portion, + @JsonKey(name: 'quantity') required final int quantity}) = + _$ProductInMealImpl; + + factory _ProductInMeal.fromJson(Map json) = + _$ProductInMealImpl.fromJson; + + @override + @JsonKey(name: 'product_name') + String get productName; + @override + @JsonKey(name: 'portion') + String get portion; + @override + @JsonKey(name: 'quantity') + int get quantity; + + /// Create a copy of ProductInMeal + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductInMealImplCopyWith<_$ProductInMealImpl> 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 05d36efa..93ece876 100644 --- a/lib/features/diary/models/diary_day_model.g.dart +++ b/lib/features/diary/models/diary_day_model.g.dart @@ -74,10 +74,10 @@ Map _$$MealModelImplToJson(_$MealModelImpl instance) => _$DiaryPhotoModelImpl _$$DiaryPhotoModelImplFromJson( Map json) => _$DiaryPhotoModelImpl( - id: json['id'] as String, + id: (json['id'] as num).toInt(), originalName: json['original_name'] as String, path: json['path'] as String, - size: json['size'] as String, + size: (json['size'] as num).toInt(), ); Map _$$DiaryPhotoModelImplToJson( @@ -120,3 +120,17 @@ Map _$$TypeOfDiaryModelImplToJson( 'has_chosen_diet': instance.hasChosenDiet, 'has_chosen_plan': instance.hasChosenPlan, }; + +_$ProductInMealImpl _$$ProductInMealImplFromJson(Map json) => + _$ProductInMealImpl( + productName: json['product_name'] as String, + portion: json['portion'] as String, + quantity: (json['quantity'] as num).toInt(), + ); + +Map _$$ProductInMealImplToJson(_$ProductInMealImpl instance) => + { + 'product_name': instance.productName, + 'portion': instance.portion, + 'quantity': instance.quantity, + }; diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 8d48da04..d7f31da6 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart'; import 'package:eatvio_app/features/diary/repositories/diary_repository.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; @@ -78,4 +81,32 @@ class DiaryCubit extends Cubit { Future> getGlossaryListByQuery(String query) async { return await glossaryRepository.getGlossaryListByQuery(query); } + + uploadPhotos({ + required List files, + required int diaryId, + required int mealId, + required String date, + }) async { + await diaryRepository.uploadPhotos( + files: files, + diaryId: diaryId, + mealId: mealId, + ); + await getDiaryDay(date, false); + } + + deletePhoto({ + required DiaryPhotoModel photo, + required int diaryId, + required int mealId, + required String date, + }) async { + await diaryRepository.deletePhoto( + photo: photo, + diaryId: diaryId, + mealId: mealId, + ); + await getDiaryDay(date, false); + } } diff --git a/lib/features/diary/presentation/pages/diary_screen.dart b/lib/features/diary/presentation/pages/diary_screen.dart index 5b329b80..bb9e530a 100644 --- a/lib/features/diary/presentation/pages/diary_screen.dart +++ b/lib/features/diary/presentation/pages/diary_screen.dart @@ -1,3 +1,6 @@ +import 'dart:io'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:eatvio_app/core/const.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'; @@ -10,7 +13,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; -// import 'package:image_picker/image_picker.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:rive/rive.dart'; import 'package:table_calendar/table_calendar.dart'; @@ -30,6 +33,7 @@ class _DiaryScreenState extends State { GlossaryModel? selectedGlossary; final productNameController = TextEditingController(); final quantityController = TextEditingController(); + int currentSelectedPhotoIndex = 0; @override Widget build(BuildContext context) { @@ -172,47 +176,69 @@ class _DiaryScreenState extends State { const SizedBox(height: 16), _addProductButton(diaryDay), const SizedBox(height: 24), - GestureDetector( - onTap: () => showCupertinoActionSheet(), - child: SvgPicture.asset( - width: MediaQuery.of(context).size.width - 32, - 'assets/no_one_image_was_added.svg', + if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isEmpty) + GestureDetector( + onTap: () => _showImagePicker(diaryDay), + child: SvgPicture.asset( + width: MediaQuery.of(context).size.width - 32, + 'assets/no_one_image_was_added.svg', + ), + ), + if (diaryDay.meals[currentSelectedMealNumber - 1].photos.isNotEmpty) + _photoGallery( + diaryDay.meals[currentSelectedMealNumber - 1].photos, + diaryDay, ), - ), ], ), ), ); } - showCupertinoActionSheet() { - showCupertinoModalPopup( + _showImagePicker(DiaryDayModel diaryDay) { + showCupertinoModalPopup( context: context, - builder: (_) => CupertinoActionSheet( + builder: (context) => CupertinoActionSheet( actions: [ CupertinoActionSheetAction( onPressed: () async { - // final picker = ImagePicker(); - // List? images = await picker.pickMultiImage(); + final picker = ImagePicker(); + XFile? image = await picker.pickImage(source: ImageSource.camera); + if (image != null) + diaryCubit.uploadPhotos( + files: [File(image.path)], + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + date: _selectedDay.toString(), + ); + Navigator.of(context).pop(); }, - child: Row( - children: [ - const Icon(Icons.photo), - const SizedBox(width: 8), - const Text('Галерея'), + child: const Row( + children: const [ + Icon(Icons.photo_camera), + SizedBox(width: 8), + Text('Фото'), ], ), ), CupertinoActionSheetAction( onPressed: () async { - // final picker = ImagePicker(); - // XFile? image = await picker.pickImage(source: ImageSource.camera); + final picker = ImagePicker(); + List? images = await picker.pickMultiImage(); + if (images.isNotEmpty) + diaryCubit.uploadPhotos( + files: images.map((f) => File(f.path)).toList(), + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + date: _selectedDay.toString(), + ); + Navigator.of(context).pop(); }, - child: Row( - children: [ - const Icon(Icons.photo_camera), - const SizedBox(width: 8), - const Text('Фото'), + child: const Row( + children: const [ + Icon(Icons.photo), + SizedBox(width: 8), + Text('Галерея'), ], ), ), @@ -226,6 +252,104 @@ class _DiaryScreenState extends State { ); } + _photoGallery(List photos, DiaryDayModel diaryDay) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + alignment: Alignment.topRight, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: baseUrl + '/storage/' + photos[currentSelectedPhotoIndex].path, + ), + ), + Padding( + padding: const EdgeInsets.all(12), + child: IconButton.filled( + style: IconButton.styleFrom( + backgroundColor: Colors.white, + ), + onPressed: () { + setState(() { + if (currentSelectedPhotoIndex == photos.length - 1 && currentSelectedPhotoIndex != 0) { + currentSelectedPhotoIndex = photos.length - 2; + } + }); + diaryCubit.deletePhoto( + photo: photos[currentSelectedPhotoIndex], + diaryId: diaryDay.id, + mealId: diaryDay.meals[currentSelectedMealNumber - 1].id, + date: _selectedDay.toString(), + ); + }, + icon: Icon( + Icons.close_rounded, + color: Colors.grey, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + for (int i = 0; i < photos.length + 1; i++) ...[ + if (i == photos.length) + Padding( + padding: const EdgeInsets.only(left: 8), + child: InkWell( + onTap: () => _showImagePicker(diaryDay), + borderRadius: BorderRadius.circular(8), + child: Ink( + height: 52, + width: 52, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Color(0xFFF1F2F6), + ), + child: Icon( + Icons.add_rounded, + color: appTheme.blueA400, + ), + ), + ), + ), + if (i < photos.length) + GestureDetector( + onTap: () { + setState(() { + currentSelectedPhotoIndex = i; + }); + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: SizedBox( + height: 52, + width: 52, + child: CachedNetworkImage( + imageUrl: baseUrl + '/storage/' + photos[i].path, + fit: BoxFit.fitWidth, + ), + ), + ), + ), + if (i < photos.length - 1) const SizedBox(width: 8), + ], + ], + ), + ), + ], + ); + } + _mealButton(int mealNumber) { bool isActive = currentSelectedMealNumber == mealNumber; diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index d76219b5..b9b8243c 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/services/diary_service.dart'; @@ -66,4 +67,39 @@ class DiaryRepository { portionId: portionId, ); } + + Future uploadPhotos({ + required int diaryId, + required int mealId, + required List files, + }) async { + try { + for (int i = 0; i < files.length; i++) { + await diaryService.uploadPhotoOfMeal( + diaryId: diaryId, + mealId: mealId, + photo: files[i], + ); + } + } catch (e) { + rethrow; + } + } + + Future deletePhoto({ + required int diaryId, + required int mealId, + required DiaryPhotoModel photo, + }) async { + try { + await diaryService.deletePhotoOfMeal( + diaryId: diaryId, + mealId: mealId, + id: photo.id, + path: photo.path, + ); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/diary/services/diary_service.dart b/lib/features/diary/services/diary_service.dart index 0b16782c..16990b1b 100644 --- a/lib/features/diary/services/diary_service.dart +++ b/lib/features/diary/services/diary_service.dart @@ -1,23 +1,11 @@ +import 'dart:io'; import 'package:dio/dio.dart'; import 'package:eatvio_app/features/diary/models/diary_day_model.dart'; import 'package:eatvio_app/features/diary/models/diary_index_model.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:retrofit/retrofit.dart'; -part 'diary_service.freezed.dart'; part 'diary_service.g.dart'; -@freezed -class ProductInMeal with _$ProductInMeal { - const factory ProductInMeal({ - @JsonKey(name: 'product_name') required String productName, - @JsonKey(name: 'portion') required String portion, - @JsonKey(name: 'quantity') required int quantity, - }) = _ProductInMeal; - - factory ProductInMeal.fromJson(Map json) => _$ProductInMealFromJson(json); -} - @RestApi() abstract class DiaryService { factory DiaryService(Dio dio, {String baseUrl}) = _DiaryService; @@ -32,6 +20,22 @@ abstract class DiaryService { @Path('diary_id') required int index, }); + @POST('/api/diary/{diary_id}/meal/{meal_id}/photo/upload') + @MultiPart() + Future uploadPhotoOfMeal({ + @Path('diary_id') required int diaryId, + @Path('meal_id') required int mealId, + @Part(name: 'photo') required File photo, + }); + + @DELETE('/api/diary/{diary_id}/meal/{meal_id}/photo/delete') + Future deletePhotoOfMeal({ + @Path('diary_id') required int diaryId, + @Path('meal_id') required int mealId, + @Query('id') required int id, + @Query('path') required String path, + }); + @POST('/api/diary/{diary_id}/meal/{meal_id}/portions/create') Future addProductToMeal({ @Path('diary_id') required int diaryId, diff --git a/lib/features/diary/services/diary_service.freezed.dart b/lib/features/diary/services/diary_service.freezed.dart deleted file mode 100644 index 258040d1..00000000 --- a/lib/features/diary/services/diary_service.freezed.dart +++ /dev/null @@ -1,220 +0,0 @@ -// 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 'diary_service.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#adding-getters-and-methods-to-our-models'); - -ProductInMeal _$ProductInMealFromJson(Map json) { - return _ProductInMeal.fromJson(json); -} - -/// @nodoc -mixin _$ProductInMeal { - @JsonKey(name: 'product_name') - String get productName => throw _privateConstructorUsedError; - @JsonKey(name: 'portion') - String get portion => throw _privateConstructorUsedError; - @JsonKey(name: 'quantity') - int get quantity => throw _privateConstructorUsedError; - - /// Serializes this ProductInMeal to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ProductInMeal - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $ProductInMealCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ProductInMealCopyWith<$Res> { - factory $ProductInMealCopyWith( - ProductInMeal value, $Res Function(ProductInMeal) then) = - _$ProductInMealCopyWithImpl<$Res, ProductInMeal>; - @useResult - $Res call( - {@JsonKey(name: 'product_name') String productName, - @JsonKey(name: 'portion') String portion, - @JsonKey(name: 'quantity') int quantity}); -} - -/// @nodoc -class _$ProductInMealCopyWithImpl<$Res, $Val extends ProductInMeal> - implements $ProductInMealCopyWith<$Res> { - _$ProductInMealCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of ProductInMeal - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? productName = null, - Object? portion = null, - Object? quantity = null, - }) { - return _then(_value.copyWith( - productName: null == productName - ? _value.productName - : productName // ignore: cast_nullable_to_non_nullable - as String, - portion: null == portion - ? _value.portion - : portion // ignore: cast_nullable_to_non_nullable - as String, - quantity: null == quantity - ? _value.quantity - : quantity // ignore: cast_nullable_to_non_nullable - as int, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$ProductInMealImplCopyWith<$Res> - implements $ProductInMealCopyWith<$Res> { - factory _$$ProductInMealImplCopyWith( - _$ProductInMealImpl value, $Res Function(_$ProductInMealImpl) then) = - __$$ProductInMealImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {@JsonKey(name: 'product_name') String productName, - @JsonKey(name: 'portion') String portion, - @JsonKey(name: 'quantity') int quantity}); -} - -/// @nodoc -class __$$ProductInMealImplCopyWithImpl<$Res> - extends _$ProductInMealCopyWithImpl<$Res, _$ProductInMealImpl> - implements _$$ProductInMealImplCopyWith<$Res> { - __$$ProductInMealImplCopyWithImpl( - _$ProductInMealImpl _value, $Res Function(_$ProductInMealImpl) _then) - : super(_value, _then); - - /// Create a copy of ProductInMeal - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? productName = null, - Object? portion = null, - Object? quantity = null, - }) { - return _then(_$ProductInMealImpl( - productName: null == productName - ? _value.productName - : productName // ignore: cast_nullable_to_non_nullable - as String, - portion: null == portion - ? _value.portion - : portion // ignore: cast_nullable_to_non_nullable - as String, - quantity: null == quantity - ? _value.quantity - : quantity // ignore: cast_nullable_to_non_nullable - as int, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$ProductInMealImpl implements _ProductInMeal { - const _$ProductInMealImpl( - {@JsonKey(name: 'product_name') required this.productName, - @JsonKey(name: 'portion') required this.portion, - @JsonKey(name: 'quantity') required this.quantity}); - - factory _$ProductInMealImpl.fromJson(Map json) => - _$$ProductInMealImplFromJson(json); - - @override - @JsonKey(name: 'product_name') - final String productName; - @override - @JsonKey(name: 'portion') - final String portion; - @override - @JsonKey(name: 'quantity') - final int quantity; - - @override - String toString() { - return 'ProductInMeal(productName: $productName, portion: $portion, quantity: $quantity)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$ProductInMealImpl && - (identical(other.productName, productName) || - other.productName == productName) && - (identical(other.portion, portion) || other.portion == portion) && - (identical(other.quantity, quantity) || - other.quantity == quantity)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, productName, portion, quantity); - - /// Create a copy of ProductInMeal - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$ProductInMealImplCopyWith<_$ProductInMealImpl> get copyWith => - __$$ProductInMealImplCopyWithImpl<_$ProductInMealImpl>(this, _$identity); - - @override - Map toJson() { - return _$$ProductInMealImplToJson( - this, - ); - } -} - -abstract class _ProductInMeal implements ProductInMeal { - const factory _ProductInMeal( - {@JsonKey(name: 'product_name') required final String productName, - @JsonKey(name: 'portion') required final String portion, - @JsonKey(name: 'quantity') required final int quantity}) = - _$ProductInMealImpl; - - factory _ProductInMeal.fromJson(Map json) = - _$ProductInMealImpl.fromJson; - - @override - @JsonKey(name: 'product_name') - String get productName; - @override - @JsonKey(name: 'portion') - String get portion; - @override - @JsonKey(name: 'quantity') - int get quantity; - - /// Create a copy of ProductInMeal - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ProductInMealImplCopyWith<_$ProductInMealImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/features/diary/services/diary_service.g.dart b/lib/features/diary/services/diary_service.g.dart index 177dd20f..74de16af 100644 --- a/lib/features/diary/services/diary_service.g.dart +++ b/lib/features/diary/services/diary_service.g.dart @@ -2,24 +2,6 @@ part of 'diary_service.dart'; -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$ProductInMealImpl _$$ProductInMealImplFromJson(Map json) => - _$ProductInMealImpl( - productName: json['product_name'] as String, - portion: json['portion'] as String, - quantity: (json['quantity'] as num).toInt(), - ); - -Map _$$ProductInMealImplToJson(_$ProductInMealImpl instance) => - { - 'product_name': instance.productName, - 'portion': instance.portion, - 'quantity': instance.quantity, - }; - // ************************************************************************** // RetrofitGenerator // ************************************************************************** @@ -105,6 +87,76 @@ class _DiaryService implements DiaryService { return _value; } + @override + Future uploadPhotoOfMeal({ + required int diaryId, + required int mealId, + required File photo, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = FormData(); + _data.files.add(MapEntry( + 'photo', + MultipartFile.fromFileSync( + photo.path, + filename: photo.path.split(Platform.pathSeparator).last, + ), + )); + final _options = _setStreamType(Options( + method: 'POST', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/api/diary/${diaryId}/meal/${mealId}/photo/upload', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + + @override + Future deletePhotoOfMeal({ + required int diaryId, + required int mealId, + required int id, + required String path, + }) async { + final _extra = {}; + final queryParameters = { + r'id': id, + r'path': path, + }; + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType(Options( + method: 'DELETE', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/diary/${diaryId}/meal/${mealId}/photo/delete', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + await _dio.fetch(_options); + } + @override Future addProductToMeal({ required int diaryId, From f13921ecaa43778b03a50c42a4f0d627b77dbbb3 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 1 Nov 2024 11:21:10 +0300 Subject: [PATCH 8/9] fix: ci/cd --- .gitea/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index ed756ee9..883dee6a 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,7 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt install -y openjdk-21-jdk wget + apt-get install -y openjdk-21-jdk wget wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest From 963e4c907e8de1c7a88620f6bd10ceadc84edf26 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 1 Nov 2024 11:22:38 +0300 Subject: [PATCH 9/9] fix: ci/cd 2 --- .gitea/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 883dee6a..196d2aa1 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,8 +24,7 @@ jobs: - name: Install Android SDK run: | apt-get update - apt-get install -y openjdk-21-jdk wget - wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip + https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip -O commandlinetools.zip unzip commandlinetools.zip mkdir -p $HOME/Android/Sdk/cmdline-tools/latest mv cmdline-tools/* $HOME/Android/Sdk/cmdline-tools/latest/