From 9a5bdf3e41d137dff8f1235df785d69c93b52993 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 13 Aug 2024 16:15:17 +0300 Subject: [PATCH 1/2] feat(recipes): Add steps and photos for recipe create --- .../pages/recipe_create_screen.dart | 445 ++++++++++++------ 1 file changed, 305 insertions(+), 140 deletions(-) diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index cb35e80c..e014bf83 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -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 createState() => _RecipeCreateScreenState(); +} + +class _RecipeCreateScreenState extends State { + final TextEditingController titleController = TextEditingController(); + + final TextEditingController productNameController = TextEditingController(); + + final TextEditingController portionController = TextEditingController(); + + final TextEditingController stepDescriptionController = + TextEditingController(); + + final TextEditingController youtubeLinkController = TextEditingController(); + + final List types = [ + SelectionPopupModel(title: 'Первые блюда'), + SelectionPopupModel(title: 'Вторые блюда'), + SelectionPopupModel(title: 'Cалаты'), + SelectionPopupModel(title: 'Перекусы/закуски'), + SelectionPopupModel(title: 'Сладости'), + SelectionPopupModel(title: 'Напитки'), + SelectionPopupModel(title: 'Прочее'), + ]; + + final List cookingTimes = [ + SelectionPopupModel(title: '10 минут'), + SelectionPopupModel(title: '20 минут'), + SelectionPopupModel(title: '30 минут'), + SelectionPopupModel(title: '1 час'), + SelectionPopupModel(title: '2 часа'), + SelectionPopupModel(title: '10 дней'), + ]; + + final List statuses = [ + SelectionPopupModel(title: 'Активна'), + SelectionPopupModel(title: 'Неактивна'), + ]; + + int numberOfSteps = 1; + XFile? recipeImage; // Переменная для общей фотографии рецепта + List 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) { From c53fa419d7a143af6c7d34957e73c5ebe54e3e5e Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 13 Aug 2024 16:17:22 +0300 Subject: [PATCH 2/2] feat(recipes): Add steps and photos for recipe create --- .../pages/recipe_create_screen.dart | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/lib/features/recipes/presentation/pages/recipe_create_screen.dart b/lib/features/recipes/presentation/pages/recipe_create_screen.dart index e014bf83..e087c34e 100644 --- a/lib/features/recipes/presentation/pages/recipe_create_screen.dart +++ b/lib/features/recipes/presentation/pages/recipe_create_screen.dart @@ -374,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); -}