forked from eatvio/eatvio_app
feat: implement recipe drafting functionality and refactor MyRecipesTab to be stateless
This commit is contained in:
@@ -172,6 +172,23 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> saveAsDraft([bool update = false, String? slug]) async {
|
||||
try {
|
||||
final model = state.entity.toModel();
|
||||
await recipeRepository.saveAsDraft(model, update, slug);
|
||||
emit(state.copyWith(drafted: true));
|
||||
return true;
|
||||
} catch (e, st) {
|
||||
await Sentry.captureException(e, stackTrace: st);
|
||||
log(e.toString(), stackTrace: st);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool validateForDraft() {
|
||||
return validateAll();
|
||||
}
|
||||
|
||||
Future<File> downloadImageAsFile(String imageUrl) async {
|
||||
final response = await http.get(Uri.parse(imageUrl));
|
||||
|
||||
@@ -239,6 +256,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
final isActiveCookTimeValid = validateActiveCookTime();
|
||||
final isEnergyPerServingsValid = validateEnergyPerServing();
|
||||
|
||||
final isIngredientsValid = validateIngredients();
|
||||
final isStepsValid = validateSteps();
|
||||
|
||||
return isTitleValid &&
|
||||
@@ -248,8 +266,19 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
isCookTimeValid &&
|
||||
isActiveCookTimeValid &&
|
||||
isEnergyPerServingsValid &&
|
||||
isIngredientsValid &&
|
||||
isStepsValid;
|
||||
}
|
||||
|
||||
bool validateIngredients() {
|
||||
if (state.entity.ingredients.isEmpty) {
|
||||
emit(state.copyWith(ingredientsValidationError: 'Добавьте хотя бы один ингредиент'));
|
||||
return false;
|
||||
} else {
|
||||
emit(state.copyWith(ingredientsValidationError: null));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool validateTitle() {
|
||||
if (state.entity.title == null || (state.entity.title?.isEmpty ?? true)) {
|
||||
@@ -343,6 +372,10 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
|
||||
|
||||
bool validateSteps() {
|
||||
bool isValid = true;
|
||||
if (state.entity.steps.isEmpty) {
|
||||
emit(state.copyWith(stepsValidationError: 'Добавьте хотя бы один шаг'));
|
||||
return false;
|
||||
}
|
||||
for (var step in state.entity.steps) {
|
||||
if (step.description == null || step.description!.trim().isEmpty) {
|
||||
isValid = false;
|
||||
|
||||
@@ -8,6 +8,7 @@ abstract class CreateRecipeState with _$CreateRecipeState {
|
||||
const factory CreateRecipeState({
|
||||
required RecipeCreateEntity entity,
|
||||
required bool loading,
|
||||
@Default(false) bool drafted,
|
||||
String? titleValidationError,
|
||||
String? descriptionValidationError,
|
||||
String? cookingTimeValidationError,
|
||||
@@ -16,6 +17,7 @@ abstract class CreateRecipeState with _$CreateRecipeState {
|
||||
String? energyPerServingValidationError,
|
||||
String? imageValidationError,
|
||||
String? stepsValidationError,
|
||||
String? ingredientsValidationError,
|
||||
}) = _CreateRecipeState;
|
||||
|
||||
}
|
||||
|
||||
@@ -81,55 +81,107 @@ class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
|
||||
builder: (context, state) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return _isRecipeBeingPublished
|
||||
? const Center(child: Loader())
|
||||
: InkWell(
|
||||
key: AppKeys.recipeCreateScreenPublishButton,
|
||||
if (_isRecipeBeingPublished) {
|
||||
return const Center(child: Loader());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
InkWell(
|
||||
key: AppKeys.recipeCreateScreenPublishButton,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = true;
|
||||
});
|
||||
if (context.read<CreateRecipeCubit>().validateAll()) {
|
||||
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe(
|
||||
widget.recipe == null ? false : true,
|
||||
widget.recipe?.slug,
|
||||
);
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = false;
|
||||
});
|
||||
if (isSuccess) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
widget.recipe != null ? 'Обновить' : 'Опубликовать',
|
||||
style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (widget.recipe == null || widget.recipe?.state == 'drafted')
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = true;
|
||||
});
|
||||
if (context.read<CreateRecipeCubit>().validateAll()) {
|
||||
final isSuccess = await context.read<CreateRecipeCubit>().publishRecipe(
|
||||
widget.recipe == null ? false : true,
|
||||
widget.recipe?.slug,
|
||||
);
|
||||
if (context.read<CreateRecipeCubit>().validateForDraft()) {
|
||||
final isSuccess = await context.read<CreateRecipeCubit>().saveAsDraft(
|
||||
widget.recipe == null ? false : true,
|
||||
widget.recipe?.slug,
|
||||
);
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = false;
|
||||
});
|
||||
if (isSuccess) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
showBottomSheetWithBlur(
|
||||
userRootNavigator: true,
|
||||
context: context,
|
||||
child: SuccessPublishedRecipeBottomSheet(title: state.entity.title ?? 'Рецепт'),
|
||||
);
|
||||
});
|
||||
if (!context.mounted) return;
|
||||
context.pop();
|
||||
} else {
|
||||
if (!context.mounted) return;
|
||||
showErrorDialog(context, title: '${s.could_not_publish_recipe} ${state.entity.title}');
|
||||
showErrorDialog(context, title: 'Не удалось сохранить черновик');
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = false;
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
_isRecipeBeingPublished = false;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Ink(
|
||||
width: MediaQuery.of(context).size.width - 32,
|
||||
decoration: BoxDecoration(color: appTheme.blueA400, borderRadius: BorderRadius.circular(20)),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: appTheme.blueA400),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
widget.recipe != null ? 'Обновить' : 'Опубликовать',
|
||||
style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w600),
|
||||
'Сохранить как черновик',
|
||||
style: TextStyle(fontSize: 16, color: appTheme.blueA400, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -85,6 +85,14 @@ class _IngredientsWidgetState extends State<IngredientsWidget> {
|
||||
title: Text('Добавить ингредиент', style: TextStyle(color: appTheme.blueA400)),
|
||||
leading: Icon(Icons.add_rounded, color: appTheme.blueA400),
|
||||
),
|
||||
if (state.ingredientsValidationError != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 4),
|
||||
child: Text(
|
||||
state.ingredientsValidationError!,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user