Merge pull request 'recipe_crud' (#107) from recipe_crud into main

Reviewed-on: maxxkim/eatvio_app#107
This commit is contained in:
2024-08-13 13:37:02 +00:00

View File

@@ -1,5 +1,7 @@
//import 'dart:io';
import 'dart:io';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/widgets/custom_drop_down.dart';
import 'package:eatvio_app/widgets/custom_text_form_field.dart';
@@ -7,12 +9,63 @@ import 'package:image_picker/image_picker.dart';
import 'package:flutter/material.dart';
import '../../../../presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart';
class RecipeCreateScreen extends StatelessWidget {
const RecipeCreateScreen({
class RecipeCreateScreen extends StatefulWidget {
RecipeCreateScreen({
super.key,
});
@override
State<RecipeCreateScreen> createState() => _RecipeCreateScreenState();
}
class _RecipeCreateScreenState extends State<RecipeCreateScreen> {
final TextEditingController titleController = TextEditingController();
final TextEditingController productNameController = TextEditingController();
final TextEditingController portionController = TextEditingController();
final TextEditingController stepDescriptionController =
TextEditingController();
final TextEditingController youtubeLinkController = TextEditingController();
final List<SelectionPopupModel> types = [
SelectionPopupModel(title: 'Первые блюда'),
SelectionPopupModel(title: 'Вторые блюда'),
SelectionPopupModel(title: 'Cалаты'),
SelectionPopupModel(title: 'Перекусы/закуски'),
SelectionPopupModel(title: 'Сладости'),
SelectionPopupModel(title: 'Напитки'),
SelectionPopupModel(title: 'Прочее'),
];
final List<SelectionPopupModel> cookingTimes = [
SelectionPopupModel(title: '10 минут'),
SelectionPopupModel(title: '20 минут'),
SelectionPopupModel(title: '30 минут'),
SelectionPopupModel(title: '1 час'),
SelectionPopupModel(title: '2 часа'),
SelectionPopupModel(title: '10 дней'),
];
final List<SelectionPopupModel> statuses = [
SelectionPopupModel(title: 'Активна'),
SelectionPopupModel(title: 'Неактивна'),
];
int numberOfSteps = 1;
XFile? recipeImage; // Переменная для общей фотографии рецепта
List<XFile?> stepImages = []; // Список фотографий для каждого шага
final ImagePicker _picker = ImagePicker();
bool portionAsItems = false;
@override
void initState() {
super.initState();
stepImages.add(null); // Добавляем начальный элемент для первого шага
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
@@ -23,43 +76,49 @@ class RecipeCreateScreen extends StatelessWidget {
resizeToAvoidBottomInset: true,
body: SizedBox(
width: double.maxFinite,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(bottom: 5),
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: [
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(
validator: _validateTitle,
fillColor: appTheme.gray50,
hintText: 'Заголовок',
),
SizedBox(height: 16),
Text('Тип блюда'),
SizedBox(height: 8),
CustomDropDown(
validator: _validateDescription,
),
SizedBox(height: 16),
Text('Изображение'),
Container(
Text('Заголовок'),
SizedBox(height: 8),
CustomTextFormField(
validator: _validateTitle,
fillColor: appTheme.gray50,
controller: titleController,
hintText: 'Заголовок',
),
SizedBox(height: 16),
Text('Тип блюда'),
SizedBox(height: 8),
CustomDropDown(
value: types[0],
items: types,
),
SizedBox(height: 16),
Text('Изображение'),
GestureDetector(
onTap: () async {
final pickedFile = await _picker.pickImage(
source: ImageSource.gallery);
setState(() {
recipeImage =
pickedFile; // Сохраняем общее изображение рецепта
});
},
child: recipeImage == null
? Container(
height: 128,
decoration: AppDecoration.fillGray.copyWith(
borderRadius: BorderRadius.circular(16),
@@ -79,117 +138,223 @@ class RecipeCreateScreen extends StatelessWidget {
),
],
),
),
SizedBox(height: 16),
Text('Время приготовления'),
SizedBox(height: 8),
CustomDropDown(
validator: _validateYouTubeLink,
),
SizedBox(height: 16),
Text('Статус'),
SizedBox(height: 8),
CustomDropDown(
validator: _validateYouTubeLink,
),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Введите название продукта',
validator: _validateYouTubeLink,
),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Порция',
fillColor: appTheme.gray50,
validator: _validateYouTubeLink,
),
SizedBox(height: 8),
CustomDropDown(
validator: _validateYouTubeLink,
),
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,
onPressed: () {},
label: Text(
'+',
style: theme.textTheme.titleSmall,
),
),
),
SizedBox(height: 24),
Text('Описание рецепта'),
SizedBox(height: 8),
CustomTextFormField(
maxLines: 8,
minLines: 8,
validator: _validateYouTubeLink,
),
SizedBox(height: 80),
],
)),
)
: Image.file(File(recipeImage!.path),
height: 200, fit: BoxFit.cover),
),
SizedBox(height: 16),
Text('Время приготовления'),
SizedBox(height: 8),
CustomDropDown(
items: cookingTimes,
value: cookingTimes[0],
),
SizedBox(height: 16),
Text('Статус'),
SizedBox(height: 8),
CustomDropDown(
items: statuses,
value: statuses[0],
),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Введите название продукта',
controller: productNameController,
),
SizedBox(height: 24),
CustomTextFormField(
hintText: 'Порция',
fillColor: appTheme.gray50,
controller: portionController,
),
SizedBox(height: 8),
Row(children: [
Text('гр'),
Switch(
value: portionAsItems,
onChanged: (bool value) {
setState(() {
portionAsItems = 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,
onPressed: () {},
label: Text(
'+',
style: theme.textTheme.titleSmall,
),
),
),
SizedBox(height: 24),
Text('Описание рецепта'),
SizedBox(height: 8),
CustomTextFormField(
maxLines: 8,
minLines: 8,
controller: stepDescriptionController,
),
SizedBox(height: 24),
for (int i = 0; i < numberOfSteps; i++) _buildStep(i),
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: () {
setState(() {
stepImages.add(null);
numberOfSteps++;
});
},
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: () {
setState(() {
if (numberOfSteps > 1) {
numberOfSteps--;
stepImages.removeLast();
}
});
},
label: Text(
'-',
style: theme.textTheme.titleSmall,
),
),
),
SizedBox(height: 24),
Text('Ссылка на видео (YouTube)'),
SizedBox(height: 8),
CustomTextFormField(
hintText: 'Введите описание шага',
fillColor: appTheme.gray50,
controller: 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,
),
),
),
],
),
),
),
),
],
)),
],
),
),
),
);
}
}
/*_buildAddPhoto(BuildContext context, BlogPostCreateLoadedState state) {
bool isLoading = state.isLoading;
return GestureDetector(
onTap: addImage(context, isLoading),
child: Stack(
_buildStep(int stepNumber) {
return Column(
children: [
if (state.image == 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,
),
],
),
),
if (state.image != null)
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image(
image: FileImage(
File(state.image!.path),
),
),
)
Row(children: [
Text('Шаг ${stepNumber + 1}'),
Spacer(),
Text('Всего шагов: $numberOfSteps')
]),
SizedBox(height: 8),
GestureDetector(
onTap: () async {
final pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
setState(() {
stepImages[stepNumber] =
pickedFile; // Сохраняем изображение для текущего шага
});
},
child: 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(stepImages[stepNumber]!.path),
height: 150, fit: BoxFit.cover)),
SizedBox(height: 16),
CustomTextFormField(
hintText: 'Введите описание шага',
maxLines: 8,
minLines: 8,
fillColor: appTheme.gray50,
),
SizedBox(height: 24),
],
),
);
}*/
);
}
}
addImage(BuildContext context, bool isLoading) {
if (!isLoading) {
@@ -209,25 +374,3 @@ String? _validateTitle(value) {
}
return null;
}
String? _validateDescription(value) {
if (value!.isEmpty) {
return 'Пожалуйста, введите описание';
}
return null;
}
String? _validateYouTubeLink(value) {
if (value!.isEmpty) {
return 'Пожалуйста, введите ссылку';
} else if (!_isValidUrl(value)) {
return 'Пожалуйста, введите корректную ссылку';
}
return null;
}
bool _isValidUrl(String url) {
final RegExp urlRegExp =
new RegExp(r"^(http|https):\/\/([\w-]+\.)+[\w-]+(\/[\w-./?%&=]*)?$");
return urlRegExp.hasMatch(url);
}