From b80eb6b33ace1789f04fb729a2342c8bd2ae2cd0 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 27 May 2024 16:57:26 +0300 Subject: [PATCH 1/2] feat(app): Fade transition applied --- lib/routes/app_routes.dart | 142 ++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 41 deletions(-) diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 6e29d6b0..16235b16 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -130,46 +130,106 @@ class AppRoutes { static const String glossaryScreen = '/glossary'; static Map get routes => { - dateOfBirthScreen: DateOfBirthScreen.builder, - cityOfLivingScreen: CityOfLivingScreen.builder, - bodyCircumferencesScreen: BodyCircumferencesScreen.builder, - weightScreen: WeightScreen.builder, - heightScreen: HeightScreen.builder, - nameScreen: NameScreen.builder, - credentialsScreen: CredentialsScreen.builder, - allergiesScreen: AllergiesScreen.builder, - medicineIntakeScreen: MedicineIntakeScreen.builder, - splashScreen: SplashScreen.builder, - authScreen: AuthScreen.builder, - genderScreen: GenderScreen.builder, - bloodPressureScreen: BloodPressureScreen.builder, - favoriteSportsScreen: FavoriteSportsScreen.builder, - favoriteFoodsScreen: FavoriteFoodsScreen.builder, - additionalInformationScreen: AdditionalInformationScreen.builder, - languageSelectScreen: LanguageSelectScreen.builder, - appNavigationScreen: AppNavigationScreen.builder, - initialRoute: SplashScreen.builder, - myProfile: MyProfileScreen.builder, - mySettings: MySettingsScreen.builder, - diaryEdit: DiaryEditScreen.builder, - chatScreen: ChatScreen.builder, - chatSelectScreen: ChatSelectScreen.builder, - recipeSelectScreen: RecipeSelectScreen.builder, - recipeScreen: RecipeScreen.builder, - recipeEditScreen: RecipeEditScreen.builder, - recipeCreateScreen: RecipeCreateScreen.builder, - dietSelectScreen: DietSelectScreen.builder, - dietScreen: DietScreen.builder, - dietEditScreen: DietEditScreen.builder, - dietCreateScreen: DietCreateScreen.builder, - planSelectScreen: PlanSelectScreen.builder, - planScreen: PlanScreen.builder, - planEditScreen: PlanEditScreen.builder, - planCreateScreen: PlanCreateScreen.builder, - feedScreen: FeedScreen.builder, - blogPostScreen: BlogPostScreen.builder, - blogPostEditScreen: BlogPostEditScreen.builder, - blogPostCreateScreen: BlogPostCreateScreen.builder, - glossaryScreen: GlossaryScreen.builder, + dateOfBirthScreen: (context) => + _buildFadeRoute(context, DateOfBirthScreen.builder), + cityOfLivingScreen: (context) => + _buildFadeRoute(context, CityOfLivingScreen.builder), + bodyCircumferencesScreen: (context) => + _buildFadeRoute(context, BodyCircumferencesScreen.builder), + weightScreen: (context) => + _buildFadeRoute(context, WeightScreen.builder), + heightScreen: (context) => + _buildFadeRoute(context, HeightScreen.builder), + nameScreen: (context) => _buildFadeRoute(context, NameScreen.builder), + credentialsScreen: (context) => + _buildFadeRoute(context, CredentialsScreen.builder), + allergiesScreen: (context) => + _buildFadeRoute(context, AllergiesScreen.builder), + medicineIntakeScreen: (context) => + _buildFadeRoute(context, MedicineIntakeScreen.builder), + splashScreen: (context) => + _buildFadeRoute(context, SplashScreen.builder), + authScreen: (context) => _buildFadeRoute(context, AuthScreen.builder), + genderScreen: (context) => + _buildFadeRoute(context, GenderScreen.builder), + bloodPressureScreen: (context) => + _buildFadeRoute(context, BloodPressureScreen.builder), + favoriteSportsScreen: (context) => + _buildFadeRoute(context, FavoriteSportsScreen.builder), + favoriteFoodsScreen: (context) => + _buildFadeRoute(context, FavoriteFoodsScreen.builder), + additionalInformationScreen: (context) => + _buildFadeRoute(context, AdditionalInformationScreen.builder), + languageSelectScreen: (context) => + _buildFadeRoute(context, LanguageSelectScreen.builder), + appNavigationScreen: (context) => + _buildFadeRoute(context, AppNavigationScreen.builder), + initialRoute: (context) => + _buildFadeRoute(context, SplashScreen.builder), + myProfile: (context) => + _buildFadeRoute(context, MyProfileScreen.builder), + mySettings: (context) => + _buildFadeRoute(context, MySettingsScreen.builder), + diaryEdit: (context) => + _buildFadeRoute(context, DiaryEditScreen.builder), + chatScreen: (context) => _buildFadeRoute(context, ChatScreen.builder), + chatSelectScreen: (context) => + _buildFadeRoute(context, ChatSelectScreen.builder), + recipeSelectScreen: (context) => + _buildFadeRoute(context, RecipeSelectScreen.builder), + recipeScreen: (context) => + _buildFadeRoute(context, RecipeScreen.builder), + recipeEditScreen: (context) => + _buildFadeRoute(context, RecipeEditScreen.builder), + recipeCreateScreen: (context) => + _buildFadeRoute(context, RecipeCreateScreen.builder), + dietSelectScreen: (context) => + _buildFadeRoute(context, DietSelectScreen.builder), + dietScreen: (context) => _buildFadeRoute(context, DietScreen.builder), + dietEditScreen: (context) => + _buildFadeRoute(context, DietEditScreen.builder), + dietCreateScreen: (context) => + _buildFadeRoute(context, DietCreateScreen.builder), + planSelectScreen: (context) => + _buildFadeRoute(context, PlanSelectScreen.builder), + planScreen: (context) => _buildFadeRoute(context, PlanScreen.builder), + planEditScreen: (context) => + _buildFadeRoute(context, PlanEditScreen.builder), + planCreateScreen: (context) => + _buildFadeRoute(context, PlanCreateScreen.builder), + feedScreen: (context) => _buildFadeRoute(context, FeedScreen.builder), + blogPostScreen: (context) => + _buildFadeRoute(context, BlogPostScreen.builder), + blogPostEditScreen: (context) => + _buildFadeRoute(context, BlogPostEditScreen.builder), + blogPostCreateScreen: (context) => + _buildFadeRoute(context, BlogPostCreateScreen.builder), + glossaryScreen: (context) => + _buildFadeRoute(context, GlossaryScreen.builder), }; + + static Widget _buildFadeRoute(BuildContext context, WidgetBuilder builder) { + return FadeTransition( + opacity: AlwaysStoppedAnimation(1.0), + child: builder(context), + ); + } +} + +class FadeTransitionPage extends PageRouteBuilder { + final WidgetBuilder builder; + + FadeTransitionPage({required this.builder}) + : super( + pageBuilder: (BuildContext context, Animation animation, + Animation secondaryAnimation) { + return builder(context); + }, + transitionsBuilder: (BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return FadeTransition(opacity: animation, child: child); + }, + ); } From 1d1e25b1dde85a7efd57ab90913cc7bf99205e68 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 28 May 2024 14:23:49 +0300 Subject: [PATCH 2/2] feat(recipe): Recipe create layouts --- .../bloc/recipe_create_bloc.dart | 10 +- .../bloc/recipe_create_state.dart | 12 +- .../recipe_create_screen.dart | 366 +++++++++++++++--- 3 files changed, 338 insertions(+), 50 deletions(-) diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart index d438c245..f637dbfe 100644 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart @@ -35,12 +35,18 @@ class RecipeCreateBloc extends Bloc { recipeModelObj: currentState.recipeModelObj, title: currentState.title, description: currentState.description, - video_url: currentState.video_url)); + video_url: currentState.video_url, + steps: currentState.steps)); } else { + final Map step = { + 'description': '', + 'image': '', + }; emit(RecipeCreateLoadedState( title: TextEditingController(), description: TextEditingController(), - video_url: TextEditingController())); + video_url: TextEditingController(), + steps: [step])); } } diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart index cf5b12e3..2a331cb4 100644 --- a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart @@ -23,25 +23,33 @@ class RecipeCreateState extends Equatable { class RecipeCreateLoadedState extends RecipeCreateState { RecipeCreateLoadedState( - {this.recipeModelObj, this.title, this.description, this.video_url}); + {this.recipeModelObj, + this.title, + this.description, + this.video_url, + this.steps}); RecipeCreateModel? recipeModelObj; TextEditingController? title; TextEditingController? description; TextEditingController? video_url; + List? steps; @override - List get props => [recipeModelObj, title, description, video_url]; + List get props => + [recipeModelObj, title, description, video_url, steps]; RecipeCreateLoadedState copyWith({ RecipeCreateModel? recipeModelObj, TextEditingController? title, TextEditingController? description, TextEditingController? video_url, + List? steps, }) { return RecipeCreateLoadedState( recipeModelObj: recipeModelObj ?? this.recipeModelObj, title: title ?? this.title, description: description ?? this.description, video_url: video_url ?? this.video_url, + steps: steps ?? this.steps, ); } } diff --git a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart index 82d2bd9a..b663db22 100644 --- a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart +++ b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart @@ -2,7 +2,12 @@ import 'bloc/recipe_create_bloc.dart'; import 'models/recipe_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:eatvio_app/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/widgets/custom_outlined_button.dart'; class RecipeCreateScreen extends StatelessWidget { const RecipeCreateScreen({Key? key}) : super(key: key); @@ -39,64 +44,333 @@ class RecipeCreateScreen extends StatelessWidget { _buildRecipeCreate(BuildContext context, RecipeCreateLoadedState state) { mediaQueryData = MediaQuery.of(context); + List steps = []; + if (state.steps != null) { + for (int i = 0; i < state.steps!.length; i++) { + steps.add(_buildStep(context, state, i)); + } + } + return SafeArea( child: Scaffold( resizeToAvoidBottomInset: false, body: Container( width: double.maxFinite, - decoration: AppDecoration.fillGray, + decoration: BoxDecoration(color: Colors.grey[200]), child: ListView(children: [ - SizedBox(height: 30.v), - Container( - margin: EdgeInsets.only( - left: 16.h, - right: 20.h, - bottom: 10.h, - ), - padding: EdgeInsets.all(12.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(12)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: Offset(0, 3), - ), - ], - ), - child: Column(mainAxisSize: MainAxisSize.min, children: [ - SizedBox(height: 16.0), - TextFormField( - controller: state.title, - decoration: InputDecoration(labelText: 'Заголовок'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.description, - decoration: InputDecoration(labelText: 'Описание'), - ), - SizedBox(height: 16.0), - TextFormField( - controller: state.video_url, - decoration: InputDecoration(labelText: 'Ссылка'), - ), - SizedBox(height: 16.0), - // Здесь нужно добавить виджет для загрузки картинки - ElevatedButton( - onPressed: () => - context.read().add(CreateRecipe()), - child: Text('Создать'), - ) - ]), - ) + _buldAppBar(context), + SizedBox(height: 20.v), + _buildRecipeInfo(state, context), + SizedBox(height: 20.v), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: steps, + ), + SizedBox(height: 20.v), + _buildAdditional(context, state) ]), ), bottomNavigationBar: _buildBottomBar(context))); } + + _buldAppBar(BuildContext context) { + return Container( + margin: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: CustomAppBar( + height: 54.v, + leadingWidth: 28.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only(left: 16.h, top: 15.v, bottom: 15.v), + onTap: onTapBack(context), + ), + title: Align( + alignment: Alignment.center, + child: AppbarSubtitle( + text: "Добавление рецепта", + )), + ), + ); + } + + _buildRecipeInfo(RecipeCreateLoadedState state, BuildContext context) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Изображение")), + _buildAddPhoto(context, state), + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Название рецепта")), + CustomTextFormField( + controller: state.title, + hintText: "Введите название рецепта", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + Align(alignment: Alignment.centerLeft, child: Text("Описание")), + CustomTextFormField( + controller: state.description, + hintText: "Введите описание рецепта", + maxLines: 4, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + ]), + ); + } +} + +_buildAddPhoto(BuildContext context, RecipeCreateLoadedState state) { + bool isLoading = false; + return GestureDetector( + child: Stack( + children: [ + if (!isLoading) + Container( + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.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( + "Добавить фото", + selectionColor: appTheme.blueA400, + ), + ), + Spacer(), + Icon( + Icons.photo_library, + color: appTheme.blueGray300, + ) + ], + ), + ), + /*if (isLoading) + Container( + margin: EdgeInsets.symmetric(horizontal: 10.h), + padding: EdgeInsets.symmetric( + horizontal: 40.h, + vertical: 80.v, + ), + decoration: AppDecoration.fillGray.copyWith( + borderRadius: BorderRadiusStyle.roundedBorder5, + ), + child: Center( + child: CircularProgressIndicator( + color: appTheme + .blueGray300), // Замените на свой прелоадер при необходимости + ), + ),*/ + ], + ), + ); +} + +_buildStep(BuildContext context, RecipeCreateLoadedState state, int i) { + bool isLastStep = i == state.steps!.length - 1; + bool isFirstStep = i == 0; + + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + "Шаг ${i + 1}", + style: CustomTextStyles.titleMediumSemiBold, + ), + ), + SizedBox(height: 8.0), + CustomTextFormField( + controller: state.description, + hintText: "Введите описание шага", + maxLines: 4, + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + _buildAddPhoto(context, state), + if (!isFirstStep) + Row( + children: [ + Flexible( + flex: 5, + child: CustomOutlinedButton( + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + SizedBox(width: 8), // Add some spacing between the buttons + Flexible( + flex: 1, + child: CustomOutlinedButton( + text: "х", + onPressed: () => Navigator.pushNamed( + context, + AppRoutes.blogPostCreateScreen, + ), + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ), + ], + ), + if (isLastStep) // Добавляем кнопку "Добавить шаг" только для последнего шага + SizedBox(height: 16.0), + CustomOutlinedButton( + text: "Добавить шаг", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); +} + +_buildAdditional(BuildContext context, RecipeCreateLoadedState state) { + return Container( + margin: EdgeInsets.only( + left: 16.h, + right: 16.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Align(alignment: Alignment.centerLeft, child: Text("Теги")), + CustomTextFormField( + controller: state.title, + hintText: "Введите теги, через запятую", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 8.0), + Align( + alignment: Alignment.centerLeft, + child: Text("Ссылка на видео (необязательно)")), + CustomTextFormField( + controller: state.description, + hintText: "YouTube", + fillColor: appTheme.gray50, + borderDecoration: OutlineInputBorder( + borderRadius: BorderRadius.circular(5.h), + borderSide: BorderSide( + color: appTheme.gray50, + width: 1, + ), + ), + ), + SizedBox(height: 16.0), + CustomOutlinedButton( + text: "Добавить рецепт", + buttonStyle: CustomButtonStyles.outlinePrimaryTL15, + ), + ]), + ); } _buildBottomBar(BuildContext context) { return CustomBottomBar(onChanged: (BottomBarEnum type) {}); } + +Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; +} + +onTapArrowLeft(BuildContext context) { + NavigatorService.goBack(); +}