feat(more_options): delete recipe, diet, plans, move to draft diets and plans

This commit is contained in:
mso7nueh
2025-05-07 12:48:52 +03:00
parent 037fc3d529
commit b2d9e808fe
14 changed files with 163 additions and 174 deletions

View File

@@ -7,7 +7,11 @@ import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_cubit.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -96,23 +100,65 @@ class CustomPopup extends StatelessWidget {
}
});
if (complainedContentType == ComplainedContent.plan || complainedContentType == ComplainedContent.diet) {
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () {
showInfoToast('В разработке');
return;
addItem('assets/icons/remove_from_public.svg', 'Снять с публикации', () async {
if (complainedContentType == ComplainedContent.diet) {
final result = await sl<MyDietsCubit>().unpublishDiet(id!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('Диета перенесена в архив, обновите ленту');
} else if (complainedContentType == ComplainedContent.plan) {
final result = await sl<MyPlansCubit>().unpublish(slug!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('План перенесен в архив, обновите ленту');
}
});
}
addItem('assets/icons/red_trash.svg', 'Удалить', () async {
if (complainedContentType == ComplainedContent.posts) {
final result = await sl<BlogPostCreateCubit>().deleteBlogPost(id!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('Пост удален, обновите ленту');
} else if (complainedContentType == ComplainedContent.recipe) {
final result = await sl<RecipesCubit>().deleteRecipe(slug!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
} else {
showInfoToast('В разработке');
return;
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('Рецепт удален, обновите ленту');
} else if (complainedContentType == ComplainedContent.diet) {
final result = await sl<DietsCubit>().deleteDiet(id!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('Диета удалена, обновите ленту');
} else if (complainedContentType == ComplainedContent.plan) {
final result = await sl<MyPlansCubit>().deletePlan(slug!);
if (!context.mounted) return;
context.read<FeedCubit>().loadFeedBlogPostList();
if (!result) {
showErrorDialog(context, title: S.current.could_not_delete_blog_post);
return;
}
showSuccessToast('План удален, обновите ленту');
}
});
} else {

View File

@@ -1,24 +1,24 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/enums/product_type.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/utils/string_extentions.dart';
import 'package:eatvio_app/core/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/info_row.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart';
import 'package:eatvio_app/core/widgets/cpfc_container.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
class DietPreviewScreen extends StatefulWidget {
@@ -190,30 +190,12 @@ class _DietPreviewScreenState extends State<DietPreviewScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
disableActions: true,
path: 'diet/${widget.diet.id}',
),
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.id}',
complainedContentType: ComplainedContent.diet,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(

View File

@@ -19,7 +19,6 @@ import 'package:eatvio_app/features/diets/presentation/widgets/cancel_diet_botto
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/start_diet_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
@@ -32,7 +31,6 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
class AboutDietScreen extends StatefulWidget {
@@ -498,31 +496,13 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
path: 'diet/${widget.diet.id}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
),
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.id}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(
@@ -542,31 +522,13 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
path: 'diet/${widget.diet.id}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
),
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
CustomPopup(
isAuthor: widget.diet.author.authorIsMe,
path: 'diet/${widget.diet.id}',
complainedContentType: ComplainedContent.diet,
id: widget.diet.id,
),
const SizedBox(width: 16),
],
),
body: const Loader(),

View File

@@ -65,6 +65,14 @@ class DietRepository {
}
}
Future<void> deleteDiet(int id) async {
try {
return await dietsService.deleteDiet(id: id);
} catch (e) {
rethrow;
}
}
void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state);
void emitFavoriteStateToFeed(FavoriteState state) => _dietFavoriteStateStreamController.add(state);
void emitCommentsStateToFeed(CommentsState state) => _dietCommentsStateStreamController.add(state);

View File

@@ -39,4 +39,9 @@ abstract class DietService {
Future<void> cancelDiet({
@Path() required int id,
});
@DELETE('/api/diets/delete/{id}')
Future<void> deleteDiet({
@Path() required int id,
});
}

View File

@@ -29,6 +29,7 @@ class DietsCubit extends Cubit<DietsState> {
late final StreamSubscription likesStateSubscription;
late final StreamSubscription favoriteStateSubscription;
late final StreamSubscription commentsStateSubscription;
DietsCubit({
required this.feedRepository,
required this.dietsRepository,
@@ -342,4 +343,13 @@ class DietsCubit extends Cubit<DietsState> {
},
);
}
Future<bool> deleteDiet(int dietId) async {
try {
await dietsRepository.deleteDiet(dietId);
return true;
} catch (e) {
return false;
}
}
}

View File

@@ -49,7 +49,7 @@ class RecipesCubit extends Cubit<RecipesState> {
tags: _selectedTags.toList(),
query: query,
);
final recipesTags = (await recipeRepository.getRecipesTags());
emit(
state.copyWith(
@@ -129,7 +129,7 @@ class RecipesCubit extends Cubit<RecipesState> {
EasyDebounce.debounce(
'add_or_remove_plan_from_favorite',
const Duration(milliseconds: 200),
() async {
() async {
try {
await aboutRecipeRepository.addToFavorite(slug);
} catch (e) {
@@ -169,7 +169,7 @@ class RecipesCubit extends Cubit<RecipesState> {
EasyDebounce.debounce(
'add_or_remove_plan_from_favorite',
const Duration(milliseconds: 200),
() async {
() async {
try {
await aboutRecipeRepository.removeFromFavorite(slug);
} catch (e) {
@@ -225,7 +225,7 @@ class RecipesCubit extends Cubit<RecipesState> {
EasyDebounce.debounce(
'like_plan',
const Duration(milliseconds: 200),
() async {
() async {
try {
await aboutRecipeRepository.likeRecipe(slug);
} catch (e) {
@@ -300,4 +300,13 @@ class RecipesCubit extends Cubit<RecipesState> {
),
);
}
Future<bool> deleteRecipe(String slug) async {
try {
await aboutRecipeRepository.deleteRecipe(slug);
return true;
} catch (e) {
return false;
}
}
}

View File

@@ -204,4 +204,13 @@ class MyPlansCubit extends Cubit<MyPlansState> {
return false;
}
}
Future<bool> unpublish(String slug) async {
try {
await myPlansRepository.unpublish(slug);
return true;
} catch (e) {
return false;
}
}
}

View File

@@ -35,5 +35,8 @@ class MyPlansRepository {
rethrow;
}
}
Future<void> unpublish(String slug) async {
return await myPlansService.unpublish(slug: slug);
}
}

View File

@@ -6,8 +6,7 @@ part 'my_plans_service.g.dart';
@RestApi()
abstract class MyPlansService {
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
factory MyPlansService(Dio dio, {String baseUrl}) = _MyPlansService;
@GET('/api/plans/index')
Future<PlanPaginationModel> getPlans({
@@ -24,5 +23,8 @@ abstract class MyPlansService {
@Path() required String slug,
});
}
@POST('/api/plans/draft/{slug}')
Future<void> unpublish({
@Path() required String slug,
});
}

View File

@@ -15,7 +15,6 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
@@ -30,7 +29,6 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
@@ -143,31 +141,13 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
complainedContentType: ComplainedContent.plan,
path: 'plan/${widget.plan.id}',
slug: widget.plan.slug,
),
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
CustomPopup(
isAuthor: widget.plan.author.authorIsMe,
path: 'plan/${widget.plan.id}',
complainedContentType: ComplainedContent.plan,
slug: widget.plan.slug,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutPlanCubit>().getPlan(widget.plan.slug)),
@@ -380,31 +360,13 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
complainedContentType: ComplainedContent.plan,
path: 'plan/${widget.plan.id}',
slug: widget.plan.slug,
),
anchor: const Aligned(
shiftToWithinBound: AxisFlag(x: true, y: true),
follower: Alignment.topRight,
target: Alignment.bottomRight,
),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset(
'assets/icons/more_button.svg',
width: 20,
height: 20,
),
),
CustomPopup(
isAuthor: widget.plan.author.authorIsMe,
path: 'plan/${widget.plan.id}',
complainedContentType: ComplainedContent.plan,
slug: widget.plan.slug,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(

View File

@@ -16,7 +16,6 @@ import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'
import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
@@ -32,7 +31,6 @@ import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/svg.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -398,23 +396,13 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
complainedContentType: ComplainedContent.recipe,
path: 'recipe/${widget.recipe.slug}',
slug: widget.recipe.slug,
),
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20),
),
CustomPopup(
isAuthor: widget.recipe.author.authorIsMe,
path: 'recipe/${widget.recipe.slug}',
complainedContentType: ComplainedContent.recipe,
slug: widget.recipe.slug,
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutRecipeCubit>().getRecipe(widget.recipe.slug, index: widget.index)),
@@ -432,23 +420,13 @@ class _AboutRecipeScreenState extends State<AboutRecipeScreen> {
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
PortalTarget(
visible: isMoreOptionsVisible,
portalFollower: MoreOptions(
complainedContentType: ComplainedContent.recipe,
path: 'recipe/${widget.recipe.slug}',
slug: widget.recipe.slug,
),
anchor: const Aligned(shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight),
child: IconButton(
onPressed: () {
setState(() {
isMoreOptionsVisible = !isMoreOptionsVisible;
});
},
icon: SvgPicture.asset('assets/icons/more_button.svg', width: 20, height: 20),
),
CustomPopup(
isAuthor: widget.recipe.author.authorIsMe,
path: 'recipe/${widget.recipe.slug}',
complainedContentType: ComplainedContent.recipe,
slug: widget.recipe.slug,
),
const SizedBox(width: 16),
],
),
body: const Loader(),

View File

@@ -47,6 +47,14 @@ class AboutRecipeRepository {
}
}
Future<void> deleteRecipe(String slug) async {
try {
await recipeService.deleteRecipe(slug: slug);
} catch (e) {
rethrow;
}
}
void emitLikesStateToFeed(LikesState state) => _recipeLikeStateStreamController.add(state);
void emitFavoriteStateToFeed(FavoriteState state) => _recipeFavoriteStateStreamController.add(state);
void emitCommentsStateToFeed(CommentsState state) => _recipeCommentsStateStreamController.add(state);

View File

@@ -25,4 +25,9 @@ abstract class AboutRecipeService {
@GET('/api/recipes/show/{id}')
Future<Recipe> getRecipe(@Path() String id);
@DELETE('/api/recipes/delete/{slug}')
Future<void> deleteRecipe({
@Path() required String slug,
});
}