Files
eatvio_app/lib/features/recipes/presentation/pages/recipe_create_screen.dart

430 lines
17 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/features/recipes/models/ingredient.dart';
import 'package:eatvio_app/features/recipes/models/recipe_model.dart';
import 'package:eatvio_app/features/recipes/models/image.dart' as image;
import 'package:eatvio_app/features/recipes/models/step.dart' as step;
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_cubit.dart';
import 'package:eatvio_app/features/recipes/presentation/bloc/recipe_create_state.dart';
import 'package:eatvio_app/widgets/custom_drop_down.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
// ignore: must_be_immutable
class RecipeCreateScreen extends StatelessWidget {
RecipeCreateScreen({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return BlocBuilder<RecipeCreateCubit, RecipeCreateState>(
builder: (context, state) {
if (state is RecipeCreateStateLoaded) {
return _buildRecipeCreate(context, state);
} else if (state is RecipeCreateStateLoading) {
return Scaffold(
body: Center(
child: CupertinoActivityIndicator(),
),
);
} else if (state is RecipeCreateStateError) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text(state.errorMessage),
),
),
);
} else
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Text("Неопределенная ошибка"),
),
),
);
},
);
}
Widget _buildRecipeCreate(
BuildContext context, RecipeCreateStateLoaded state) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
automaticallyImplyLeading: false,
),
backgroundColor: Color.fromARGB(255, 240, 242, 245),
resizeToAvoidBottomInset: true,
body: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Заголовок'),
SizedBox(height: 8),
CustomTextFormField(
fillColor: appTheme.gray50,
controller: state.titleController,
hintText: 'Заголовок',
),
SizedBox(height: 16),
Text('Тип блюда'),
SizedBox(height: 8),
CustomDropDown(
value: state.types[0],
items: state.types,
),
SizedBox(height: 16),
Text('Изображение'),
GestureDetector(
onTap: () async {
final pickedFile = await state.picker
.pickImage(source: ImageSource.gallery);
context
.read<RecipeCreateCubit>()
.uploadRecipeImage(pickedFile);
},
child: state.recipeImage == null
? Container(
height: 128,
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
),
SizedBox(width: 8),
Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
],
),
)
: Image.file(File(state.recipeImage!.path),
height: 200, fit: BoxFit.cover),
),
SizedBox(height: 16),
Text('Время приготовления'),
SizedBox(height: 8),
CustomDropDown(
items: state.cookingTimes,
value: state.cookingTimes[0],
),
SizedBox(height: 16),
Text('Статус'),
SizedBox(height: 8),
CustomDropDown(
items: state.statuses,
value: state.statuses[0],
),
SizedBox(height: 24),
if (!state.ingredients.isEmpty)
for (int j = 0; j < state.ingredients.length; j++)
_buildIngredient(state.ingredients[j]),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Введите название продукта',
controller: state.productNameController,
),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Порция',
fillColor: appTheme.gray50,
controller: state.ingredientController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
),
SizedBox(height: 8),
Row(children: [
Text('гр'),
Switch(
value: state.ingredientAsItems,
onChanged: (bool value) => context
.read<RecipeCreateCubit>()
.switchUnits(value),
activeColor: appTheme.blueA400,
inactiveThumbColor: Colors.grey,
inactiveTrackColor: Colors.grey.shade300,
),
Text('шт'),
]),
SizedBox(height: 8),
Container(
height: 48,
width: MediaQuery.of(context).size.width,
child: FloatingActionButton.extended(
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
backgroundColor: appTheme.blueA400,
label: Text(
'+',
style: theme.textTheme.titleSmall,
),
onPressed: () => context
.read<RecipeCreateCubit>()
.addIngredient(new Ingredient(
product_name:
state.productNameController.text,
portion: int.parse(
state.ingredientController.text),
unit: state.ingredientAsItems
? 'pcs'
: 'gr'))),
),
SizedBox(height: 24),
Text('Описание рецепта'),
SizedBox(height: 8),
CustomTextFormField(
maxLines: 8,
minLines: 8,
controller: state.stepDescriptionController,
),
SizedBox(height: 24),
for (int i = 0; i < state.numberOfSteps; i++)
_buildStep(context, i, state),
SizedBox(height: 24),
Container(
height: 48,
width: MediaQuery.of(context).size.width,
child: FloatingActionButton.extended(
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
backgroundColor: appTheme.blueA400,
onPressed: () =>
context.read<RecipeCreateCubit>().addStep(),
label: Text(
'+',
style: theme.textTheme.titleSmall,
),
),
),
SizedBox(height: 8),
Container(
height: 48,
width: MediaQuery.of(context).size.width,
child: FloatingActionButton.extended(
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
backgroundColor: Colors.grey,
onPressed: () =>
context.read<RecipeCreateCubit>().removeStep(),
label: Text(
'-',
style: theme.textTheme.titleSmall,
),
),
),
SizedBox(height: 24),
Text('Ссылка на видео (YouTube)'),
SizedBox(height: 8),
CustomTextFormField(
hintText: 'Введите описание шага',
fillColor: appTheme.gray50,
controller: state.youtubeLinkController,
),
SizedBox(height: 24),
Container(
height: 48,
width: MediaQuery.of(context).size.width,
child: FloatingActionButton.extended(
highlightElevation: 0,
elevation: 0,
focusElevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
backgroundColor: appTheme.blueA400,
onPressed: () {},
label: Text(
'Опубликовать',
style: theme.textTheme.titleSmall,
),
),
),
],
)),
],
),
),
),
);
}
_buildStep(
BuildContext context, int stepNumber, RecipeCreateStateLoaded state) {
return Column(
children: [
Row(children: [
Text('Шаг ${stepNumber + 1}'),
Spacer(),
Text('Всего шагов: ${state.numberOfSteps}')
]),
SizedBox(height: 8),
GestureDetector(
onTap: () async {
final pickedFile =
await state.picker.pickImage(source: ImageSource.gallery);
context
.read<RecipeCreateCubit>()
.uploadStepImage(pickedFile, stepNumber);
},
child: state.stepImages[stepNumber] == null
? Container(
height: 128,
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.photo_library,
color: appTheme.blueGray300,
),
SizedBox(width: 8),
Text(
"Добавить фото",
selectionColor: appTheme.blueA400,
),
SizedBox(height: 8),
],
),
)
: Image.file(File(state.stepImages[stepNumber]!.path),
height: 150, fit: BoxFit.cover)),
SizedBox(height: 16),
CustomTextFormField(
hintText: 'Введите описание шага',
maxLines: 8,
minLines: 8,
fillColor: appTheme.gray50,
controller: state.stepControllers[stepNumber],
),
SizedBox(height: 24),
],
);
}
_buildIngredient(Ingredient ingredient) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 10.h),
padding: EdgeInsets.symmetric(
horizontal: 8.h,
vertical: 6.v,
),
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadiusStyle.roundedBorder5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.only(
left: 7.h,
top: 5.v,
),
child: Text(
ingredient.product_name.length > 8
? ingredient.product_name.substring(0, 8)
: ingredient.product_name,
style: CustomTextStyles.titleMedium16,
),
),
Padding(
padding: EdgeInsets.only(
left: 20.h,
top: 3.v,
bottom: 3.v,
),
child: Text(
ingredient.portion.toString() +
' ' +
ingredient.unit.toString().tr,
),
),
Spacer(),
GestureDetector(
// onTap: removeIngredient(context, ingredient),
child: Padding(
padding: const EdgeInsets.only(right: 10),
child: Icon(
Icons.close,
color: appTheme.blueGray300,
),
),
),
],
),
);
}
RecipeModel createRecipeFromState(RecipeCreateStateLoaded state) {
// Подготовка ингредиентов
List<Ingredient> ingredients = state.ingredients;
// Подготовка шагов (например, можно создать шаги из stepControllers)
List<step.Step> steps = [];
for (int i = 0; i < state.stepControllers.length; i++) {
if (state.stepControllers[i].text.isNotEmpty) {
steps.add(step.Step(description: state.stepControllers[i].text));
}
}
// Подготовка изображения
image.Image pic = image.Image(path: state.recipeImage?.path ?? '');
// Создание экземпляра RecipeModel
return RecipeModel(
id: null,
ingredients: ingredients,
steps: steps,
state: 'draft',
description: state.titleController.text,
type: state.types.first.value,
image: pic,
video_url: state.youtubeLinkController.text.isNotEmpty
? state.youtubeLinkController.text
: null,
cooking_time:
state.cookingTimes.isNotEmpty ? state.cookingTimes.first.value : null,
);
}
}