From 6576ce4bc4f52b6220c282835059448dac0d7783 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Fri, 3 May 2024 16:36:44 +0300 Subject: [PATCH 01/15] feat(chat): More layoouts --- .../chat/chat_screen/chat_screen.dart | 100 ++++++++---------- lib/routes/app_routes.dart | 4 +- 2 files changed, 49 insertions(+), 55 deletions(-) diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart index 8b1fc731..2a960624 100644 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ b/lib/presentation/chat/chat_screen/chat_screen.dart @@ -1,8 +1,13 @@ -/*import 'bloc/chat_bloc.dart'; +import 'bloc/chat_bloc.dart'; import 'models/chat_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_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:eatvio_app/widgets/custom_text_form_field.dart'; class ChatScreen extends StatelessWidget { const ChatScreen({Key? key}) : super(key: key); @@ -37,7 +42,7 @@ class ChatScreen extends StatelessWidget { alignment: Alignment.bottomRight, children: [ CustomImageView( - imagePath: ImageConstant.imgPath, + imagePath: ImageConstant.imageNotFound, height: 34.v, width: 211.h, alignment: Alignment.center, @@ -72,8 +77,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: - ImageConstant.imgCheckmarkBlueA400, + imagePath: ImageConstant.imageNotFound, height: 7.v, width: 14.h, margin: EdgeInsets.only( @@ -97,7 +101,7 @@ class ChatScreen extends StatelessWidget { ), child: _buildColumncheckmark( context, - imageOne: ImageConstant.imgRectangle, + imageOne: ImageConstant.imageNotFound, time: "lbl_10_15".tr, ), ), @@ -109,7 +113,7 @@ class ChatScreen extends StatelessWidget { ), child: _buildColumncheckmark( context, - imageOne: ImageConstant.imgRectangle186x254, + imageOne: ImageConstant.imageNotFound, time: "lbl_10_15".tr, ), ), @@ -127,9 +131,7 @@ class ChatScreen extends StatelessWidget { ), decoration: BoxDecoration( image: DecorationImage( - image: fs.Svg( - ImageConstant.imgGroup14, - ), + image: AssetImage(ImageConstant.imageNotFound), fit: BoxFit.cover, ), ), @@ -158,8 +160,8 @@ class ChatScreen extends StatelessWidget { children: [ Text( "lbl_martha_craig".tr, - style: CustomTextStyles - .bodyMediumLightblueA70001, + style: + CustomTextStyles.titleSmallBold, ), Text( "lbl_good_morning".tr, @@ -188,8 +190,8 @@ class ChatScreen extends StatelessWidget { ), child: Text( "lbl_11_40".tr, - style: CustomTextStyles - .bodySmallBluegray40001, + style: + CustomTextStyles.bodyMediumBlack900, ), ) ], @@ -208,7 +210,7 @@ class ChatScreen extends StatelessWidget { alignment: Alignment.topCenter, children: [ CustomImageView( - imagePath: ImageConstant.imgPathGray50, + imagePath: ImageConstant.imageNotFound, height: 34.v, width: 266.h, alignment: Alignment.center, @@ -249,8 +251,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: - ImageConstant.imgCheckmarkBlueA400, + imagePath: ImageConstant.imageNotFound, height: 7.v, width: 14.h, margin: EdgeInsets.only( @@ -281,9 +282,7 @@ class ChatScreen extends StatelessWidget { ), decoration: BoxDecoration( image: DecorationImage( - image: fs.Svg( - ImageConstant.imgGroup76, - ), + image: AssetImage(ImageConstant.imageNotFound), fit: BoxFit.cover, ), ), @@ -312,7 +311,8 @@ class ChatScreen extends StatelessWidget { ), child: Text( "lbl_11_45".tr, - style: CustomTextStyles.bodySmallBluegray40001, + style: + CustomTextStyles.bodySmallMulishBluegray800, ), ) ], @@ -330,7 +330,7 @@ class ChatScreen extends StatelessWidget { alignment: Alignment.bottomCenter, children: [ CustomImageView( - imagePath: ImageConstant.imgGroup14, + imagePath: ImageConstant.imageNotFound, height: 34.v, width: 178.h, alignment: Alignment.center, @@ -358,7 +358,7 @@ class ChatScreen extends StatelessWidget { child: Text( "lbl_11_45".tr, style: CustomTextStyles - .bodySmallBluegray40001, + .bodySmallMulishBluegray800, ), ) ], @@ -378,7 +378,7 @@ class ChatScreen extends StatelessWidget { alignment: Alignment.bottomCenter, children: [ CustomImageView( - imagePath: ImageConstant.imgPath, + imagePath: ImageConstant.imageNotFound, height: 34.v, width: 218.h, alignment: Alignment.center, @@ -406,8 +406,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: - ImageConstant.imgCheckmarkBlueA400, + imagePath: ImageConstant.imageNotFound, height: 7.v, width: 14.h, margin: EdgeInsets.only( @@ -436,9 +435,7 @@ class ChatScreen extends StatelessWidget { ), decoration: BoxDecoration( image: DecorationImage( - image: fs.Svg( - ImageConstant.imgGroup11, - ), + image: AssetImage(ImageConstant.imageNotFound), fit: BoxFit.cover, ), ), @@ -452,7 +449,7 @@ class ChatScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomImageView( - imagePath: ImageConstant.imgDownload, + imagePath: ImageConstant.imageNotFound, height: 40.adaptSize, width: 40.adaptSize, margin: EdgeInsets.only(bottom: 2.v), @@ -468,13 +465,13 @@ class ChatScreen extends StatelessWidget { children: [ Text( "lbl7".tr, - style: - CustomTextStyles.bodyLargeBlueA400, + style: CustomTextStyles + .bodyMediumBluegray800, ), Text( "lbl_2_8_mb".tr, - style: - CustomTextStyles.bodyMediumBlueA400, + style: CustomTextStyles + .bodyMediumBluegray800, ) ], ), @@ -502,8 +499,9 @@ class ChatScreen extends StatelessWidget { bottomNavigationBar: _buildBottomBar(context), ); } - /// Section Widget - _buildAppbar(BuildContext context) { + + /// Section Widget + _buildAppbar(BuildContext context) { return CustomAppBar( leadingWidth: 26.h, leading: AppbarLeadingImage( @@ -521,8 +519,8 @@ class ChatScreen extends StatelessWidget { padding: EdgeInsets.only(left: 12.h), child: Row( children: [ - AppbarTitleCircleimage( - imagePath: ImageConstant.imgOval40x40, + AppbarTitleImage( + imagePath: ImageConstant.imgIconMoreHorizontal, margin: EdgeInsets.only(bottom: 3.v), ), Padding( @@ -532,7 +530,7 @@ class ChatScreen extends StatelessWidget { ), child: Column( children: [ - AppbarTitle( + AppbarSubtitle( text: "lbl2".tr, ), SizedBox(height: 3.v), @@ -546,7 +544,6 @@ class ChatScreen extends StatelessWidget { ], ), ), - styleType: Style.bgFill, ); } @@ -558,13 +555,13 @@ class ChatScreen extends StatelessWidget { horizontal: 7.h, vertical: 6.v, ), - decoration: AppDecoration.outlineGray, + decoration: AppDecoration.outlineBlueGray, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomImageView( - imagePath: ImageConstant.imgAttach, + imagePath: ImageConstant.imageNotFound, height: 29.adaptSize, width: 29.adaptSize, margin: EdgeInsets.only( @@ -579,7 +576,7 @@ class ChatScreen extends StatelessWidget { bottom: 4.v, ), child: BlocSelector( - selector: (state) => state.messageController, + selector: (state) => TextEditingController(), builder: (context, messageController) { return CustomTextFormField( controller: messageController, @@ -591,7 +588,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: ImageConstant.imgMenu, + imagePath: ImageConstant.imageNotFound, height: 24.v, width: 18.h, margin: EdgeInsets.only( @@ -620,9 +617,7 @@ class ChatScreen extends StatelessWidget { padding: EdgeInsets.all(9.h), decoration: BoxDecoration( image: DecorationImage( - image: fs.Svg( - ImageConstant.imgGroup11, - ), + image: AssetImage(ImageConstant.imageNotFound), fit: BoxFit.cover, ), ), @@ -650,7 +645,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: ImageConstant.imgCheckmarkBlueA400, + imagePath: ImageConstant.imageNotFound, height: 7.v, width: 14.h, margin: EdgeInsets.only( @@ -681,7 +676,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: ImageConstant.imgCheckmarkBlueA400, + imagePath: ImageConstant.imageNotFound, height: 7.v, width: 14.h, margin: EdgeInsets.only( @@ -698,7 +693,7 @@ class ChatScreen extends StatelessWidget { String getCurrentRoute(BottomBarEnum type) { switch (type) { case BottomBarEnum.tf: - return AppRoutes.k1Page; + return AppRoutes.chatScreen; default: return "/"; } @@ -710,10 +705,10 @@ class ChatScreen extends StatelessWidget { String currentRoute, ) { switch (currentRoute) { - case AppRoutes.k1Page: - return K1Page.builder(context); + case AppRoutes.chatScreen: + return ChatScreen.builder(context); default: - return DefaultWidget(); + return ChatScreen(); } } @@ -722,4 +717,3 @@ class ChatScreen extends StatelessWidget { NavigatorService.goBack(); } } -*/ \ No newline at end of file diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 6f988361..97311c58 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -20,7 +20,7 @@ import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/my_profile_screen.dart'; import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_screen.dart'; import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart'; -//import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart'; +import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart'; import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_screen.dart'; class AppRoutes { @@ -101,7 +101,7 @@ class AppRoutes { myProfile: MyProfileScreen.builder, mySettings: MySettingsScreen.builder, diaryEdit: DiaryEditScreen.builder, - //chatScreen: ChatScreen.builder, + chatScreen: ChatScreen.builder, chatSelectScreen: ChatSelectScreen.builder, }; } From 150e6dc1f9d850fa2cc1cb727ecadabcba321427 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 6 May 2024 14:30:08 +0300 Subject: [PATCH 02/15] Lanscape orientation and more chat layouts --- assets/images/img_attach.svg | 3 + assets/images/img_mic.svg | 3 + lib/core/utils/image_constant.dart | 4 + lib/main.dart | 2 + .../chat/chat_screen/chat_screen.dart | 525 +----------------- 5 files changed, 27 insertions(+), 510 deletions(-) create mode 100644 assets/images/img_attach.svg create mode 100644 assets/images/img_mic.svg diff --git a/assets/images/img_attach.svg b/assets/images/img_attach.svg new file mode 100644 index 00000000..9f75ca7c --- /dev/null +++ b/assets/images/img_attach.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/img_mic.svg b/assets/images/img_mic.svg new file mode 100644 index 00000000..a7c11027 --- /dev/null +++ b/assets/images/img_mic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/utils/image_constant.dart b/lib/core/utils/image_constant.dart index 8534fec2..8c9c0981 100644 --- a/lib/core/utils/image_constant.dart +++ b/lib/core/utils/image_constant.dart @@ -104,6 +104,10 @@ class ImageConstant { static String imgSupport = '$imagePath/img_nav_support.svg'; + static String imgMic = '$imagePath/img_mic.svg'; + + static String imgAttach = '$imagePath/img_attach.svg'; + static String imgExit = '$imagePath/img_nav_exit.svg'; //------------------------------------------------------------- diff --git a/lib/main.dart b/lib/main.dart index 7b5326dc..3a5a94bd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,8 @@ void main() async { await dotenv.load(fileName: ".env"); await Hive.initFlutter(path); SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, DeviceOrientation.portraitUp, ]); await PrefUtils().init(); diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart index 2a960624..6fa4071a 100644 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ b/lib/presentation/chat/chat_screen/chat_screen.dart @@ -26,477 +26,17 @@ class ChatScreen extends StatelessWidget { appBar: _buildAppbar(context), body: SizedBox( width: double.maxFinite, - child: Column( - children: [ - SizedBox(height: 13.v), - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Container( - height: 34.v, - width: 211.h, - margin: EdgeInsets.only(right: 17.h), - child: Stack( - alignment: Alignment.bottomRight, - children: [ - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 34.v, - width: 211.h, - alignment: Alignment.center, - ), - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: 9.h, - bottom: 4.v, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(bottom: 1.v), - child: Text( - "msg_food_is_amazing".tr, - style: theme.textTheme.bodyLarge, - ), - ), - Padding( - padding: EdgeInsets.only( - left: 7.h, - top: 7.v, - ), - child: Text( - "lbl_10_10".tr, - style: theme.textTheme.bodySmall, - ), - ), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 7.v, - width: 14.h, - margin: EdgeInsets.only( - left: 2.h, - top: 9.v, - bottom: 4.v, - ), - ) - ], - ), - ), - ) - ], - ), - ), - SizedBox(height: 8.v), - Padding( - padding: EdgeInsets.only( - left: 84.h, - right: 17.h, - ), - child: _buildColumncheckmark( - context, - imageOne: ImageConstant.imageNotFound, - time: "lbl_10_15".tr, - ), - ), - SizedBox(height: 8.v), - Padding( - padding: EdgeInsets.only( - left: 84.h, - right: 17.h, - ), - child: _buildColumncheckmark( - context, - imageOne: ImageConstant.imageNotFound, - time: "lbl_10_15".tr, - ), - ), - SizedBox(height: 20.v), - Align( - alignment: Alignment.centerLeft, - child: Container( - margin: EdgeInsets.only( - left: 10.h, - right: 77.h, - ), - padding: EdgeInsets.symmetric( - horizontal: 11.h, - vertical: 4.v, - ), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(ImageConstant.imageNotFound), - fit: BoxFit.cover, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(height: 3.v), - Padding( - padding: EdgeInsets.only(left: 7.h), - child: Row( - children: [ - SizedBox( - height: 33.v, - child: VerticalDivider( - width: 2.h, - thickness: 2.v, - endIndent: 1.h, - ), - ), - Padding( - padding: EdgeInsets.only(left: 8.h), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "lbl_martha_craig".tr, - style: - CustomTextStyles.titleSmallBold, - ), - Text( - "lbl_good_morning".tr, - style: CustomTextStyles - .bodyMediumBluegray800_1, - ) - ], - ), - ) - ], - ), - ), - SizedBox(height: 4.v), - Padding( - padding: EdgeInsets.only(left: 6.h), - child: Row( - children: [ - Text( - "msg_do_you_know_what".tr, - style: theme.textTheme.bodyLarge, - ), - Padding( - padding: EdgeInsets.only( - left: 14.h, - top: 8.v, - ), - child: Text( - "lbl_11_40".tr, - style: - CustomTextStyles.bodyMediumBlack900, - ), - ) - ], - ), - ) - ], - ), - ), - ), - SizedBox(height: 20.v), - Container( - height: 34.v, - width: 266.h, - margin: EdgeInsets.only(right: 11.h), - child: Stack( - alignment: Alignment.topCenter, - children: [ - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 34.v, - width: 266.h, - alignment: Alignment.center, - ), - Align( - alignment: Alignment.topCenter, - child: Padding( - padding: EdgeInsets.only(top: 2.v), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(top: 4.v), - child: Text( - "msg_it_s_morning_in".tr, - style: theme.textTheme.bodyLarge, - ), - ), - CustomImageView( - imagePath: ImageConstant.img, - height: 19.v, - width: 17.h, - margin: EdgeInsets.only( - left: 10.h, - bottom: 7.v, - ), - ), - Padding( - padding: EdgeInsets.only( - left: 14.h, - top: 12.v, - ), - child: Text( - "lbl_11_43".tr, - style: theme.textTheme.bodySmall, - ), - ), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 7.v, - width: 14.h, - margin: EdgeInsets.only( - left: 2.h, - top: 13.v, - bottom: 5.v, - ), - ) - ], - ), - ), - ) - ], - ), - ), - SizedBox(height: 20.v), - Align( - alignment: Alignment.centerLeft, - child: Container( - width: 281.h, - margin: EdgeInsets.only( - left: 17.h, - right: 76.h, - ), - padding: EdgeInsets.symmetric( - horizontal: 11.h, - vertical: 2.v, - ), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(ImageConstant.imageNotFound), - fit: BoxFit.cover, - ), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Container( - width: 191.h, - margin: EdgeInsets.only(top: 3.v), - child: Text( - "msg_what_is_the_most".tr, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyLarge!.copyWith( - height: 1.29, - ), - ), - ), - ), - Padding( - padding: EdgeInsets.only( - left: 38.h, - top: 34.v, - ), - child: Text( - "lbl_11_45".tr, - style: - CustomTextStyles.bodySmallMulishBluegray800, - ), - ) - ], - ), - ), - ), - SizedBox(height: 8.v), - Align( - alignment: Alignment.centerLeft, - child: Container( - height: 34.v, - width: 178.h, - margin: EdgeInsets.only(left: 10.h), - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 34.v, - width: 178.h, - alignment: Alignment.center, - ), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only(bottom: 3.v), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(bottom: 1.v), - child: Text( - "lbl_do_you_like_it".tr, - style: theme.textTheme.bodyLarge, - ), - ), - Padding( - padding: EdgeInsets.only( - left: 1.h, - top: 8.v, - ), - child: Text( - "lbl_11_45".tr, - style: CustomTextStyles - .bodySmallMulishBluegray800, - ), - ) - ], - ), - ), - ) - ], - ), - ), - ), - SizedBox(height: 20.v), - Container( - height: 34.v, - width: 218.h, - margin: EdgeInsets.only(right: 17.h), - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 34.v, - width: 218.h, - alignment: Alignment.center, - ), - Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only(bottom: 4.v), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - "msg_i_think_top_two".tr, - style: theme.textTheme.bodyLarge, - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 7.v, - ), - child: Text( - "lbl_11_50".tr, - style: theme.textTheme.bodySmall, - ), - ), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 7.v, - width: 14.h, - margin: EdgeInsets.only( - left: 2.h, - top: 9.v, - bottom: 4.v, - ), - ) - ], - ), - ), - ) - ], - ), - ), - SizedBox(height: 8.v), - Container( - width: 235.h, - margin: EdgeInsets.only( - left: 123.h, - right: 16.h, - ), - padding: EdgeInsets.symmetric( - horizontal: 9.h, - vertical: 2.v, - ), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(ImageConstant.imageNotFound), - fit: BoxFit.cover, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 20.v), - Padding( - padding: EdgeInsets.only(left: 14.h), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 40.adaptSize, - width: 40.adaptSize, - margin: EdgeInsets.only(bottom: 2.v), - ), - Padding( - padding: EdgeInsets.only( - left: 31.h, - top: 5.v, - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "lbl7".tr, - style: CustomTextStyles - .bodyMediumBluegray800, - ), - Text( - "lbl_2_8_mb".tr, - style: CustomTextStyles - .bodyMediumBluegray800, - ) - ], - ), - ) - ], - ), - ), - SizedBox(height: 4.v), - _buildRowcheckmark( - context, - time: "lbl_11_51".tr, - ) - ], - ), - ), - SizedBox(height: 30.v), - _buildRowattachone(context) - ], - ), - ), - ) - ], + child: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView( + children: [ + SizedBox(height: 30.v), + ], + ), ), ), - bottomNavigationBar: _buildBottomBar(context), + bottomNavigationBar: _buildTextField(context), ); } @@ -531,13 +71,10 @@ class ChatScreen extends StatelessWidget { child: Column( children: [ AppbarSubtitle( - text: "lbl2".tr, + text: "Валерий Леонтьев", ), SizedBox(height: 3.v), - AppbarSubtitle( - text: "msg_12_30".tr, - margin: EdgeInsets.only(right: 38.h), - ) + Text("Был в сети вчера", style: theme.textTheme.bodySmall!) ], ), ) @@ -548,7 +85,7 @@ class ChatScreen extends StatelessWidget { } /// Section Widget - Widget _buildRowattachone(BuildContext context) { + _buildTextField(BuildContext context) { return Container( width: double.maxFinite, padding: EdgeInsets.symmetric( @@ -561,7 +98,7 @@ class ChatScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomImageView( - imagePath: ImageConstant.imageNotFound, + imagePath: ImageConstant.imgAttach, height: 29.adaptSize, width: 29.adaptSize, margin: EdgeInsets.only( @@ -580,7 +117,7 @@ class ChatScreen extends StatelessWidget { builder: (context, messageController) { return CustomTextFormField( controller: messageController, - hintText: "lbl_message".tr, + hintText: "Напишите сообщение...", textInputAction: TextInputAction.done, ); }, @@ -588,7 +125,7 @@ class ChatScreen extends StatelessWidget { ), ), CustomImageView( - imagePath: ImageConstant.imageNotFound, + imagePath: ImageConstant.imgMic, height: 24.v, width: 18.h, margin: EdgeInsets.only( @@ -602,11 +139,6 @@ class ChatScreen extends StatelessWidget { ); } - /// Section Widget - _buildBottomBar(BuildContext context) { - return CustomBottomBar(onChanged: (BottomBarEnum type) {}); - } - /// Common widget Widget _buildColumncheckmark( BuildContext context, { @@ -661,33 +193,6 @@ class ChatScreen extends StatelessWidget { } /// Common widget - Widget _buildRowcheckmark( - BuildContext context, { - required String time, - }) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - time, - style: theme.textTheme.bodySmall!.copyWith( - color: appTheme.blueA400, - ), - ), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 7.v, - width: 14.h, - margin: EdgeInsets.only( - left: 2.h, - top: 2.v, - bottom: 3.v, - ), - ) - ], - ); - } ///Handling route based on bottom click actions String getCurrentRoute(BottomBarEnum type) { From d9956ed549755028f0577b699dd80e1bc71f2a9d Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 7 May 2024 11:22:17 +0300 Subject: [PATCH 03/15] Data transfer and state control --- .../chat/chat_screen/bloc/chat_bloc.dart | 6 +- .../chat/chat_screen/chat_screen.dart | 86 ++++++------------- .../chat/chat_screen/models/chat_model.dart | 9 +- .../bloc/chat_select_bloc.dart | 1 - 4 files changed, 35 insertions(+), 67 deletions(-) diff --git a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart index 0f4a2add..bc91ba8d 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart @@ -27,16 +27,16 @@ class ChatBloc extends Bloc { AppRoutes.authScreen, ); } - final int id = 1; + final String? id = state.chatModelObj?.chatID; final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/$id/show'), + Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/show/$id'), headers: { 'Authorization': 'Bearer $token', }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); - print(data['chatRooms']); + print(data); if (data is Map && data.containsKey('chatRooms')) { if (state is ChatLoadedState) { var currentState = state as ChatLoadedState; diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart index 6fa4071a..28c42e27 100644 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ b/lib/presentation/chat/chat_screen/chat_screen.dart @@ -12,14 +12,37 @@ import 'package:eatvio_app/widgets/custom_text_form_field.dart'; class ChatScreen extends StatelessWidget { const ChatScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { + final String chatID = ModalRoute.of(context)!.settings.arguments as String; return BlocProvider( - create: (context) => ChatBloc(ChatState(chatModelObj: ChatModel())) - ..add(ChatInitialEvent()), + create: (context) => + ChatBloc(ChatState(chatModelObj: ChatModel(chatID: chatID))) + ..add(ChatInitialEvent()), child: ChatScreen()); } - @override Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is ChatInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is ChatLoadedState) { + // Данные загружены, можно строить экран. + return _buildChat(context, state); + } else if (state is ChatErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildChat(BuildContext context, ChatLoadedState state) { mediaQueryData = MediaQuery.of(context); return Scaffold( resizeToAvoidBottomInset: false, @@ -51,9 +74,7 @@ class ChatScreen extends StatelessWidget { top: 18.v, bottom: 25.v, ), - onTap: () { - onTapArrowleftone(context); - }, + onTap: () => onTapArrowleftone(context), ), title: Padding( padding: EdgeInsets.only(left: 12.h), @@ -139,59 +160,6 @@ class ChatScreen extends StatelessWidget { ); } - /// Common widget - Widget _buildColumncheckmark( - BuildContext context, { - required String imageOne, - required String time, - }) { - return Container( - padding: EdgeInsets.all(9.h), - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(ImageConstant.imageNotFound), - fit: BoxFit.cover, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - CustomImageView( - imagePath: imageOne, - height: 186.v, - width: 254.h, - radius: BorderRadius.circular( - 8.h, - ), - margin: EdgeInsets.only(right: 1.h), - ), - SizedBox(height: 10.v), - Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - time, - style: theme.textTheme.bodySmall!.copyWith( - color: appTheme.blueA400, - ), - ), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 7.v, - width: 14.h, - margin: EdgeInsets.only( - left: 3.h, - bottom: 4.v, - ), - ) - ], - ) - ], - ), - ); - } - /// Common widget ///Handling route based on bottom click actions diff --git a/lib/presentation/chat/chat_screen/models/chat_model.dart b/lib/presentation/chat/chat_screen/models/chat_model.dart index 0e74b3ee..4c6e566b 100644 --- a/lib/presentation/chat/chat_screen/models/chat_model.dart +++ b/lib/presentation/chat/chat_screen/models/chat_model.dart @@ -5,12 +5,13 @@ import 'package:equatable/equatable.dart'; /// This class defines the variables used in the [chat_screen], /// and is typically used to hold data that is passed between different parts of the application. class ChatModel extends Equatable { - ChatModel(); + String? chatID; + ChatModel({String? chatID}); - ChatModel copyWith() { - return ChatModel(); + ChatModel copyWith(String chatID) { + return ChatModel(chatID: chatID); } @override - List get props => []; + List get props => [chatID]; } diff --git a/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart b/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart index 740dbab7..f543c5aa 100644 --- a/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart +++ b/lib/presentation/chat/chat_select_screen/bloc/chat_select_bloc.dart @@ -59,7 +59,6 @@ class ChatSelectBloc extends Bloc { ); if (response.statusCode == 200) { final data = jsonDecode(response.body); - print(data['chatRooms']); if (data is Map && data.containsKey('chatRooms')) { if (state is ChatSelectLoadedState) { var currentState = state as ChatSelectLoadedState; From d537a05fac5a2b901946a5ad676ecff0123fba8b Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 7 May 2024 11:59:37 +0300 Subject: [PATCH 04/15] feat(chat): API method info success --- .../chat/chat_screen/bloc/chat_bloc.dart | 5 +- .../chat/chat_screen/bloc/chat_event.dart | 12 -- .../chat/chat_screen/chat_screen.dart | 9 +- .../chat/chat_screen/models/chat_model.dart | 6 +- .../widgets/chat_entry_widget.dart | 155 +++++++++--------- 5 files changed, 90 insertions(+), 97 deletions(-) diff --git a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart index bc91ba8d..c53410db 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart @@ -27,9 +27,10 @@ class ChatBloc extends Bloc { AppRoutes.authScreen, ); } - final String? id = state.chatModelObj?.chatID; + final String? chatID = state.chatModelObj?.chatID; + print(chatID); final response = await http.get( - Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/show/$id'), + Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/show/$chatID'), headers: { 'Authorization': 'Bearer $token', }, diff --git a/lib/presentation/chat/chat_screen/bloc/chat_event.dart b/lib/presentation/chat/chat_screen/bloc/chat_event.dart index a3778f98..1fe6df94 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_event.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_event.dart @@ -14,15 +14,3 @@ class ChatInitialEvent extends ChatEvent { @override List get props => []; } - -///event for dropdown selection -class ChangeDropDownEvent extends ChatEvent { - ChangeDropDownEvent({required this.value}); - - SelectionPopupModel value; - - @override - List get props => [ - value, - ]; -} diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart index 28c42e27..0a7e8c29 100644 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ b/lib/presentation/chat/chat_screen/chat_screen.dart @@ -12,11 +12,12 @@ import 'package:eatvio_app/widgets/custom_text_form_field.dart'; class ChatScreen extends StatelessWidget { const ChatScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { - final String chatID = ModalRoute.of(context)!.settings.arguments as String; + final chatID = ModalRoute.of(context)!.settings.arguments; + print(chatID.toString()); return BlocProvider( - create: (context) => - ChatBloc(ChatState(chatModelObj: ChatModel(chatID: chatID))) - ..add(ChatInitialEvent()), + create: (context) => ChatBloc( + ChatState(chatModelObj: ChatModel(chatID: chatID.toString()))) + ..add(ChatInitialEvent()), child: ChatScreen()); } diff --git a/lib/presentation/chat/chat_screen/models/chat_model.dart b/lib/presentation/chat/chat_screen/models/chat_model.dart index 4c6e566b..970b9ada 100644 --- a/lib/presentation/chat/chat_screen/models/chat_model.dart +++ b/lib/presentation/chat/chat_screen/models/chat_model.dart @@ -4,9 +4,11 @@ import 'package:equatable/equatable.dart'; /// This class defines the variables used in the [chat_screen], /// and is typically used to hold data that is passed between different parts of the application. + class ChatModel extends Equatable { - String? chatID; - ChatModel({String? chatID}); + final String chatID; + + ChatModel({required String chatID}) : chatID = chatID; ChatModel copyWith(String chatID) { return ChatModel(chatID: chatID); diff --git a/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart b/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart index a26b602b..52f9de3a 100644 --- a/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart +++ b/lib/presentation/chat/chat_select_screen/widgets/chat_entry_widget.dart @@ -16,29 +16,35 @@ class ChatEntryWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.all(8.h), - decoration: AppDecoration.fillGray100.copyWith( - borderRadius: BorderRadiusStyle.circleBorder8, + return GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.chatScreen, + arguments: chatEntryObj.id.toString(), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CachedNetworkImage( - imageUrl: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!, - imageBuilder: (context, imageProvider) => CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - /*CustomImageView( + child: Container( + padding: EdgeInsets.all(8.h), + decoration: AppDecoration.fillGray100.copyWith( + borderRadius: BorderRadiusStyle.circleBorder8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!, + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + /*CustomImageView( imagePath: dotenv.env['STORAGE_URL']! + chatEntryObj.userImage!, height: 60.adaptSize, @@ -47,62 +53,57 @@ class ChatEntryWidget extends StatelessWidget { 30.h, ), ),*/ - Expanded( - child: Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - bottom: 11.v, - ), - child: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.chatScreen, - arguments: chatEntryObj.id, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 227.h, - child: Row( - children: [ - Text( - chatEntryObj.username!.length <= 15 - ? chatEntryObj.username! - : chatEntryObj.username!.substring(0, 15) + '...', - style: theme.textTheme.titleMedium, - ), - Spacer(), - CustomImageView( - imagePath: ImageConstant.imageNotFound, - height: 9.v, - width: 16.h, - margin: EdgeInsets.only( - top: 4.v, - bottom: 6.v, - ), - ), - Padding( - padding: EdgeInsets.only(left: 6.h), - child: Text( - chatEntryObj.dateText!, - style: theme.textTheme.bodyMedium, - ), - ) - ], - ), + Expanded( + child: Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + bottom: 11.v, ), - SizedBox(height: 6.v), - Text( - chatEntryObj.greetingText!, - style: CustomTextStyles.bodyMediumBluegray800, - ) - ], - ), - ), - )) - ], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 227.h, + child: Row( + children: [ + Text( + chatEntryObj.username!.length <= 15 + ? chatEntryObj.username! + : chatEntryObj.username!.substring(0, 15) + + '...', + style: theme.textTheme.titleMedium, + ), + Spacer(), + CustomImageView( + imagePath: ImageConstant.imageNotFound, + height: 9.v, + width: 16.h, + margin: EdgeInsets.only( + top: 4.v, + bottom: 6.v, + ), + ), + Padding( + padding: EdgeInsets.only(left: 6.h), + child: Text( + chatEntryObj.dateText!, + style: theme.textTheme.bodyMedium, + ), + ) + ], + ), + ), + SizedBox(height: 6.v), + Text( + chatEntryObj.greetingText!, + style: CustomTextStyles.bodyMediumBluegray800, + ) + ], + ), + ), + ) + ]), ), ); } From 7279ff69a8a689d659d90e5d4099e3cd9550e6c7 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 7 May 2024 13:02:59 +0300 Subject: [PATCH 05/15] feat(chat): Messages list fill, message model --- .../chat/chat_screen/bloc/chat_bloc.dart | 29 ++++++++++++--- .../chat/chat_screen/bloc/chat_state.dart | 22 ++++-------- .../chat/chat_screen/chat_screen.dart | 1 - .../models/chat_message_model.dart | 36 +++++++++++++++++++ 4 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 lib/presentation/chat/chat_screen/models/chat_message_model.dart diff --git a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart index c53410db..f621d783 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart @@ -3,9 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_model.dart'; +import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_message_model.dart'; part 'chat_event.dart'; part 'chat_state.dart'; @@ -28,7 +30,6 @@ class ChatBloc extends Bloc { ); } final String? chatID = state.chatModelObj?.chatID; - print(chatID); final response = await http.get( Uri.parse(dotenv.env['SERVER_ROOT']! + '/chat/show/$chatID'), headers: { @@ -37,13 +38,17 @@ class ChatBloc extends Bloc { ); if (response.statusCode == 200) { final data = jsonDecode(response.body); - print(data); - if (data is Map && data.containsKey('chatRooms')) { + if (data is Map && + data.containsKey('chatRoom') && + data['chatRoom'].containsKey('messages')) { if (state is ChatLoadedState) { var currentState = state as ChatLoadedState; - emit(currentState.copyWith()); + emit(currentState.copyWith( + chatModelObj: currentState.chatModelObj, + messages: currentState.messages)); } else { - emit(ChatLoadedState()); + emit(ChatLoadedState( + messages: fillMessages(data['chatRoom']['messages']))); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -58,4 +63,18 @@ class ChatBloc extends Bloc { emit(ChatErrorState('Ошибка при получении данных о чатах: $e')); } } + + List fillMessages(List messagesJSON) { + List messages = []; + for (int i = 0; i < messagesJSON.length; i++) { + messages.add(ChatMessageModel( + id: messagesJSON[i]['id'], + data: messagesJSON[i]['data'], + mediaData: messagesJSON[i]['mediaData'] ?? [], + updatedAt: messagesJSON[i]['updated_at'], + authorId: messagesJSON[i]['author_id'])); + } + + return messages; + } } diff --git a/lib/presentation/chat/chat_screen/bloc/chat_state.dart b/lib/presentation/chat/chat_screen/bloc/chat_state.dart index aae133dc..6f5718d1 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_state.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_state.dart @@ -22,26 +22,18 @@ class ChatState extends Equatable { } class ChatLoadedState extends ChatState { - ChatLoadedState({ - this.selectedDropDownValue, - this.chatModelObj, - }); - - SelectionPopupModel? selectedDropDownValue; + ChatLoadedState({this.chatModelObj, this.messages}); ChatModel? chatModelObj; + List? messages; @override - List get props => [ - selectedDropDownValue, - chatModelObj, - ]; - ChatState copyWith({ - SelectionPopupModel? selectedDropDownValue, - ChatModel? chatModelObj, - }) { - return ChatState( + List get props => [chatModelObj, messages]; + ChatLoadedState copyWith( + {ChatModel? chatModelObj, List? messages}) { + return ChatLoadedState( chatModelObj: chatModelObj ?? this.chatModelObj, + messages: messages ?? this.messages, ); } } diff --git a/lib/presentation/chat/chat_screen/chat_screen.dart b/lib/presentation/chat/chat_screen/chat_screen.dart index 0a7e8c29..37c3df52 100644 --- a/lib/presentation/chat/chat_screen/chat_screen.dart +++ b/lib/presentation/chat/chat_screen/chat_screen.dart @@ -13,7 +13,6 @@ class ChatScreen extends StatelessWidget { const ChatScreen({Key? key}) : super(key: key); static Widget builder(BuildContext context) { final chatID = ModalRoute.of(context)!.settings.arguments; - print(chatID.toString()); return BlocProvider( create: (context) => ChatBloc( ChatState(chatModelObj: ChatModel(chatID: chatID.toString()))) diff --git a/lib/presentation/chat/chat_screen/models/chat_message_model.dart b/lib/presentation/chat/chat_screen/models/chat_message_model.dart new file mode 100644 index 00000000..e91596f3 --- /dev/null +++ b/lib/presentation/chat/chat_screen/models/chat_message_model.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; + +class ChatMessageModel extends Equatable { + final int id; + final String data; + final List mediaData; + final int authorId; + final String updatedAt; + + ChatMessageModel({ + required this.id, + required this.data, + required this.mediaData, + required this.authorId, + required this.updatedAt, + }); + + ChatMessageModel copyWith({ + int? id, + String? data, + List? mediaData, + int? authorId, + String? updatedAt, + }) { + return ChatMessageModel( + id: id ?? this.id, + data: data ?? this.data, + mediaData: mediaData ?? this.mediaData, + authorId: authorId ?? this.authorId, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + List get props => [id, data, mediaData, authorId, updatedAt]; +} From 404b22e78a94979ad4f08e367433add29a6d6201 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 7 May 2024 13:19:07 +0300 Subject: [PATCH 06/15] fix(version): Version reset to 0.0 --- eatvio_app.iml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eatvio_app.iml b/eatvio_app.iml index f66303d5..ed4ab455 100644 --- a/eatvio_app.iml +++ b/eatvio_app.iml @@ -1,4 +1,4 @@ - + diff --git a/pubspec.yaml b/pubspec.yaml index 14690c6d..8f1a6f45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.0.0+1 +version: 0.0.0+1 environment: sdk: '>=2.12.0 <3.0.0' dependencies: From 76a29ebf6706428b453f581a3d9314d0483e5f7e Mon Sep 17 00:00:00 2001 From: maxxkim Date: Wed, 8 May 2024 14:11:18 +0300 Subject: [PATCH 07/15] feat(app): Feed, recipe, blog posts, diet and plan show and index pages, state management, API implementation and layouts --- .../chat/chat_screen/bloc/chat_bloc.dart | 1 - .../bloc/diet_select_bloc.dart | 73 +++++++ .../bloc/diet_select_event.dart | 23 ++ .../bloc/diet_select_state.dart | 49 +++++ .../diet_select_screen.dart | 169 +++++++++++++++ .../models/diet_select_model.dart | 16 ++ .../diet_show_screen/bloc/diet_show_bloc.dart | 62 ++++++ .../bloc/diet_show_event.dart | 16 ++ .../bloc/diet_show_state.dart | 52 +++++ .../diet_show_screen/diet_show_screen.dart | 199 +++++++++++++++++ .../models/diet_show_model.dart | 19 ++ .../blog_post_screen/bloc/blog_post_bloc.dart | 62 ++++++ .../bloc/blog_post_event.dart | 16 ++ .../bloc/blog_post_state.dart | 52 +++++ .../blog_post_screen/blog_post_screen.dart | 200 ++++++++++++++++++ .../models/blog_post_model.dart | 19 ++ .../feed/feed_screen/bloc/feed_bloc.dart | 70 ++++++ .../feed/feed_screen/bloc/feed_event.dart | 23 ++ .../feed/feed_screen/bloc/feed_state.dart | 49 +++++ .../feed/feed_screen/feed_screen.dart | 168 +++++++++++++++ .../feed/feed_screen/models/feed_model.dart | 16 ++ .../bloc/plan_select_bloc.dart | 70 ++++++ .../bloc/plan_select_event.dart | 23 ++ .../bloc/plan_select_state.dart | 49 +++++ .../models/plan_select_model.dart | 16 ++ .../plan_select_screen.dart | 169 +++++++++++++++ .../plan_show_screen/bloc/plan_show_bloc.dart | 62 ++++++ .../bloc/plan_show_event.dart | 16 ++ .../bloc/plan_show_state.dart | 52 +++++ .../models/plan_show_model.dart | 19 ++ .../plan_show_screen/plan_show_screen.dart | 199 +++++++++++++++++ .../my_profile_screen/my_profile_screen.dart | 25 ++- .../bloc/recipe_select_bloc.dart | 70 ++++++ .../bloc/recipe_select_event.dart | 23 ++ .../bloc/recipe_select_state.dart | 49 +++++ .../models/recipe_select_model.dart | 16 ++ .../recipe_select_screen.dart | 169 +++++++++++++++ .../bloc/recipe_show_bloc.dart | 62 ++++++ .../bloc/recipe_show_event.dart | 16 ++ .../bloc/recipe_show_state.dart | 52 +++++ .../models/recipe_show_model.dart | 19 ++ .../recipe_show_screen.dart | 199 +++++++++++++++++ lib/routes/app_routes.dart | 32 +++ lib/widgets/custom_bottom_bar.dart | 1 + lib/widgets/custom_navigation_menu.dart | 18 +- 45 files changed, 2759 insertions(+), 21 deletions(-) create mode 100644 lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart create mode 100644 lib/presentation/diets/diet_select_screen/bloc/diet_select_event.dart create mode 100644 lib/presentation/diets/diet_select_screen/bloc/diet_select_state.dart create mode 100644 lib/presentation/diets/diet_select_screen/diet_select_screen.dart create mode 100644 lib/presentation/diets/diet_select_screen/models/diet_select_model.dart create mode 100644 lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart create mode 100644 lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart create mode 100644 lib/presentation/diets/diet_show_screen/bloc/diet_show_state.dart create mode 100644 lib/presentation/diets/diet_show_screen/diet_show_screen.dart create mode 100644 lib/presentation/diets/diet_show_screen/models/diet_show_model.dart create mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart create mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart create mode 100644 lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart create mode 100644 lib/presentation/feed/blog_post_screen/blog_post_screen.dart create mode 100644 lib/presentation/feed/blog_post_screen/models/blog_post_model.dart create mode 100644 lib/presentation/feed/feed_screen/bloc/feed_bloc.dart create mode 100644 lib/presentation/feed/feed_screen/bloc/feed_event.dart create mode 100644 lib/presentation/feed/feed_screen/bloc/feed_state.dart create mode 100644 lib/presentation/feed/feed_screen/feed_screen.dart create mode 100644 lib/presentation/feed/feed_screen/models/feed_model.dart create mode 100644 lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart create mode 100644 lib/presentation/plans/plan_select_screen/bloc/plan_select_event.dart create mode 100644 lib/presentation/plans/plan_select_screen/bloc/plan_select_state.dart create mode 100644 lib/presentation/plans/plan_select_screen/models/plan_select_model.dart create mode 100644 lib/presentation/plans/plan_select_screen/plan_select_screen.dart create mode 100644 lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart create mode 100644 lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart create mode 100644 lib/presentation/plans/plan_show_screen/bloc/plan_show_state.dart create mode 100644 lib/presentation/plans/plan_show_screen/models/plan_show_model.dart create mode 100644 lib/presentation/plans/plan_show_screen/plan_show_screen.dart create mode 100644 lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart create mode 100644 lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_event.dart create mode 100644 lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_state.dart create mode 100644 lib/presentation/recipe/recipe_select_screen/models/recipe_select_model.dart create mode 100644 lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart create mode 100644 lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart create mode 100644 lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart create mode 100644 lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart create mode 100644 lib/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart create mode 100644 lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart diff --git a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart index f621d783..52cf81df 100644 --- a/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart +++ b/lib/presentation/chat/chat_screen/bloc/chat_bloc.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:hive/hive.dart'; import 'package:http/http.dart' as http; -import 'package:intl/intl.dart'; import 'dart:convert'; import '/core/app_export.dart'; import 'package:eatvio_app/presentation/chat/chat_screen/models/chat_model.dart'; diff --git a/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart b/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart new file mode 100644 index 00000000..49def2d1 --- /dev/null +++ b/lib/presentation/diets/diet_select_screen/bloc/diet_select_bloc.dart @@ -0,0 +1,73 @@ +//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/diets/diet_select_screen/models/diet_select_model.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'diet_select_event.dart'; +part 'diet_select_state.dart'; + +/// A bloc that manages the state of a DietSelect according to the event that is dispatched to it. +class DietSelectBloc extends Bloc { + DietSelectBloc(DietSelectState initialState) : super(initialState) { + on(_onInitialize); + } + + Future _onInitialize( + DietSelectInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); // Если токен есть, запускаем приложение + } + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/index'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + print('Got response'); + if (response.statusCode == 200) { + print('Got 200'); + final data = jsonDecode(response.body); + if (data is List) { + print('Got List'); + if (state is DietSelectLoadedState) { + print('Got loaded'); + var currentState = state as DietSelectLoadedState; + emit((currentState).copyWith( + posts: data, + myProfileModelObj: currentState.myProfileModelObj, + )); + } else { + print('Got unloaded'); + emit(DietSelectLoadedState( + posts: data, + )); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(DietSelectErrorState('Неверный формат ответа API.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(DietSelectErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(DietSelectErrorState('Ошибка при получении данных ленты: $e')); + } + } +} diff --git a/lib/presentation/diets/diet_select_screen/bloc/diet_select_event.dart b/lib/presentation/diets/diet_select_screen/bloc/diet_select_event.dart new file mode 100644 index 00000000..91372953 --- /dev/null +++ b/lib/presentation/diets/diet_select_screen/bloc/diet_select_event.dart @@ -0,0 +1,23 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_select_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///DietSelect widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class DietSelectEvent extends Equatable {} + +/// Event that is dispatched when the DietSelect widget is first created. +class DietSelectInitialEvent extends DietSelectEvent { + @override + List get props => []; +} + +class UploadAvatar extends DietSelectEvent { + UploadAvatar(this.image); + final XFile image; + @override + List get props => [image]; +} diff --git a/lib/presentation/diets/diet_select_screen/bloc/diet_select_state.dart b/lib/presentation/diets/diet_select_screen/bloc/diet_select_state.dart new file mode 100644 index 00000000..e7b709db --- /dev/null +++ b/lib/presentation/diets/diet_select_screen/bloc/diet_select_state.dart @@ -0,0 +1,49 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_select_bloc.dart'; + +class DietSelectState extends Equatable { + const DietSelectState({this.myProfileModelObj}); + + final DietSelectModel? myProfileModelObj; + + @override + List get props => [myProfileModelObj]; +} + +class DietSelectInitialState extends DietSelectState { + DietSelectInitialState({required DietSelectModel myProfileModelObj}) + : super(myProfileModelObj: myProfileModelObj); +} + +/// Represents the state of DietSelect in the application. +class DietSelectLoadedState extends DietSelectState { + DietSelectLoadedState({this.myProfileModelObj, this.posts}) + : super(myProfileModelObj: null); + + DietSelectModel? myProfileModelObj; + List? posts; + @override + List get props => [ + myProfileModelObj, + posts, + ]; + DietSelectLoadedState copyWith({ + DietSelectModel? myProfileModelObj, + List? posts, + }) { + return DietSelectLoadedState( + myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj, + posts: posts ?? this.posts, + ); + } +} + +class DietSelectErrorState extends DietSelectState { + final String errorMessage; + + const DietSelectErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart new file mode 100644 index 00000000..eb14d7f1 --- /dev/null +++ b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart @@ -0,0 +1,169 @@ +import 'bloc/diet_select_bloc.dart'; +import 'models/diet_select_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter/material.dart'; + +class DietSelectScreen extends StatelessWidget { + static Widget builder(BuildContext context) { + return BlocProvider( + create: (context) => DietSelectBloc( + DietSelectState(myProfileModelObj: DietSelectModel())) + ..add(DietSelectInitialEvent()), + child: DietSelectScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DietSelectInitialState) { + // Это начальное состояние, данные ещё не загружены. + return CircularProgressIndicator( + color: Colors.white, + backgroundColor: Colors.white, + ); + } else if (state is DietSelectLoadedState) { + // Данные загружены, можно строить экран. + return _buildSetingsScreen(context, state); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildSetingsScreen(BuildContext context, DietSelectLoadedState state) { + mediaQueryData = MediaQuery.of(context); + print(state.posts); + return SafeArea( + child: Scaffold( + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + 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: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Диеты", + margin: EdgeInsets.only( + left: 80.h, top: 12.v, bottom: 9.v), + ), + ], + ), + ), + ), + ), + SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: + dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => + CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => + CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['name'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['details'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.dietScreen, + arguments: post?['id'].toString(), + ), + )); + }, + ), + ], + ), + ), + ), + ), + bottomNavigationBar: _buildBottomBar(context), + ), + ); + } + + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + onTapArrowLeft(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/diets/diet_select_screen/models/diet_select_model.dart b/lib/presentation/diets/diet_select_screen/models/diet_select_model.dart new file mode 100644 index 00000000..5dcaa802 --- /dev/null +++ b/lib/presentation/diets/diet_select_screen/models/diet_select_model.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [k0_page], +/// and is typically used to hold data that is passed between different parts of the application. +class DietSelectModel extends Equatable { + DietSelectModel(); + + DietSelectModel copyWith() { + return DietSelectModel(); + } + + @override + List get props => []; +} diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart new file mode 100644 index 00000000..064dcca9 --- /dev/null +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart @@ -0,0 +1,62 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/diets/diet_show_screen/models/diet_show_model.dart'; +part 'diet_show_event.dart'; +part 'diet_show_state.dart'; + +/// A bloc that manages the state of a Diet according to the event that is dispatched to it. +class DietBloc extends Bloc { + DietBloc(DietState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + DietInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? dietID = state.dietModelObj?.dietID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/show/$dietID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is DietLoadedState) { + var currentState = state as DietLoadedState; + emit(currentState.copyWith( + dietModelObj: currentState.dietModelObj, + diet: currentState.diet)); + } else { + emit(DietLoadedState(diet: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(DietErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(DietErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(DietErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart new file mode 100644 index 00000000..3f1d6801 --- /dev/null +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_show_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///Diet widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class DietEvent extends Equatable {} + +/// Event that is dispatched when the Diet widget is first created. +class DietInitialEvent extends DietEvent { + @override + List get props => []; +} diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_state.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_state.dart new file mode 100644 index 00000000..49913f7f --- /dev/null +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_state.dart @@ -0,0 +1,52 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_show_bloc.dart'; + +/// Represents the state of Diet in the application. +/// + +class DietState extends Equatable { + DietState({ + this.dietModelObj, + }); + + DietModel? dietModelObj; + + @override + List get props => [dietModelObj]; + DietState copyWith({DietModel? dietModelObj}) { + return DietState( + dietModelObj: dietModelObj ?? this.dietModelObj, + ); + } +} + +class DietLoadedState extends DietState { + DietLoadedState({this.dietModelObj, this.diet}); + + DietModel? dietModelObj; + Map? diet; + @override + List get props => [dietModelObj, diet]; + DietLoadedState copyWith( + {DietModel? dietModelObj, Map? diet}) { + return DietLoadedState( + dietModelObj: dietModelObj ?? this.dietModelObj, + diet: diet ?? this.diet, + ); + } +} + +class DietErrorState extends DietState { + final String errorMessage; + + DietErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class DietInitialState extends DietState { + DietInitialState({required DietModel dietModelObj}) + : super(dietModelObj: dietModelObj); +} diff --git a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart new file mode 100644 index 00000000..68fc6500 --- /dev/null +++ b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart @@ -0,0 +1,199 @@ +import 'bloc/diet_show_bloc.dart'; +import 'models/diet_show_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_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class DietScreen extends StatelessWidget { + const DietScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final dietID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => DietBloc( + DietState(dietModelObj: DietModel(dietID: dietID.toString()))) + ..add(DietInitialEvent()), + child: DietScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DietInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is DietLoadedState) { + // Данные загружены, можно строить экран. + return _buildDiet(context, state); + } else if (state is DietErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildDiet(BuildContext context, DietLoadedState state) { + print(state.diet); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.diet?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.diet?['name'], + style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.diet?['details'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + // Действие для редактирования + }, + child: Text('Редактировать'), + ), + ElevatedButton( + onPressed: () { + // Действие для удаления + }, + child: Text('Удалить'), + ), + ], + ) + ]), + ) + ], + ), + ), + bottomNavigationBar: _buildBottomBar(context))); + } + + /// Section Widget + _buildAppbar(BuildContext context) { + return CustomAppBar( + leadingWidth: 26.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only( + left: 16.h, + top: 18.v, + bottom: 25.v, + ), + onTap: () => onTapArrowleftone(context), + ), + title: Padding( + padding: EdgeInsets.only(left: 12.h), + child: Row( + children: [ + AppbarTitleImage( + imagePath: ImageConstant.imgIconMoreHorizontal, + margin: EdgeInsets.only(bottom: 3.v), + ), + Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + ), + child: Column( + children: [ + AppbarSubtitle( + text: "Валерий Леонтьев", + ), + SizedBox(height: 3.v), + Text("Был в сети вчера", style: theme.textTheme.bodySmall!) + ], + ), + ) + ], + ), + ), + ); + } + + ///Handling route based on bottom click actions + String getCurrentRoute(BottomBarEnum type) { + switch (type) { + case BottomBarEnum.tf: + return AppRoutes.dietScreen; + default: + return "/"; + } + } + + ///Handling page based on route + Widget getCurrentPage( + BuildContext context, + String currentRoute, + ) { + switch (currentRoute) { + case AppRoutes.dietScreen: + return DietScreen.builder(context); + default: + return DietScreen(); + } + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + /// Navigates to the previous screen. + onTapArrowleftone(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/diets/diet_show_screen/models/diet_show_model.dart b/lib/presentation/diets/diet_show_screen/models/diet_show_model.dart new file mode 100644 index 00000000..803827f8 --- /dev/null +++ b/lib/presentation/diets/diet_show_screen/models/diet_show_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [diet_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class DietModel extends Equatable { + final String dietID; + + DietModel({required String dietID}) : dietID = dietID; + + DietModel copyWith(String dietID) { + return DietModel(dietID: dietID); + } + + @override + List get props => [dietID]; +} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart new file mode 100644 index 00000000..6744b873 --- /dev/null +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart @@ -0,0 +1,62 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_screen/models/blog_post_model.dart'; +part 'blog_post_event.dart'; +part 'blog_post_state.dart'; + +/// A bloc that manages the state of a BlogPost according to the event that is dispatched to it. +class BlogPostBloc extends Bloc { + BlogPostBloc(BlogPostState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + BlogPostInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? blog_postID = state.blog_postModelObj?.blog_postID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/show/$blog_postID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is BlogPostLoadedState) { + var currentState = state as BlogPostLoadedState; + emit(currentState.copyWith( + blog_postModelObj: currentState.blog_postModelObj, + blog_post: currentState.blog_post)); + } else { + emit(BlogPostLoadedState(blog_post: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(BlogPostErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(BlogPostErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(BlogPostErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart new file mode 100644 index 00000000..d656a785 --- /dev/null +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///BlogPost widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class BlogPostEvent extends Equatable {} + +/// Event that is dispatched when the BlogPost widget is first created. +class BlogPostInitialEvent extends BlogPostEvent { + @override + List get props => []; +} diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart new file mode 100644 index 00000000..033ba293 --- /dev/null +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_state.dart @@ -0,0 +1,52 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_bloc.dart'; + +/// Represents the state of BlogPost in the application. +/// + +class BlogPostState extends Equatable { + BlogPostState({ + this.blog_postModelObj, + }); + + BlogPostModel? blog_postModelObj; + + @override + List get props => [blog_postModelObj]; + BlogPostState copyWith({BlogPostModel? blog_postModelObj}) { + return BlogPostState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + ); + } +} + +class BlogPostLoadedState extends BlogPostState { + BlogPostLoadedState({this.blog_postModelObj, this.blog_post}); + + BlogPostModel? blog_postModelObj; + Map? blog_post; + @override + List get props => [blog_postModelObj, blog_post]; + BlogPostLoadedState copyWith( + {BlogPostModel? blog_postModelObj, Map? blog_post}) { + return BlogPostLoadedState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + blog_post: blog_post ?? this.blog_post, + ); + } +} + +class BlogPostErrorState extends BlogPostState { + final String errorMessage; + + BlogPostErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class BlogPostInitialState extends BlogPostState { + BlogPostInitialState({required BlogPostModel blog_postModelObj}) + : super(blog_postModelObj: blog_postModelObj); +} diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart new file mode 100644 index 00000000..31260611 --- /dev/null +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -0,0 +1,200 @@ +import 'bloc/blog_post_bloc.dart'; +import 'models/blog_post_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_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class BlogPostScreen extends StatelessWidget { + const BlogPostScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final blog_postID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => BlogPostBloc(BlogPostState( + blog_postModelObj: + BlogPostModel(blog_postID: blog_postID.toString()))) + ..add(BlogPostInitialEvent()), + child: BlogPostScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BlogPostInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is BlogPostLoadedState) { + // Данные загружены, можно строить экран. + return _buildBlogPost(context, state); + } else if (state is BlogPostErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildBlogPost(BuildContext context, BlogPostLoadedState state) { + print(state.blog_post); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.blog_post?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.blog_post?['title'], + style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.blog_post?['description'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + // Действие для редактирования + }, + child: Text('Редактировать'), + ), + ElevatedButton( + onPressed: () { + // Действие для удаления + }, + child: Text('Удалить'), + ), + ], + ) + ]), + ) + ], + ), + ), + bottomNavigationBar: _buildBottomBar(context))); + } + + /// Section Widget + _buildAppbar(BuildContext context) { + return CustomAppBar( + leadingWidth: 26.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only( + left: 16.h, + top: 18.v, + bottom: 25.v, + ), + onTap: () => onTapArrowleftone(context), + ), + title: Padding( + padding: EdgeInsets.only(left: 12.h), + child: Row( + children: [ + AppbarTitleImage( + imagePath: ImageConstant.imgIconMoreHorizontal, + margin: EdgeInsets.only(bottom: 3.v), + ), + Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + ), + child: Column( + children: [ + AppbarSubtitle( + text: "Валерий Леонтьев", + ), + SizedBox(height: 3.v), + Text("Был в сети вчера", style: theme.textTheme.bodySmall!) + ], + ), + ) + ], + ), + ), + ); + } + + ///Handling route based on bottom click actions + String getCurrentRoute(BottomBarEnum type) { + switch (type) { + case BottomBarEnum.tf: + return AppRoutes.blogPostScreen; + default: + return "/"; + } + } + + ///Handling page based on route + Widget getCurrentPage( + BuildContext context, + String currentRoute, + ) { + switch (currentRoute) { + case AppRoutes.blogPostScreen: + return BlogPostScreen.builder(context); + default: + return BlogPostScreen(); + } + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + /// Navigates to the previous screen. + onTapArrowleftone(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/feed/blog_post_screen/models/blog_post_model.dart b/lib/presentation/feed/blog_post_screen/models/blog_post_model.dart new file mode 100644 index 00000000..a005b747 --- /dev/null +++ b/lib/presentation/feed/blog_post_screen/models/blog_post_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [blog_post_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class BlogPostModel extends Equatable { + final String blog_postID; + + BlogPostModel({required String blog_postID}) : blog_postID = blog_postID; + + BlogPostModel copyWith(String blog_postID) { + return BlogPostModel(blog_postID: blog_postID); + } + + @override + List get props => [blog_postID]; +} diff --git a/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart b/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart new file mode 100644 index 00000000..984bbbf7 --- /dev/null +++ b/lib/presentation/feed/feed_screen/bloc/feed_bloc.dart @@ -0,0 +1,70 @@ +//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/feed_screen/models/feed_model.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'feed_event.dart'; +part 'feed_state.dart'; + +/// A bloc that manages the state of a Feed according to the event that is dispatched to it. +class FeedBloc extends Bloc { + FeedBloc(FeedState initialState) : super(initialState) { + on(_onInitialize); + } + + Future _onInitialize( + FeedInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); // Если токен есть, запускаем приложение + } + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/index'), + ); + print('Got response'); + if (response.statusCode == 200) { + print('Got 200'); + final data = jsonDecode(response.body); + if (data is List) { + print('Got List'); + if (state is FeedLoadedState) { + print('Got loaded'); + var currentState = state as FeedLoadedState; + emit((currentState).copyWith( + posts: data, + myProfileModelObj: currentState.myProfileModelObj, + )); + } else { + print('Got unloaded'); + emit(FeedLoadedState( + posts: data, + )); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(FeedErrorState('Неверный формат ответа API.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(FeedErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(FeedErrorState('Ошибка при получении данных ленты: $e')); + } + } +} diff --git a/lib/presentation/feed/feed_screen/bloc/feed_event.dart b/lib/presentation/feed/feed_screen/bloc/feed_event.dart new file mode 100644 index 00000000..cbb510f5 --- /dev/null +++ b/lib/presentation/feed/feed_screen/bloc/feed_event.dart @@ -0,0 +1,23 @@ +// ignore_for_file: must_be_immutable + +part of 'feed_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///Feed widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class FeedEvent extends Equatable {} + +/// Event that is dispatched when the Feed widget is first created. +class FeedInitialEvent extends FeedEvent { + @override + List get props => []; +} + +class UploadAvatar extends FeedEvent { + UploadAvatar(this.image); + final XFile image; + @override + List get props => [image]; +} diff --git a/lib/presentation/feed/feed_screen/bloc/feed_state.dart b/lib/presentation/feed/feed_screen/bloc/feed_state.dart new file mode 100644 index 00000000..dbc57ac5 --- /dev/null +++ b/lib/presentation/feed/feed_screen/bloc/feed_state.dart @@ -0,0 +1,49 @@ +// ignore_for_file: must_be_immutable + +part of 'feed_bloc.dart'; + +class FeedState extends Equatable { + const FeedState({this.myProfileModelObj}); + + final FeedModel? myProfileModelObj; + + @override + List get props => [myProfileModelObj]; +} + +class FeedInitialState extends FeedState { + FeedInitialState({required FeedModel myProfileModelObj}) + : super(myProfileModelObj: myProfileModelObj); +} + +/// Represents the state of Feed in the application. +class FeedLoadedState extends FeedState { + FeedLoadedState({this.myProfileModelObj, this.posts}) + : super(myProfileModelObj: null); + + FeedModel? myProfileModelObj; + List? posts; + @override + List get props => [ + myProfileModelObj, + posts, + ]; + FeedLoadedState copyWith({ + FeedModel? myProfileModelObj, + List? posts, + }) { + return FeedLoadedState( + myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj, + posts: posts ?? this.posts, + ); + } +} + +class FeedErrorState extends FeedState { + final String errorMessage; + + const FeedErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart new file mode 100644 index 00000000..6a4c668d --- /dev/null +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -0,0 +1,168 @@ +import 'bloc/feed_bloc.dart'; +import 'models/feed_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter/material.dart'; + +class FeedScreen extends StatelessWidget { + static Widget builder(BuildContext context) { + return BlocProvider( + create: (context) => FeedBloc(FeedState(myProfileModelObj: FeedModel())) + ..add(FeedInitialEvent()), + child: FeedScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is FeedInitialState) { + // Это начальное состояние, данные ещё не загружены. + return CircularProgressIndicator( + color: Colors.white, + backgroundColor: Colors.white, + ); + } else if (state is FeedLoadedState) { + // Данные загружены, можно строить экран. + return _buildSetingsScreen(context, state); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildSetingsScreen(BuildContext context, FeedLoadedState state) { + mediaQueryData = MediaQuery.of(context); + print(state.posts); + return SafeArea( + child: Scaffold( + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + 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: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Лента", + margin: EdgeInsets.only( + left: 80.h, top: 12.v, bottom: 9.v), + ), + ], + ), + ), + ), + ), + SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: + dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => + CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => + CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['title'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['description'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostScreen, + arguments: post?['id'].toString(), + ), + )); + }, + ), + ], + ), + ), + ), + ), + bottomNavigationBar: _buildBottomBar(context), + ), + ); + } + + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + onTapArrowLeft(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/feed/feed_screen/models/feed_model.dart b/lib/presentation/feed/feed_screen/models/feed_model.dart new file mode 100644 index 00000000..9e13172c --- /dev/null +++ b/lib/presentation/feed/feed_screen/models/feed_model.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [k0_page], +/// and is typically used to hold data that is passed between different parts of the application. +class FeedModel extends Equatable { + FeedModel(); + + FeedModel copyWith() { + return FeedModel(); + } + + @override + List get props => []; +} diff --git a/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart b/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart new file mode 100644 index 00000000..40d5f6fd --- /dev/null +++ b/lib/presentation/plans/plan_select_screen/bloc/plan_select_bloc.dart @@ -0,0 +1,70 @@ +//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/plans/plan_select_screen/models/plan_select_model.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'plan_select_event.dart'; +part 'plan_select_state.dart'; + +/// A bloc that manages the state of a PlanSelect according to the event that is dispatched to it. +class PlanSelectBloc extends Bloc { + PlanSelectBloc(PlanSelectState initialState) : super(initialState) { + on(_onInitialize); + } + + Future _onInitialize( + PlanSelectInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); // Если токен есть, запускаем приложение + } + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/index'), + ); + print('Got response'); + if (response.statusCode == 200) { + print('Got 200'); + final data = jsonDecode(response.body); + if (data is List) { + print('Got List'); + if (state is PlanSelectLoadedState) { + print('Got loaded'); + var currentState = state as PlanSelectLoadedState; + emit((currentState).copyWith( + posts: data, + myProfileModelObj: currentState.myProfileModelObj, + )); + } else { + print('Got unloaded'); + emit(PlanSelectLoadedState( + posts: data, + )); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(PlanSelectErrorState('Неверный формат ответа API.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(PlanSelectErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(PlanSelectErrorState('Ошибка при получении данных ленты: $e')); + } + } +} diff --git a/lib/presentation/plans/plan_select_screen/bloc/plan_select_event.dart b/lib/presentation/plans/plan_select_screen/bloc/plan_select_event.dart new file mode 100644 index 00000000..5cb4b41b --- /dev/null +++ b/lib/presentation/plans/plan_select_screen/bloc/plan_select_event.dart @@ -0,0 +1,23 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_select_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///PlanSelect widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class PlanSelectEvent extends Equatable {} + +/// Event that is dispatched when the PlanSelect widget is first created. +class PlanSelectInitialEvent extends PlanSelectEvent { + @override + List get props => []; +} + +class UploadAvatar extends PlanSelectEvent { + UploadAvatar(this.image); + final XFile image; + @override + List get props => [image]; +} diff --git a/lib/presentation/plans/plan_select_screen/bloc/plan_select_state.dart b/lib/presentation/plans/plan_select_screen/bloc/plan_select_state.dart new file mode 100644 index 00000000..5ab68c28 --- /dev/null +++ b/lib/presentation/plans/plan_select_screen/bloc/plan_select_state.dart @@ -0,0 +1,49 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_select_bloc.dart'; + +class PlanSelectState extends Equatable { + const PlanSelectState({this.myProfileModelObj}); + + final PlanSelectModel? myProfileModelObj; + + @override + List get props => [myProfileModelObj]; +} + +class PlanSelectInitialState extends PlanSelectState { + PlanSelectInitialState({required PlanSelectModel myProfileModelObj}) + : super(myProfileModelObj: myProfileModelObj); +} + +/// Represents the state of PlanSelect in the application. +class PlanSelectLoadedState extends PlanSelectState { + PlanSelectLoadedState({this.myProfileModelObj, this.posts}) + : super(myProfileModelObj: null); + + PlanSelectModel? myProfileModelObj; + List? posts; + @override + List get props => [ + myProfileModelObj, + posts, + ]; + PlanSelectLoadedState copyWith({ + PlanSelectModel? myProfileModelObj, + List? posts, + }) { + return PlanSelectLoadedState( + myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj, + posts: posts ?? this.posts, + ); + } +} + +class PlanSelectErrorState extends PlanSelectState { + final String errorMessage; + + const PlanSelectErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/presentation/plans/plan_select_screen/models/plan_select_model.dart b/lib/presentation/plans/plan_select_screen/models/plan_select_model.dart new file mode 100644 index 00000000..238869f6 --- /dev/null +++ b/lib/presentation/plans/plan_select_screen/models/plan_select_model.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [k0_page], +/// and is typically used to hold data that is passed between different parts of the application. +class PlanSelectModel extends Equatable { + PlanSelectModel(); + + PlanSelectModel copyWith() { + return PlanSelectModel(); + } + + @override + List get props => []; +} diff --git a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart new file mode 100644 index 00000000..93268863 --- /dev/null +++ b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart @@ -0,0 +1,169 @@ +import 'bloc/plan_select_bloc.dart'; +import 'models/plan_select_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter/material.dart'; + +class PlanSelectScreen extends StatelessWidget { + static Widget builder(BuildContext context) { + return BlocProvider( + create: (context) => PlanSelectBloc( + PlanSelectState(myProfileModelObj: PlanSelectModel())) + ..add(PlanSelectInitialEvent()), + child: PlanSelectScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is PlanSelectInitialState) { + // Это начальное состояние, данные ещё не загружены. + return CircularProgressIndicator( + color: Colors.white, + backgroundColor: Colors.white, + ); + } else if (state is PlanSelectLoadedState) { + // Данные загружены, можно строить экран. + return _buildSetingsScreen(context, state); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildSetingsScreen(BuildContext context, PlanSelectLoadedState state) { + mediaQueryData = MediaQuery.of(context); + print(state.posts); + return SafeArea( + child: Scaffold( + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + 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: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Планы", + margin: EdgeInsets.only( + left: 80.h, top: 12.v, bottom: 9.v), + ), + ], + ), + ), + ), + ), + SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: + dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => + CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => + CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['name'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['details'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.planScreen, + arguments: post?['id'].toString(), + ), + )); + }, + ), + ], + ), + ), + ), + ), + bottomNavigationBar: _buildBottomBar(context), + ), + ); + } + + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + onTapArrowLeft(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart new file mode 100644 index 00000000..1416bafa --- /dev/null +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart @@ -0,0 +1,62 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/plans/plan_show_screen/models/plan_show_model.dart'; +part 'plan_show_event.dart'; +part 'plan_show_state.dart'; + +/// A bloc that manages the state of a Plan according to the event that is dispatched to it. +class PlanBloc extends Bloc { + PlanBloc(PlanState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + PlanInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? planID = state.planModelObj?.planID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/show/$planID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is PlanLoadedState) { + var currentState = state as PlanLoadedState; + emit(currentState.copyWith( + planModelObj: currentState.planModelObj, + plan: currentState.plan)); + } else { + emit(PlanLoadedState(plan: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(PlanErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(PlanErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(PlanErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart new file mode 100644 index 00000000..767eefad --- /dev/null +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_show_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///Plan widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class PlanEvent extends Equatable {} + +/// Event that is dispatched when the Plan widget is first created. +class PlanInitialEvent extends PlanEvent { + @override + List get props => []; +} diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_state.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_state.dart new file mode 100644 index 00000000..cefb6cf6 --- /dev/null +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_state.dart @@ -0,0 +1,52 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_show_bloc.dart'; + +/// Represents the state of Plan in the application. +/// + +class PlanState extends Equatable { + PlanState({ + this.planModelObj, + }); + + PlanModel? planModelObj; + + @override + List get props => [planModelObj]; + PlanState copyWith({PlanModel? planModelObj}) { + return PlanState( + planModelObj: planModelObj ?? this.planModelObj, + ); + } +} + +class PlanLoadedState extends PlanState { + PlanLoadedState({this.planModelObj, this.plan}); + + PlanModel? planModelObj; + Map? plan; + @override + List get props => [planModelObj, plan]; + PlanLoadedState copyWith( + {PlanModel? planModelObj, Map? plan}) { + return PlanLoadedState( + planModelObj: planModelObj ?? this.planModelObj, + plan: plan ?? this.plan, + ); + } +} + +class PlanErrorState extends PlanState { + final String errorMessage; + + PlanErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class PlanInitialState extends PlanState { + PlanInitialState({required PlanModel planModelObj}) + : super(planModelObj: planModelObj); +} diff --git a/lib/presentation/plans/plan_show_screen/models/plan_show_model.dart b/lib/presentation/plans/plan_show_screen/models/plan_show_model.dart new file mode 100644 index 00000000..6a6c37c2 --- /dev/null +++ b/lib/presentation/plans/plan_show_screen/models/plan_show_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [plan_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class PlanModel extends Equatable { + final String planID; + + PlanModel({required String planID}) : planID = planID; + + PlanModel copyWith(String planID) { + return PlanModel(planID: planID); + } + + @override + List get props => [planID]; +} diff --git a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart new file mode 100644 index 00000000..9da7deca --- /dev/null +++ b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart @@ -0,0 +1,199 @@ +import 'bloc/plan_show_bloc.dart'; +import 'models/plan_show_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_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class PlanScreen extends StatelessWidget { + const PlanScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final planID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => PlanBloc( + PlanState(planModelObj: PlanModel(planID: planID.toString()))) + ..add(PlanInitialEvent()), + child: PlanScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is PlanInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is PlanLoadedState) { + // Данные загружены, можно строить экран. + return _buildPlan(context, state); + } else if (state is PlanErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildPlan(BuildContext context, PlanLoadedState state) { + print(state.plan); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.plan?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.plan?['name'], + style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.plan?['details'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + // Действие для редактирования + }, + child: Text('Редактировать'), + ), + ElevatedButton( + onPressed: () { + // Действие для удаления + }, + child: Text('Удалить'), + ), + ], + ) + ]), + ) + ], + ), + ), + bottomNavigationBar: _buildBottomBar(context))); + } + + /// Section Widget + _buildAppbar(BuildContext context) { + return CustomAppBar( + leadingWidth: 26.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only( + left: 16.h, + top: 18.v, + bottom: 25.v, + ), + onTap: () => onTapArrowleftone(context), + ), + title: Padding( + padding: EdgeInsets.only(left: 12.h), + child: Row( + children: [ + AppbarTitleImage( + imagePath: ImageConstant.imgIconMoreHorizontal, + margin: EdgeInsets.only(bottom: 3.v), + ), + Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + ), + child: Column( + children: [ + AppbarSubtitle( + text: "Валерий Леонтьев", + ), + SizedBox(height: 3.v), + Text("Был в сети вчера", style: theme.textTheme.bodySmall!) + ], + ), + ) + ], + ), + ), + ); + } + + ///Handling route based on bottom click actions + String getCurrentRoute(BottomBarEnum type) { + switch (type) { + case BottomBarEnum.tf: + return AppRoutes.planScreen; + default: + return "/"; + } + } + + ///Handling page based on route + Widget getCurrentPage( + BuildContext context, + String currentRoute, + ) { + switch (currentRoute) { + case AppRoutes.planScreen: + return PlanScreen.builder(context); + default: + return PlanScreen(); + } + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + /// Navigates to the previous screen. + onTapArrowleftone(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart index ad417337..5d1f9bf3 100644 --- a/lib/presentation/profile/my_profile_screen/my_profile_screen.dart +++ b/lib/presentation/profile/my_profile_screen/my_profile_screen.dart @@ -140,12 +140,13 @@ class MyProfileScreen extends StatelessWidget { /// Section Widget _buildInfo(BuildContext context, MyProfileLoadedState state) { - return Column(children: [ - GestureDetector( - onTap: addImage(context), + return Column( + children: [ + GestureDetector( + onTap: () => addImage(context), child: CachedNetworkImage( - imageUrl: - dotenv.env['STORAGE_URL']! + state.user?['profile_photo_path'], + imageUrl: dotenv.env['STORAGE_URL']! + + (state.user?['profile_photo_path'] ?? ''), imageBuilder: (context, imageProvider) => CircleAvatar( backgroundImage: imageProvider, radius: 60, @@ -153,14 +154,16 @@ class MyProfileScreen extends StatelessWidget { placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => CircleAvatar( child: Icon(Icons.person), - backgroundColor: Colors.grey[200], + backgroundColor: Colors.white, radius: 60, ), - )), - AppbarSubtitle(text: state.user?['name']), - SizedBox(height: 20.v), - Text(state.userDetails?['additional_info']), - ]); + ), + ), + AppbarSubtitle(text: state.user?['name'] ?? ''), + SizedBox(height: 20.v), + Text(state.userDetails?['additional_info'] ?? ''), + ], + ); } addImage(BuildContext context) { diff --git a/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart new file mode 100644 index 00000000..fc9a96f4 --- /dev/null +++ b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_bloc.dart @@ -0,0 +1,70 @@ +//import 'package:eatvio_app/presentation/auth/auth_screen/auth_screen.dart'; +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_select_screen/models/recipe_select_model.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:image_picker/image_picker.dart'; + +part 'recipe_select_event.dart'; +part 'recipe_select_state.dart'; + +/// A bloc that manages the state of a RecipeSelect according to the event that is dispatched to it. +class RecipeSelectBloc extends Bloc { + RecipeSelectBloc(RecipeSelectState initialState) : super(initialState) { + on(_onInitialize); + } + + Future _onInitialize( + RecipeSelectInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); // Если токен есть, запускаем приложение + } + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/index'), + ); + print('Got response'); + if (response.statusCode == 200) { + print('Got 200'); + final data = jsonDecode(response.body); + if (data is List) { + print('Got List'); + if (state is RecipeSelectLoadedState) { + print('Got loaded'); + var currentState = state as RecipeSelectLoadedState; + emit((currentState).copyWith( + posts: data, + myProfileModelObj: currentState.myProfileModelObj, + )); + } else { + print('Got unloaded'); + emit(RecipeSelectLoadedState( + posts: data, + )); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(RecipeSelectErrorState('Неверный формат ответа API.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(RecipeSelectErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(RecipeSelectErrorState('Ошибка при получении данных ленты: $e')); + } + } +} diff --git a/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_event.dart b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_event.dart new file mode 100644 index 00000000..460c79c8 --- /dev/null +++ b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_event.dart @@ -0,0 +1,23 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_select_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///RecipeSelect widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class RecipeSelectEvent extends Equatable {} + +/// Event that is dispatched when the RecipeSelect widget is first created. +class RecipeSelectInitialEvent extends RecipeSelectEvent { + @override + List get props => []; +} + +class UploadAvatar extends RecipeSelectEvent { + UploadAvatar(this.image); + final XFile image; + @override + List get props => [image]; +} diff --git a/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_state.dart b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_state.dart new file mode 100644 index 00000000..feb87b2e --- /dev/null +++ b/lib/presentation/recipe/recipe_select_screen/bloc/recipe_select_state.dart @@ -0,0 +1,49 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_select_bloc.dart'; + +class RecipeSelectState extends Equatable { + const RecipeSelectState({this.myProfileModelObj}); + + final RecipeSelectModel? myProfileModelObj; + + @override + List get props => [myProfileModelObj]; +} + +class RecipeSelectInitialState extends RecipeSelectState { + RecipeSelectInitialState({required RecipeSelectModel myProfileModelObj}) + : super(myProfileModelObj: myProfileModelObj); +} + +/// Represents the state of RecipeSelect in the application. +class RecipeSelectLoadedState extends RecipeSelectState { + RecipeSelectLoadedState({this.myProfileModelObj, this.posts}) + : super(myProfileModelObj: null); + + RecipeSelectModel? myProfileModelObj; + List? posts; + @override + List get props => [ + myProfileModelObj, + posts, + ]; + RecipeSelectLoadedState copyWith({ + RecipeSelectModel? myProfileModelObj, + List? posts, + }) { + return RecipeSelectLoadedState( + myProfileModelObj: myProfileModelObj ?? this.myProfileModelObj, + posts: posts ?? this.posts, + ); + } +} + +class RecipeSelectErrorState extends RecipeSelectState { + final String errorMessage; + + const RecipeSelectErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/presentation/recipe/recipe_select_screen/models/recipe_select_model.dart b/lib/presentation/recipe/recipe_select_screen/models/recipe_select_model.dart new file mode 100644 index 00000000..1e9a1d2d --- /dev/null +++ b/lib/presentation/recipe/recipe_select_screen/models/recipe_select_model.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [k0_page], +/// and is typically used to hold data that is passed between different parts of the application. +class RecipeSelectModel extends Equatable { + RecipeSelectModel(); + + RecipeSelectModel copyWith() { + return RecipeSelectModel(); + } + + @override + List get props => []; +} diff --git a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart new file mode 100644 index 00000000..a16b4906 --- /dev/null +++ b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart @@ -0,0 +1,169 @@ +import 'bloc/recipe_select_bloc.dart'; +import 'models/recipe_select_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter/material.dart'; + +class RecipeSelectScreen extends StatelessWidget { + static Widget builder(BuildContext context) { + return BlocProvider( + create: (context) => RecipeSelectBloc( + RecipeSelectState(myProfileModelObj: RecipeSelectModel())) + ..add(RecipeSelectInitialEvent()), + child: RecipeSelectScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is RecipeSelectInitialState) { + // Это начальное состояние, данные ещё не загружены. + return CircularProgressIndicator( + color: Colors.white, + backgroundColor: Colors.white, + ); + } else if (state is RecipeSelectLoadedState) { + // Данные загружены, можно строить экран. + return _buildSetingsScreen(context, state); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildSetingsScreen(BuildContext context, RecipeSelectLoadedState state) { + mediaQueryData = MediaQuery.of(context); + print(state.posts); + return SafeArea( + child: Scaffold( + backgroundColor: Colors.grey[200], + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + decoration: BoxDecoration(color: Colors.grey[200]), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SizedBox(height: 10), + Container( + 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: Padding( + padding: EdgeInsets.only(left: 20.h), + child: Row( + children: [ + AppbarSubtitle( + text: "Рецепты", + margin: EdgeInsets.only( + left: 80.h, top: 12.v, bottom: 9.v), + ), + ], + ), + ), + ), + ), + SizedBox(height: 20), + ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: + dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => + CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => + CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['title'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['description'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.recipeScreen, + arguments: post?['id'].toString(), + ), + )); + }, + ), + ], + ), + ), + ), + ), + bottomNavigationBar: _buildBottomBar(context), + ), + ); + } + + Function onTapBack(BuildContext context) { + return () { + onTapArrowLeft(context); + }; + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + onTapArrowLeft(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart new file mode 100644 index 00000000..e5c375a6 --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart @@ -0,0 +1,62 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart'; +part 'recipe_show_event.dart'; +part 'recipe_show_state.dart'; + +/// A bloc that manages the state of a Recipe according to the event that is dispatched to it. +class RecipeBloc extends Bloc { + RecipeBloc(RecipeState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + RecipeInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? recipeID = state.recipeModelObj?.recipeID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/show/$recipeID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is RecipeLoadedState) { + var currentState = state as RecipeLoadedState; + emit(currentState.copyWith( + recipeModelObj: currentState.recipeModelObj, + recipe: currentState.recipe)); + } else { + emit(RecipeLoadedState(recipe: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(RecipeErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(RecipeErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(RecipeErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart new file mode 100644 index 00000000..489e3597 --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_show_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///Recipe widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class RecipeEvent extends Equatable {} + +/// Event that is dispatched when the Recipe widget is first created. +class RecipeInitialEvent extends RecipeEvent { + @override + List get props => []; +} diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart new file mode 100644 index 00000000..e8c1807f --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_state.dart @@ -0,0 +1,52 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_show_bloc.dart'; + +/// Represents the state of Recipe in the application. +/// + +class RecipeState extends Equatable { + RecipeState({ + this.recipeModelObj, + }); + + RecipeModel? recipeModelObj; + + @override + List get props => [recipeModelObj]; + RecipeState copyWith({RecipeModel? recipeModelObj}) { + return RecipeState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + ); + } +} + +class RecipeLoadedState extends RecipeState { + RecipeLoadedState({this.recipeModelObj, this.recipe}); + + RecipeModel? recipeModelObj; + Map? recipe; + @override + List get props => [recipeModelObj, recipe]; + RecipeLoadedState copyWith( + {RecipeModel? recipeModelObj, Map? recipe}) { + return RecipeLoadedState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + recipe: recipe ?? this.recipe, + ); + } +} + +class RecipeErrorState extends RecipeState { + final String errorMessage; + + RecipeErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class RecipeInitialState extends RecipeState { + RecipeInitialState({required RecipeModel recipeModelObj}) + : super(recipeModelObj: recipeModelObj); +} diff --git a/lib/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart b/lib/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart new file mode 100644 index 00000000..1786388e --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/models/recipe_show_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [recipe_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class RecipeModel extends Equatable { + final String recipeID; + + RecipeModel({required String recipeID}) : recipeID = recipeID; + + RecipeModel copyWith(String recipeID) { + return RecipeModel(recipeID: recipeID); + } + + @override + List get props => [recipeID]; +} diff --git a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart new file mode 100644 index 00000000..b09c5efa --- /dev/null +++ b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart @@ -0,0 +1,199 @@ +import 'bloc/recipe_show_bloc.dart'; +import 'models/recipe_show_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_subtitle.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; +import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class RecipeScreen extends StatelessWidget { + const RecipeScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final recipeID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => RecipeBloc(RecipeState( + recipeModelObj: RecipeModel(recipeID: recipeID.toString()))) + ..add(RecipeInitialEvent()), + child: RecipeScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is RecipeInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is RecipeLoadedState) { + // Данные загружены, можно строить экран. + return _buildRecipe(context, state); + } else if (state is RecipeErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildRecipe(BuildContext context, RecipeLoadedState state) { + print(state.recipe); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.recipe?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + Text(state.recipe?['title'], + style: CustomTextStyles.titleMediumBlack), + SizedBox(height: 8.0), + Text( + state.recipe?['description'], + ), + SizedBox(height: 16.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ElevatedButton( + onPressed: () { + // Действие для редактирования + }, + child: Text('Редактировать'), + ), + ElevatedButton( + onPressed: () { + // Действие для удаления + }, + child: Text('Удалить'), + ), + ], + ) + ]), + ) + ], + ), + ), + bottomNavigationBar: _buildBottomBar(context))); + } + + /// Section Widget + _buildAppbar(BuildContext context) { + return CustomAppBar( + leadingWidth: 26.h, + leading: AppbarLeadingImage( + imagePath: ImageConstant.imgArrowLeft, + margin: EdgeInsets.only( + left: 16.h, + top: 18.v, + bottom: 25.v, + ), + onTap: () => onTapArrowleftone(context), + ), + title: Padding( + padding: EdgeInsets.only(left: 12.h), + child: Row( + children: [ + AppbarTitleImage( + imagePath: ImageConstant.imgIconMoreHorizontal, + margin: EdgeInsets.only(bottom: 3.v), + ), + Padding( + padding: EdgeInsets.only( + left: 12.h, + top: 2.v, + ), + child: Column( + children: [ + AppbarSubtitle( + text: "Валерий Леонтьев", + ), + SizedBox(height: 3.v), + Text("Был в сети вчера", style: theme.textTheme.bodySmall!) + ], + ), + ) + ], + ), + ), + ); + } + + ///Handling route based on bottom click actions + String getCurrentRoute(BottomBarEnum type) { + switch (type) { + case BottomBarEnum.tf: + return AppRoutes.recipeScreen; + default: + return "/"; + } + } + + ///Handling page based on route + Widget getCurrentPage( + BuildContext context, + String currentRoute, + ) { + switch (currentRoute) { + case AppRoutes.recipeScreen: + return RecipeScreen.builder(context); + default: + return RecipeScreen(); + } + } + + _buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); + } + + /// Navigates to the previous screen. + onTapArrowleftone(BuildContext context) { + NavigatorService.goBack(); + } +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 97311c58..39b58b96 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/presentation/diets/diet_select_screen/diet_select_screen.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/presentation/registration/date_of_birth_screen/date_of_birth_screen.dart'; import 'package:eatvio_app/presentation/registration/city_of_living_screen/city_of_living_screen.dart'; @@ -22,6 +23,13 @@ import 'package:eatvio_app/presentation/profile/my_profile_screen/my_settings_sc import 'package:eatvio_app/presentation/diary/diary_edit_screen/diary_edit_screen.dart'; import 'package:eatvio_app/presentation/chat/chat_screen/chat_screen.dart'; import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_screen.dart'; +import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; +import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; +import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen.dart'; +import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; class AppRoutes { // REGISTRATION @@ -78,6 +86,22 @@ class AppRoutes { static const String chatSelectScreen = '/chatSelect'; + static const String feedScreen = '/feed'; + + static const String blogPostScreen = '/blogPost'; + + static const String recipeSelectScreen = '/recipeSelect'; + + static const String recipeScreen = '/recipe'; + + static const String dietSelectScreen = '/dietSelect'; + + static const String dietScreen = '/diet'; + + static const String planSelectScreen = '/planSelect'; + + static const String planScreen = '/plan'; + static Map get routes => { dateOfBirthScreen: DateOfBirthScreen.builder, cityOfLivingScreen: CityOfLivingScreen.builder, @@ -103,5 +127,13 @@ class AppRoutes { diaryEdit: DiaryEditScreen.builder, chatScreen: ChatScreen.builder, chatSelectScreen: ChatSelectScreen.builder, + feedScreen: FeedScreen.builder, + recipeSelectScreen: RecipeSelectScreen.builder, + dietSelectScreen: DietSelectScreen.builder, + planSelectScreen: PlanSelectScreen.builder, + recipeScreen: RecipeScreen.builder, + dietScreen: DietScreen.builder, + planScreen: PlanScreen.builder, + blogPostScreen: BlogPostScreen.builder, }; } diff --git a/lib/widgets/custom_bottom_bar.dart b/lib/widgets/custom_bottom_bar.dart index 6968a085..d6e47d6d 100644 --- a/lib/widgets/custom_bottom_bar.dart +++ b/lib/widgets/custom_bottom_bar.dart @@ -87,6 +87,7 @@ class CustomBottomBarState extends State { Navigator.pushNamed(context, AppRoutes.myProfile); break; case 1: + Navigator.pushNamed(context, AppRoutes.feedScreen); break; case 2: Navigator.pushNamed(context, AppRoutes.diaryEdit); diff --git a/lib/widgets/custom_navigation_menu.dart b/lib/widgets/custom_navigation_menu.dart index fbe6cbce..674dd0f2 100644 --- a/lib/widgets/custom_navigation_menu.dart +++ b/lib/widgets/custom_navigation_menu.dart @@ -21,23 +21,23 @@ class CustomNavigationMenu extends StatelessWidget { MenuItem( icon: ImageConstant.imgRecipes, text: 'Мои рецепты', - onTap: () { - // Действие при нажатии на "Мои рецепты" - }, + onTap: () => + Navigator.pushNamed(context, AppRoutes.recipeSelectScreen), + // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgDiets, text: 'Мои диеты', - onTap: () { - // Действие при нажатии на "Мои диеты" - }, + onTap: () => + Navigator.pushNamed(context, AppRoutes.dietSelectScreen), + // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgPlans, text: 'Мои планы', - onTap: () { - // Действие при нажатии на "Мои планы" - }, + onTap: () => + Navigator.pushNamed(context, AppRoutes.planSelectScreen), + // Действие при нажатии на "Мои рецепты" ), MenuItem( icon: ImageConstant.imgFinance, From 96ad5a7cfb0ff98eff7c266b959ce18d532a5b33 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 13 May 2024 11:53:42 +0300 Subject: [PATCH 08/15] Blog post edit layouts and BloC --- .../bloc/blog_post_edit_bloc.dart | 63 +++++++++ .../bloc/blog_post_edit_event.dart | 16 +++ .../bloc/blog_post_edit_state.dart | 69 ++++++++++ .../blog_post_edit_screen.dart | 128 ++++++++++++++++++ .../models/blog_post_edit_model.dart | 19 +++ .../blog_post_screen/blog_post_screen.dart | 9 +- .../feed/feed_screen/feed_screen.dart | 7 + lib/routes/app_routes.dart | 4 + 8 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart new file mode 100644 index 00000000..8928b58b --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart'; +part 'blog_post_edit_event.dart'; +part 'blog_post_edit_state.dart'; + +/// A bloc that manages the state of a BlogPostEdit according to the event that is dispatched to it. +class BlogPostEditBloc extends Bloc { + BlogPostEditBloc(BlogPostEditState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + BlogPostEditInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? blog_postID = state.blog_postModelObj?.blog_postID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/show/$blog_postID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is BlogPostEditLoadedState) { + var currentState = state as BlogPostEditLoadedState; + emit(currentState.copyWith( + blog_postModelObj: currentState.blog_postModelObj, + blog_post: currentState.blog_post)); + } else { + emit(BlogPostEditLoadedState(blog_post: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(BlogPostEditErrorState( + 'Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(BlogPostEditErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(BlogPostEditErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart new file mode 100644 index 00000000..debaa1d4 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_edit_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///BlogPostEdit widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class BlogPostEditEvent extends Equatable {} + +/// Event that is dispatched when the BlogPostEdit widget is first created. +class BlogPostEditInitialEvent extends BlogPostEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart new file mode 100644 index 00000000..321448ee --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart @@ -0,0 +1,69 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_edit_bloc.dart'; + +/// Represents the state of BlogPostEdit in the application. +/// + +class BlogPostEditState extends Equatable { + BlogPostEditState({ + this.blog_postModelObj, + }); + + BlogPostEditModel? blog_postModelObj; + + @override + List get props => [blog_postModelObj]; + BlogPostEditState copyWith({BlogPostEditModel? blog_postModelObj}) { + return BlogPostEditState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + ); + } +} + +class BlogPostEditLoadedState extends BlogPostEditState { + BlogPostEditLoadedState( + {this.blog_postModelObj, + this.blog_post, + this.title, + this.description, + this.youtube_url}); + + BlogPostEditModel? blog_postModelObj; + Map? blog_post; + TextEditingController? title; + TextEditingController? description; + TextEditingController? youtube_url; + @override + List get props => + [blog_postModelObj, blog_post, title, description, youtube_url]; + BlogPostEditLoadedState copyWith({ + BlogPostEditModel? blog_postModelObj, + Map? blog_post, + TextEditingController? title, + TextEditingController? description, + TextEditingController? youtube_url, + }) { + return BlogPostEditLoadedState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + blog_post: blog_post ?? this.blog_post, + title: title ?? this.title, + description: description ?? this.description, + youtube_url: youtube_url ?? this.youtube_url, + ); + } +} + +class BlogPostEditErrorState extends BlogPostEditState { + final String errorMessage; + + BlogPostEditErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class BlogPostEditInitialState extends BlogPostEditState { + BlogPostEditInitialState({required BlogPostEditModel blog_postModelObj}) + : super(blog_postModelObj: blog_postModelObj); +} diff --git a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart new file mode 100644 index 00000000..9a452d31 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart @@ -0,0 +1,128 @@ +import 'bloc/blog_post_edit_bloc.dart'; +import 'models/blog_post_edit_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class BlogPostEditScreen extends StatelessWidget { + const BlogPostEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final blog_postID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => BlogPostEditBloc(BlogPostEditState( + blog_postModelObj: + BlogPostEditModel(blog_postID: blog_postID.toString()))) + ..add(BlogPostEditInitialEvent()), + child: BlogPostEditScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BlogPostEditInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is BlogPostEditLoadedState) { + // Данные загружены, можно строить экран. + return _buildBlogPostEdit(context, state); + } else if (state is BlogPostEditErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) { + print(state.blog_post); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.blog_post?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.title, + decoration: InputDecoration(labelText: 'Заголовок'), + initialValue: state + .blog_post?['title'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.description, + decoration: InputDecoration(labelText: 'Описание'), + initialValue: state.blog_post?[ + 'description'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + initialValue: state.blog_post?[ + 'youtube_url'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () { + // Действия по сохранению записи в блоге + }, + child: Text('Сохранить'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart b/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart new file mode 100644 index 00000000..f9ef98c2 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [blog_post_edit_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class BlogPostEditModel extends Equatable { + final String blog_postID; + + BlogPostEditModel({required String blog_postID}) : blog_postID = blog_postID; + + BlogPostEditModel copyWith(String blog_postID) { + return BlogPostEditModel(blog_postID: blog_postID); + } + + @override + List get props => [blog_postID]; +} diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index 31260611..357ba232 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -3,6 +3,7 @@ import 'models/blog_post_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/custom_outlined_button.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; @@ -103,9 +104,11 @@ class BlogPostScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( - onPressed: () { - // Действие для редактирования - }, + onPressed: () => Navigator.pushNamed( + context, + AppRoutes.blogPostEditScreen, + arguments: state.blog_post?['id'].toString(), + ), child: Text('Редактировать'), ), ElevatedButton( diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart index 6a4c668d..55a8215e 100644 --- a/lib/presentation/feed/feed_screen/feed_screen.dart +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -139,6 +139,13 @@ class FeedScreen extends StatelessWidget { AppRoutes.blogPostScreen, arguments: post?['id'].toString(), ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), )); }, ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 39b58b96..25a9037b 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -30,6 +30,7 @@ import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen. import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; class AppRoutes { // REGISTRATION @@ -90,6 +91,8 @@ class AppRoutes { static const String blogPostScreen = '/blogPost'; + static const String blogPostEditScreen = '/blogPostEdit'; + static const String recipeSelectScreen = '/recipeSelect'; static const String recipeScreen = '/recipe'; @@ -135,5 +138,6 @@ class AppRoutes { dietScreen: DietScreen.builder, planScreen: PlanScreen.builder, blogPostScreen: BlogPostScreen.builder, + blogPostEditScreen: BlogPostEditScreen.builder, }; } From 1ad58f8f42cfeec0a5ac8e2020a032f066bdfd65 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 13 May 2024 12:49:47 +0300 Subject: [PATCH 09/15] feat(feed) Blog post edit API connect, TextController management and redirect --- .../bloc/blog_post_edit_bloc.dart | 65 ++++++++++++++++++- .../bloc/blog_post_edit_event.dart | 5 ++ .../blog_post_edit_screen.dart | 12 +--- .../blog_post_screen/bloc/blog_post_bloc.dart | 1 - .../blog_post_screen/blog_post_screen.dart | 1 - 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart index 8928b58b..f3b193e4 100644 --- a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -13,6 +15,7 @@ part 'blog_post_edit_state.dart'; class BlogPostEditBloc extends Bloc { BlogPostEditBloc(BlogPostEditState initialState) : super(initialState) { on(_onInitialize); + on(_editBlogPost); } _onInitialize( @@ -41,10 +44,16 @@ class BlogPostEditBloc extends Bloc { var currentState = state as BlogPostEditLoadedState; emit(currentState.copyWith( blog_postModelObj: currentState.blog_postModelObj, - blog_post: currentState.blog_post)); + blog_post: currentState.blog_post, + title: currentState.title, + description: currentState.description, + youtube_url: currentState.youtube_url)); } else { - emit(BlogPostEditLoadedState(blog_post: data)); - print(data); + emit(BlogPostEditLoadedState( + blog_post: data, + title: TextEditingController(text: data['title']), + description: TextEditingController(text: data['description']), + youtube_url: TextEditingController(text: data['youtube_url']))); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -60,4 +69,54 @@ class BlogPostEditBloc extends Bloc { emit(BlogPostEditErrorState('Ошибка при получении данных о чатах: $e')); } } + + FutureOr _editBlogPost( + EditBlogPost event, Emitter emit) async { + if (state is BlogPostEditLoadedState) { + var currentState = state as BlogPostEditLoadedState; + + var response = await sendBlogPost( + currentState.blog_post, + currentState.title?.text, + currentState.youtube_url?.text, + currentState.description?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.blogPostScreen, + arguments: currentState.blog_post?['id'].toString()); + } else { + print( + 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendBlogPost( + Map? blog_post, + String? title, + String? youtube_url, + String? description, + ) async { + int id = blog_post?['id']; + String url = '${dotenv.env['SERVER_ROOT']!}/feed/update/$id'; + + final Map credentials = { + 'id': id, + 'title': title, + 'description': description, + 'youtube_url': youtube_url, + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } } diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart index debaa1d4..c8961556 100644 --- a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart @@ -14,3 +14,8 @@ class BlogPostEditInitialEvent extends BlogPostEditEvent { @override List get props => []; } + +class EditBlogPost extends BlogPostEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart index 9a452d31..1bba76d6 100644 --- a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart +++ b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart @@ -41,7 +41,6 @@ class BlogPostEditScreen extends StatelessWidget { } _buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) { - print(state.blog_post); mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( @@ -90,29 +89,22 @@ class BlogPostEditScreen extends StatelessWidget { TextFormField( controller: state.title, decoration: InputDecoration(labelText: 'Заголовок'), - initialValue: state - .blog_post?['title'], // Если есть начальное значение ), SizedBox(height: 16.0), TextFormField( controller: state.description, decoration: InputDecoration(labelText: 'Описание'), - initialValue: state.blog_post?[ - 'description'], // Если есть начальное значение ), SizedBox(height: 16.0), TextFormField( controller: state.youtube_url, decoration: InputDecoration(labelText: 'Ссылка'), - initialValue: state.blog_post?[ - 'youtube_url'], // Если есть начальное значение ), SizedBox(height: 16.0), // Здесь нужно добавить виджет для загрузки картинки ElevatedButton( - onPressed: () { - // Действия по сохранению записи в блоге - }, + onPressed: () => + context.read().add(EditBlogPost()), child: Text('Сохранить'), ) ]), diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart index 6744b873..621beb1c 100644 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart @@ -44,7 +44,6 @@ class BlogPostBloc extends Bloc { blog_post: currentState.blog_post)); } else { emit(BlogPostLoadedState(blog_post: data)); - print(data); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index 357ba232..58672875 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -46,7 +46,6 @@ class BlogPostScreen extends StatelessWidget { } _buildBlogPost(BuildContext context, BlogPostLoadedState state) { - print(state.blog_post); mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( From e7e48905cfdaca3ce609bc798f7e9400ec1a4511 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 13 May 2024 16:21:23 +0300 Subject: [PATCH 10/15] feat(diets) Diets create and edit API connect, TextController management and redirect --- .../bloc/diet_create_bloc.dart | 93 ++++++++++++++ .../bloc/diet_create_event.dart | 21 ++++ .../bloc/diet_create_state.dart | 65 ++++++++++ .../diet_create_screen.dart | 99 +++++++++++++++ .../models/diet_create_model.dart | 19 +++ .../diet_edit_screen/bloc/diet_edit_bloc.dart | 117 ++++++++++++++++++ .../bloc/diet_edit_event.dart | 21 ++++ .../bloc/diet_edit_state.dart | 64 ++++++++++ .../diet_edit_screen/diet_edit_screen.dart | 114 +++++++++++++++++ .../models/diet_edit_model.dart | 19 +++ .../diet_select_screen.dart | 18 ++- .../diet_show_screen/diet_show_screen.dart | 8 +- .../bloc/blog_post_create_bloc.dart | 93 ++++++++++++++ .../bloc/blog_post_create_event.dart | 21 ++++ .../bloc/blog_post_create_state.dart | 62 ++++++++++ .../blog_post_create_screen.dart | 106 ++++++++++++++++ .../models/blog_post_create_model.dart | 20 +++ .../feed/feed_screen/feed_screen.dart | 11 +- lib/routes/app_routes.dart | 12 ++ 19 files changed, 978 insertions(+), 5 deletions(-) create mode 100644 lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart create mode 100644 lib/presentation/diets/diet_create_screen/bloc/diet_create_event.dart create mode 100644 lib/presentation/diets/diet_create_screen/bloc/diet_create_state.dart create mode 100644 lib/presentation/diets/diet_create_screen/diet_create_screen.dart create mode 100644 lib/presentation/diets/diet_create_screen/models/diet_create_model.dart create mode 100644 lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart create mode 100644 lib/presentation/diets/diet_edit_screen/bloc/diet_edit_event.dart create mode 100644 lib/presentation/diets/diet_edit_screen/bloc/diet_edit_state.dart create mode 100644 lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart create mode 100644 lib/presentation/diets/diet_edit_screen/models/diet_edit_model.dart create mode 100644 lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart create mode 100644 lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart create mode 100644 lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart create mode 100644 lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart create mode 100644 lib/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart diff --git a/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart b/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart new file mode 100644 index 00000000..e517c1ac --- /dev/null +++ b/lib/presentation/diets/diet_create_screen/bloc/diet_create_bloc.dart @@ -0,0 +1,93 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/diets/diet_create_screen/models/diet_create_model.dart'; +part 'diet_create_event.dart'; +part 'diet_create_state.dart'; + +/// A bloc that manages the state of a DietCreate according to the event that is dispatched to it. +class DietCreateBloc extends Bloc { + DietCreateBloc(DietCreateState initialState) : super(initialState) { + on(_onInitialize); + on(_createDiet); + } + + _onInitialize( + DietCreateInitialEvent event, + Emitter emit, + ) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + if (state is DietCreateLoadedState) { + var currentState = state as DietCreateLoadedState; + emit(currentState.copyWith( + dietModelObj: currentState.dietModelObj, + name: currentState.name, + details: currentState.details, + )); + } else { + emit(DietCreateLoadedState( + name: TextEditingController(), + details: TextEditingController(), + )); + } + } + + FutureOr _createDiet( + CreateDiet event, Emitter emit) async { + if (state is DietCreateLoadedState) { + var currentState = state as DietCreateLoadedState; + + var response = await sendDiet( + currentState.name?.text, + currentState.details?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.dietSelectScreen); + } else { + print( + 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendDiet( + String? name, + String? details, + ) async { + String url = '${dotenv.env['SERVER_ROOT']!}/diets/create'; + + final Map credentials = { + 'name': name, + 'details': details, + 'duration': '14', + 'state': 'active', + 'type': '{"meat": true}', + 'daily_schedule': + '{"1": {"lunch": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 494}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 12}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 432}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 195}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 710}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "grams", "quantity": 119}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 287}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 445}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 500}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 764}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 735}], "breakfast": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 127}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 89}]}, "2": {"lunch": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 436}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 700}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 766}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 194}], "dinner": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 426}], "snacks": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 58}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 152}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 925}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 224}], "breakfast": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 918}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 81}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 308}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 579}]}, "3": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 266}], "dinner": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 790}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 26}], "snacks": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 549}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 446}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 406}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 92}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 403}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 894}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 457}]}, "4": {"lunch": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 698}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 43}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 994}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 965}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 42}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 948}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 28}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 54}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 112}], "supper": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 891}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 538}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 635}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 586}]}, "5": {"lunch": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 900}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 242}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 375}], "snacks": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 604}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 510}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 887}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 554}], "supper": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 731}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 574}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 274}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 276}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 80}]}, "6": {"lunch": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 359}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 358}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 639}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 861}], "dinner": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 124}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 122}], "snacks": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 980}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 837}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 444}], "supper": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 577}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 706}], "breakfast": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 839}]}, "7": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 118}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 664}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 665}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 986}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 296}], "snacks": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 807}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 493}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 227}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 385}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 448}], "breakfast": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 784}]}, "8": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 433}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 871}], "dinner": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 332}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 41}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 388}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 649}], "supper": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 114}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 968}], "breakfast": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 180}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 269}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 998}]}, "9": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 68}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 505}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 554}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 579}], "snacks": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 751}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 144}], "supper": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 677}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 263}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 304}], "breakfast": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 963}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 258}]}, "10": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 167}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 306}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 243}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 79}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 560}], "snacks": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 294}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 145}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 639}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 108}], "supper": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 924}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 894}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 280}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 336}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 338}]}, "11": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 817}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 968}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 525}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 864}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 457}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 548}], "snacks": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 658}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 239}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 358}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 406}], "supper": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 416}], "breakfast": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 220}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 323}]}, "12": {"lunch": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 377}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 138}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 654}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 237}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 902}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 76}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 723}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 481}], "breakfast": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 571}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 515}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 188}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 411}]}, "13": {"lunch": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 379}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 231}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 376}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 147}], "dinner": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 871}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 679}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 359}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 561}], "snacks": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 414}], "supper": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 827}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 649}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 363}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 161}], "breakfast": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 185}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 464}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 387}]}, "14": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 248}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 5}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 693}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 655}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 709}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 881}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 588}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 36}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 453}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 692}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 186}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 721}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 740}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 731}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 842}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 850}]}, "15": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 124}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 380}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 211}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 689}], "dinner": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 326}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 629}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 201}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 129}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 749}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 315}], "breakfast": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 444}]}, "16": {"lunch": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 953}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 101}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 684}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 506}], "dinner": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 333}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 416}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 796}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 799}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 799}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 756}], "supper": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 858}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 619}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 800}], "breakfast": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 363}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 539}]}, "17": {"lunch": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 134}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 977}], "snacks": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 211}], "supper": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 209}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "grams", "quantity": 227}], "breakfast": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 195}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 917}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 706}]}, "18": {"lunch": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 296}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 239}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 798}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 155}], "dinner": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 959}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 958}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 984}], "supper": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 134}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 432}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 242}]}, "19": {"lunch": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 906}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 152}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 610}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 605}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 498}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 616}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 512}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 146}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 200}], "supper": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 764}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 353}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 181}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 783}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 506}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 887}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 130}]}, "20": {"lunch": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 48}], "dinner": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 266}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 143}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 55}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 174}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 855}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 6}], "supper": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 296}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 878}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 376}], "breakfast": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 442}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 528}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "milliliters", "quantity": 558}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 885}]}, "21": {"lunch": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 504}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 652}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 996}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 200}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 641}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 87}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 689}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 143}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 528}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 475}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 924}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 98}], "breakfast": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 438}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 383}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 462}]}}' + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/diets/diet_create_screen/bloc/diet_create_event.dart b/lib/presentation/diets/diet_create_screen/bloc/diet_create_event.dart new file mode 100644 index 00000000..a346af77 --- /dev/null +++ b/lib/presentation/diets/diet_create_screen/bloc/diet_create_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_create_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///DietCreate widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class DietCreateEvent extends Equatable {} + +/// Event that is dispatched when the DietCreate widget is first created. +class DietCreateInitialEvent extends DietCreateEvent { + @override + List get props => []; +} + +class CreateDiet extends DietCreateEvent { + @override + List get props => []; +} diff --git a/lib/presentation/diets/diet_create_screen/bloc/diet_create_state.dart b/lib/presentation/diets/diet_create_screen/bloc/diet_create_state.dart new file mode 100644 index 00000000..be825abe --- /dev/null +++ b/lib/presentation/diets/diet_create_screen/bloc/diet_create_state.dart @@ -0,0 +1,65 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_create_bloc.dart'; + +/// Represents the state of DietCreate in the application. +/// + +class DietCreateState extends Equatable { + DietCreateState({ + this.dietModelObj, + }); + + DietCreateModel? dietModelObj; + + @override + List get props => [dietModelObj]; + DietCreateState copyWith({DietCreateModel? dietModelObj}) { + return DietCreateState( + dietModelObj: dietModelObj ?? this.dietModelObj, + ); + } +} + +class DietCreateLoadedState extends DietCreateState { + DietCreateLoadedState({ + this.dietModelObj, + this.name, + this.details, + }); + + DietCreateModel? dietModelObj; + TextEditingController? name; + TextEditingController? details; + @override + List get props => [ + dietModelObj, + name, + details, + ]; + DietCreateLoadedState copyWith({ + DietCreateModel? dietModelObj, + TextEditingController? name, + TextEditingController? details, + }) { + return DietCreateLoadedState( + dietModelObj: dietModelObj ?? this.dietModelObj, + name: name ?? this.name, + details: details ?? this.details, + ); + } +} + +class DietCreateErrorState extends DietCreateState { + final String errorMessage; + + DietCreateErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class DietCreateInitialState extends DietCreateState { + DietCreateInitialState({required DietCreateModel dietModelObj}) + : super(dietModelObj: dietModelObj); +} diff --git a/lib/presentation/diets/diet_create_screen/diet_create_screen.dart b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart new file mode 100644 index 00000000..df525f60 --- /dev/null +++ b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart @@ -0,0 +1,99 @@ +import 'bloc/diet_create_bloc.dart'; +import 'models/diet_create_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class DietCreateScreen extends StatelessWidget { + const DietCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final dietID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => DietCreateBloc(DietCreateState( + dietModelObj: DietCreateModel(dietID: dietID.toString()))) + ..add(DietCreateInitialEvent()), + child: DietCreateScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DietCreateInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is DietCreateLoadedState) { + // Данные загружены, можно строить экран. + return _buildDietCreate(context, state); + } else if (state is DietCreateErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildDietCreate(BuildContext context, DietCreateLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(CreateDiet()), + child: Text('Создать'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/diets/diet_create_screen/models/diet_create_model.dart b/lib/presentation/diets/diet_create_screen/models/diet_create_model.dart new file mode 100644 index 00000000..955e8d63 --- /dev/null +++ b/lib/presentation/diets/diet_create_screen/models/diet_create_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [diet_create_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class DietCreateModel extends Equatable { + final String dietID; + + DietCreateModel({required String dietID}) : dietID = dietID; + + DietCreateModel copyWith(String dietID) { + return DietCreateModel(dietID: dietID); + } + + @override + List get props => [dietID]; +} diff --git a/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart new file mode 100644 index 00000000..d8167793 --- /dev/null +++ b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_bloc.dart @@ -0,0 +1,117 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/diets/diet_edit_screen/models/diet_edit_model.dart'; +part 'diet_edit_event.dart'; +part 'diet_edit_state.dart'; + +/// A bloc that manages the state of a DietEdit according to the event that is dispatched to it. +class DietEditBloc extends Bloc { + DietEditBloc(DietEditState initialState) : super(initialState) { + on(_onInitialize); + on(_editDiet); + } + + _onInitialize( + DietEditInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? dietID = state.dietModelObj?.dietID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/show/$dietID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is DietEditLoadedState) { + var currentState = state as DietEditLoadedState; + emit(currentState.copyWith( + dietModelObj: currentState.dietModelObj, + diet: currentState.diet, + name: currentState.name, + duration: currentState.duration, + details: currentState.details)); + } else { + emit(DietEditLoadedState( + diet: data, + name: TextEditingController(text: data['name']), + details: TextEditingController(text: data['details']), + duration: TextEditingController(text: data['duration']))); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(DietEditErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(DietEditErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(DietEditErrorState('Ошибка при получении данных о чатах: $e')); + } + } + + FutureOr _editDiet(EditDiet event, Emitter emit) async { + if (state is DietEditLoadedState) { + var currentState = state as DietEditLoadedState; + int duration = int.parse(currentState.duration!.text) * 7; + print(duration); + var response = await sendDiet(currentState.diet, currentState.name?.text, + currentState.details?.text, duration.toString()); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.dietScreen, + arguments: currentState.diet?['id'].toString()); + } else { + print( + 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendDiet( + Map? diet, + String? name, + String? details, + String? duration, + ) async { + int id = diet?['id']; + String url = '${dotenv.env['SERVER_ROOT']!}/diets/update/$id'; + + final Map credentials = { + 'id': id, + 'name': name, + 'details': details, + 'duration': '14', + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_event.dart b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_event.dart new file mode 100644 index 00000000..b2184444 --- /dev/null +++ b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_edit_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///DietEdit widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class DietEditEvent extends Equatable {} + +/// Event that is dispatched when the DietEdit widget is first created. +class DietEditInitialEvent extends DietEditEvent { + @override + List get props => []; +} + +class EditDiet extends DietEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_state.dart b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_state.dart new file mode 100644 index 00000000..1c10a404 --- /dev/null +++ b/lib/presentation/diets/diet_edit_screen/bloc/diet_edit_state.dart @@ -0,0 +1,64 @@ +// ignore_for_file: must_be_immutable + +part of 'diet_edit_bloc.dart'; + +/// Represents the state of DietEdit in the application. +/// + +class DietEditState extends Equatable { + DietEditState({ + this.dietModelObj, + }); + + DietEditModel? dietModelObj; + + @override + List get props => [dietModelObj]; + DietEditState copyWith({DietEditModel? dietModelObj}) { + return DietEditState( + dietModelObj: dietModelObj ?? this.dietModelObj, + ); + } +} + +class DietEditLoadedState extends DietEditState { + DietEditLoadedState( + {this.dietModelObj, this.diet, this.name, this.details, this.duration}); + + DietEditModel? dietModelObj; + Map? diet; + TextEditingController? name; + TextEditingController? details; + TextEditingController? duration; + @override + List get props => [dietModelObj, diet, name, details, duration]; + DietEditLoadedState copyWith({ + DietEditModel? dietModelObj, + Map? diet, + TextEditingController? name, + TextEditingController? details, + TextEditingController? duration, + }) { + return DietEditLoadedState( + dietModelObj: dietModelObj ?? this.dietModelObj, + diet: diet ?? this.diet, + name: name ?? this.name, + details: details ?? this.details, + duration: duration ?? this.duration, + ); + } +} + +class DietEditErrorState extends DietEditState { + final String errorMessage; + + DietEditErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class DietEditInitialState extends DietEditState { + DietEditInitialState({required DietEditModel dietModelObj}) + : super(dietModelObj: dietModelObj); +} diff --git a/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart b/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart new file mode 100644 index 00000000..75f02810 --- /dev/null +++ b/lib/presentation/diets/diet_edit_screen/diet_edit_screen.dart @@ -0,0 +1,114 @@ +import 'bloc/diet_edit_bloc.dart'; +import 'models/diet_edit_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class DietEditScreen extends StatelessWidget { + const DietEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final dietID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => DietEditBloc(DietEditState( + dietModelObj: DietEditModel(dietID: dietID.toString()))) + ..add(DietEditInitialEvent()), + child: DietEditScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is DietEditInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is DietEditLoadedState) { + // Данные загружены, можно строить экран. + return _buildDietEdit(context, state); + } else if (state is DietEditErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildDietEdit(BuildContext context, DietEditLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.diet?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(EditDiet()), + child: Text('Сохранить'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/diets/diet_edit_screen/models/diet_edit_model.dart b/lib/presentation/diets/diet_edit_screen/models/diet_edit_model.dart new file mode 100644 index 00000000..8c02d1b1 --- /dev/null +++ b/lib/presentation/diets/diet_edit_screen/models/diet_edit_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [diet_edit_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class DietEditModel extends Equatable { + final String dietID; + + DietEditModel({required String dietID}) : dietID = dietID; + + DietEditModel copyWith(String dietID) { + return DietEditModel(dietID: dietID); + } + + @override + List get props => [dietID]; +} diff --git a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart index eb14d7f1..b37dcb7f 100644 --- a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart +++ b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart @@ -40,7 +40,6 @@ class DietSelectScreen extends StatelessWidget { _buildSetingsScreen(BuildContext context, DietSelectLoadedState state) { mediaQueryData = MediaQuery.of(context); - print(state.posts); return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], @@ -86,6 +85,16 @@ class DietSelectScreen extends StatelessWidget { margin: EdgeInsets.only( left: 80.h, top: 12.v, bottom: 9.v), ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.dietCreateScreen, + ), + child: Icon(Icons.create_outlined)), + ) ], ), ), @@ -140,6 +149,13 @@ class DietSelectScreen extends StatelessWidget { AppRoutes.dietScreen, arguments: post?['id'].toString(), ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.dietEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), )); }, ), diff --git a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart index 68fc6500..ce98505c 100644 --- a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart +++ b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart @@ -102,9 +102,11 @@ class DietScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( - onPressed: () { - // Действие для редактирования - }, + onPressed: () => Navigator.pushNamed( + context, + AppRoutes.dietEditScreen, + arguments: state.diet?['id'].toString(), + ), child: Text('Редактировать'), ), ElevatedButton( diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart new file mode 100644 index 00000000..fee6cd51 --- /dev/null +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_bloc.dart @@ -0,0 +1,93 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart'; +part 'blog_post_create_event.dart'; +part 'blog_post_create_state.dart'; + +/// A bloc that manages the state of a BlogPostCreate according to the event that is dispatched to it. +class BlogPostCreateBloc + extends Bloc { + BlogPostCreateBloc(BlogPostCreateState initialState) : super(initialState) { + on(_onInitialize); + on(_createBlogPost); + } + + _onInitialize( + BlogPostCreateInitialEvent event, + Emitter emit, + ) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + if (state is BlogPostCreateLoadedState) { + var currentState = state as BlogPostCreateLoadedState; + emit(currentState.copyWith( + blog_postModelObj: currentState.blog_postModelObj, + title: currentState.title, + description: currentState.description, + youtube_url: currentState.youtube_url)); + } else { + emit(BlogPostCreateLoadedState( + title: TextEditingController(), + description: TextEditingController(), + youtube_url: TextEditingController())); + } + } + + FutureOr _createBlogPost( + CreateBlogPost event, Emitter emit) async { + if (state is BlogPostCreateLoadedState) { + var currentState = state as BlogPostCreateLoadedState; + + var response = await sendBlogPost( + currentState.title?.text, + currentState.youtube_url?.text, + currentState.description?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.feedScreen); + } else { + print( + 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendBlogPost( + String? title, + String? youtube_url, + String? description, + ) async { + String url = '${dotenv.env['SERVER_ROOT']!}/feed/create'; + + final Map credentials = { + 'title': title, + 'description': description, + 'youtube_url': youtube_url, + 'status': 'active', + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart new file mode 100644 index 00000000..7631fac0 --- /dev/null +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_create_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///BlogPostCreate widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class BlogPostCreateEvent extends Equatable {} + +/// Event that is dispatched when the BlogPostCreate widget is first created. +class BlogPostCreateInitialEvent extends BlogPostCreateEvent { + @override + List get props => []; +} + +class CreateBlogPost extends BlogPostCreateEvent { + @override + List get props => []; +} diff --git a/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart new file mode 100644 index 00000000..0cc3c405 --- /dev/null +++ b/lib/presentation/feed/blog_post_create_screen/bloc/blog_post_create_state.dart @@ -0,0 +1,62 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_create_bloc.dart'; + +/// Represents the state of BlogPostCreate in the application. +/// + +class BlogPostCreateState extends Equatable { + BlogPostCreateState({ + this.blog_postModelObj, + }); + + BlogPostCreateModel? blog_postModelObj; + + @override + List get props => [blog_postModelObj]; + BlogPostCreateState copyWith({BlogPostCreateModel? blog_postModelObj}) { + return BlogPostCreateState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + ); + } +} + +class BlogPostCreateLoadedState extends BlogPostCreateState { + BlogPostCreateLoadedState( + {this.blog_postModelObj, this.title, this.description, this.youtube_url}); + + BlogPostCreateModel? blog_postModelObj; + TextEditingController? title; + TextEditingController? description; + TextEditingController? youtube_url; + @override + List get props => + [blog_postModelObj, title, description, youtube_url]; + BlogPostCreateLoadedState copyWith({ + BlogPostCreateModel? blog_postModelObj, + TextEditingController? title, + TextEditingController? description, + TextEditingController? youtube_url, + }) { + return BlogPostCreateLoadedState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + title: title ?? this.title, + description: description ?? this.description, + youtube_url: youtube_url ?? this.youtube_url, + ); + } +} + +class BlogPostCreateErrorState extends BlogPostCreateState { + final String errorMessage; + + BlogPostCreateErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class BlogPostCreateInitialState extends BlogPostCreateState { + BlogPostCreateInitialState({required BlogPostCreateModel blog_postModelObj}) + : super(blog_postModelObj: blog_postModelObj); +} diff --git a/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart new file mode 100644 index 00000000..f19a29a4 --- /dev/null +++ b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart @@ -0,0 +1,106 @@ +import 'bloc/blog_post_create_bloc.dart'; +import 'models/blog_post_create_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class BlogPostCreateScreen extends StatelessWidget { + const BlogPostCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final blog_postID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => BlogPostCreateBloc(BlogPostCreateState( + blog_postModelObj: + BlogPostCreateModel(blog_postID: blog_postID.toString()))) + ..add(BlogPostCreateInitialEvent()), + child: BlogPostCreateScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BlogPostCreateInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is BlogPostCreateLoadedState) { + // Данные загружены, можно строить экран. + return _buildBlogPostCreate(context, state); + } else if (state is BlogPostCreateErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildBlogPostCreate(BuildContext context, BlogPostCreateLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => context + .read() + .add(CreateBlogPost()), + child: Text('Создать'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart b/lib/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart new file mode 100644 index 00000000..46b21c4e --- /dev/null +++ b/lib/presentation/feed/blog_post_create_screen/models/blog_post_create_model.dart @@ -0,0 +1,20 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [blog_post_create_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class BlogPostCreateModel extends Equatable { + final String blog_postID; + + BlogPostCreateModel({required String blog_postID}) + : blog_postID = blog_postID; + + BlogPostCreateModel copyWith(String blog_postID) { + return BlogPostCreateModel(blog_postID: blog_postID); + } + + @override + List get props => [blog_postID]; +} diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart index 55a8215e..e0c6b78f 100644 --- a/lib/presentation/feed/feed_screen/feed_screen.dart +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -39,7 +39,6 @@ class FeedScreen extends StatelessWidget { _buildSetingsScreen(BuildContext context, FeedLoadedState state) { mediaQueryData = MediaQuery.of(context); - print(state.posts); return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], @@ -85,6 +84,16 @@ class FeedScreen extends StatelessWidget { margin: EdgeInsets.only( left: 80.h, top: 12.v, bottom: 9.v), ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostCreateScreen, + ), + child: Icon(Icons.create_outlined)), + ) ], ), ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 25a9037b..175cd9f9 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -27,10 +27,13 @@ import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen.dart'; +import 'package:eatvio_app/presentation/diets/diet_edit_screen/diet_edit_screen.dart'; +import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart'; class AppRoutes { // REGISTRATION @@ -93,6 +96,8 @@ class AppRoutes { static const String blogPostEditScreen = '/blogPostEdit'; + static const String blogPostCreateScreen = '/blogPostCreate'; + static const String recipeSelectScreen = '/recipeSelect'; static const String recipeScreen = '/recipe'; @@ -101,6 +106,10 @@ class AppRoutes { static const String dietScreen = '/diet'; + static const String dietEditScreen = 'dietEdit'; + + static const String dietCreateScreen = '/dietCreate'; + static const String planSelectScreen = '/planSelect'; static const String planScreen = '/plan'; @@ -139,5 +148,8 @@ class AppRoutes { planScreen: PlanScreen.builder, blogPostScreen: BlogPostScreen.builder, blogPostEditScreen: BlogPostEditScreen.builder, + blogPostCreateScreen: BlogPostCreateScreen.builder, + dietEditScreen: DietEditScreen.builder, + dietCreateScreen: DietCreateScreen.builder, }; } From fb2ebe5a70bfef033d4f49d6910507f51ebf1f2e Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 13 May 2024 17:15:07 +0300 Subject: [PATCH 11/15] feat(plans) Plans create and edit API connect, TextController management and redirect --- .../bloc/plan_create_bloc.dart | 93 ++++++++++++++ .../bloc/plan_create_event.dart | 21 ++++ .../bloc/plan_create_state.dart | 65 ++++++++++ .../models/plan_create_model.dart | 19 +++ .../plan_create_screen.dart | 99 +++++++++++++++ .../plan_edit_screen/bloc/plan_edit_bloc.dart | 118 ++++++++++++++++++ .../bloc/plan_edit_event.dart | 21 ++++ .../bloc/plan_edit_state.dart | 64 ++++++++++ .../models/plan_edit_model.dart | 19 +++ .../plan_edit_screen/plan_edit_screen.dart | 114 +++++++++++++++++ .../plan_select_screen.dart | 17 +++ lib/routes/app_routes.dart | 10 +- 12 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart create mode 100644 lib/presentation/plans/plan_create_screen/bloc/plan_create_event.dart create mode 100644 lib/presentation/plans/plan_create_screen/bloc/plan_create_state.dart create mode 100644 lib/presentation/plans/plan_create_screen/models/plan_create_model.dart create mode 100644 lib/presentation/plans/plan_create_screen/plan_create_screen.dart create mode 100644 lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart create mode 100644 lib/presentation/plans/plan_edit_screen/bloc/plan_edit_event.dart create mode 100644 lib/presentation/plans/plan_edit_screen/bloc/plan_edit_state.dart create mode 100644 lib/presentation/plans/plan_edit_screen/models/plan_edit_model.dart create mode 100644 lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart diff --git a/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart b/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart new file mode 100644 index 00000000..b06092c3 --- /dev/null +++ b/lib/presentation/plans/plan_create_screen/bloc/plan_create_bloc.dart @@ -0,0 +1,93 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/plans/plan_create_screen/models/plan_create_model.dart'; +part 'plan_create_event.dart'; +part 'plan_create_state.dart'; + +/// A bloc that manages the state of a PlanCreate according to the event that is dispatched to it. +class PlanCreateBloc extends Bloc { + PlanCreateBloc(PlanCreateState initialState) : super(initialState) { + on(_onInitialize); + on(_createPlan); + } + + _onInitialize( + PlanCreateInitialEvent event, + Emitter emit, + ) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + if (state is PlanCreateLoadedState) { + var currentState = state as PlanCreateLoadedState; + emit(currentState.copyWith( + planModelObj: currentState.planModelObj, + name: currentState.name, + details: currentState.details, + )); + } else { + emit(PlanCreateLoadedState( + name: TextEditingController(), + details: TextEditingController(), + )); + } + } + + FutureOr _createPlan( + CreatePlan event, Emitter emit) async { + if (state is PlanCreateLoadedState) { + var currentState = state as PlanCreateLoadedState; + + var response = await sendPlan( + currentState.name?.text, + currentState.details?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.planSelectScreen); + } else { + print( + 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendPlan( + String? name, + String? details, + ) async { + String url = '${dotenv.env['SERVER_ROOT']!}/plans/create'; + + final Map credentials = { + 'name': name, + 'details': details, + 'duration': '14', + 'state': 'active', + 'type': '{"meat": true}', + 'daily_schedule': + '{"1": {"lunch": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 494}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 12}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 432}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 195}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 710}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "grams", "quantity": 119}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 287}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 445}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 500}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 764}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 735}], "breakfast": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 127}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 89}]}, "2": {"lunch": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 436}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 700}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 766}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 194}], "dinner": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 426}], "snacks": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 58}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 152}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 925}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 224}], "breakfast": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 918}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 81}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 308}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 579}]}, "3": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 266}], "dinner": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 790}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 26}], "snacks": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 549}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 446}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 406}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 92}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 403}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 894}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 457}]}, "4": {"lunch": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 698}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 43}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 994}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 965}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 42}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 948}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 28}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 54}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 112}], "supper": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 891}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 538}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 635}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 586}]}, "5": {"lunch": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 900}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 242}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 375}], "snacks": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 604}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 510}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 887}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 554}], "supper": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 731}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 574}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 274}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 276}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 80}]}, "6": {"lunch": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 359}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 358}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 639}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 861}], "dinner": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 124}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 122}], "snacks": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 980}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 837}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 444}], "supper": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 577}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 706}], "breakfast": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 839}]}, "7": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 118}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 664}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 665}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 986}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 296}], "snacks": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 807}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 493}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 227}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 385}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 448}], "breakfast": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 784}]}, "8": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 433}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 871}], "dinner": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 332}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 41}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 388}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 649}], "supper": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 114}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "milliliters", "quantity": 968}], "breakfast": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 180}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 269}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 998}]}, "9": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 68}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 505}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 554}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 579}], "snacks": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 751}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 144}], "supper": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 677}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 263}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 304}], "breakfast": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 963}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 258}]}, "10": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 167}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 306}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 243}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 79}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 560}], "snacks": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 294}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 145}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 639}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 108}], "supper": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 924}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 894}], "breakfast": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 280}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 336}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 338}]}, "11": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 817}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 968}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 525}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 864}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "pcs", "quantity": 457}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 548}], "snacks": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 658}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 239}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 358}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 406}], "supper": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "grams", "quantity": 416}], "breakfast": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 220}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 323}]}, "12": {"lunch": [{"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 377}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 138}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 654}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 237}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 902}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 76}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 723}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 481}], "breakfast": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 571}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 515}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 188}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 411}]}, "13": {"lunch": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 379}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "grams", "quantity": 231}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 376}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 147}], "dinner": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 871}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 679}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 359}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 561}], "snacks": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 414}], "supper": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 827}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 649}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 363}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 161}], "breakfast": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 185}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 464}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 387}]}, "14": {"lunch": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 248}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 5}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 693}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 655}], "dinner": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 709}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 881}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 588}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 36}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "pcs", "quantity": 453}], "supper": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 692}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 186}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 721}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 740}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 731}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 842}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 850}]}, "15": {"lunch": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "grams", "quantity": 124}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 380}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 211}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 689}], "dinner": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 326}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 629}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 201}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 129}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 749}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "milliliters", "quantity": 315}], "breakfast": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 444}]}, "16": {"lunch": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 953}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 101}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 684}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "pcs", "quantity": 506}], "dinner": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 333}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 416}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "milliliters", "quantity": 796}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 799}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 799}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "pcs", "quantity": 756}], "supper": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 858}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 619}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 800}], "breakfast": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "pcs", "quantity": 363}, {"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "grams", "quantity": 539}]}, "17": {"lunch": [{"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 134}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 977}], "snacks": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "pcs", "quantity": 211}], "supper": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 209}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "grams", "quantity": 227}], "breakfast": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 195}, {"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "pcs", "quantity": 917}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 706}]}, "18": {"lunch": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "pcs", "quantity": 296}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 239}, {"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 798}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 155}], "dinner": [{"name": "Little Hamburger", "uniq": "Little Hamburger-1709115623", "portion": "grams", "quantity": 959}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 958}], "snacks": [{"name": "Pasta", "uniq": "Pasta-1709115623", "portion": "milliliters", "quantity": 984}], "supper": [{"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 134}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 432}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "milliliters", "quantity": 242}]}, "19": {"lunch": [{"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 906}, {"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 152}], "dinner": [{"name": "Cheeseburger", "uniq": "Cheeseburger-1709115623", "portion": "milliliters", "quantity": 610}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 605}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "grams", "quantity": 498}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 616}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 512}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 146}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 200}], "supper": [{"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 764}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 353}, {"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 181}], "breakfast": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 783}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 506}, {"name": "Bacon Cheeseburger", "uniq": "Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 887}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 130}]}, "20": {"lunch": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 48}], "dinner": [{"name": "Bacon Dog", "uniq": "Bacon Dog-1709115623", "portion": "milliliters", "quantity": 266}, {"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 143}], "snacks": [{"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "grams", "quantity": 55}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "grams", "quantity": 174}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 855}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "milliliters", "quantity": 6}], "supper": [{"name": "Grilled Cheese", "uniq": "Grilled Cheese-1709115623", "portion": "pcs", "quantity": 296}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 878}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "pcs", "quantity": 376}], "breakfast": [{"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "milliliters", "quantity": 442}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "pcs", "quantity": 528}, {"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "milliliters", "quantity": 558}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 885}]}, "21": {"lunch": [{"name": "Little Bacon Burger", "uniq": "Little Bacon Burger-1709115623", "portion": "grams", "quantity": 504}, {"name": "Little Bacon Cheeseburger", "uniq": "Little Bacon Cheeseburger-1709115623", "portion": "milliliters", "quantity": 652}, {"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 996}], "dinner": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "milliliters", "quantity": 200}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "pcs", "quantity": 641}, {"name": "Cheese Veggie Sandwich", "uniq": "Cheese Veggie Sandwich-1709115623", "portion": "milliliters", "quantity": 87}, {"name": "Cheese Dog", "uniq": "Cheese Dog-1709115623", "portion": "pcs", "quantity": 689}], "snacks": [{"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 143}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "pcs", "quantity": 528}, {"name": "Veggie Sandwich", "uniq": "Veggie Sandwich-1709115623", "portion": "grams", "quantity": 475}], "supper": [{"name": "Little Cheeseburger", "uniq": "Little Cheeseburger-1709115623", "portion": "grams", "quantity": 924}, {"name": "Hamburger", "uniq": "Hamburger-1709115623", "portion": "grams", "quantity": 98}], "breakfast": [{"name": "Bacon Cheese Dog", "uniq": "Bacon Cheese Dog-1709115623", "portion": "grams", "quantity": 438}, {"name": "Cheese Pizza", "uniq": "Cheese Pizza-1709115623", "portion": "milliliters", "quantity": 383}, {"name": "Bacon Burger", "uniq": "Bacon Burger-1709115623", "portion": "grams", "quantity": 462}]}}' + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/plans/plan_create_screen/bloc/plan_create_event.dart b/lib/presentation/plans/plan_create_screen/bloc/plan_create_event.dart new file mode 100644 index 00000000..0bcfb740 --- /dev/null +++ b/lib/presentation/plans/plan_create_screen/bloc/plan_create_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_create_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///PlanCreate widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class PlanCreateEvent extends Equatable {} + +/// Event that is dispatched when the PlanCreate widget is first created. +class PlanCreateInitialEvent extends PlanCreateEvent { + @override + List get props => []; +} + +class CreatePlan extends PlanCreateEvent { + @override + List get props => []; +} diff --git a/lib/presentation/plans/plan_create_screen/bloc/plan_create_state.dart b/lib/presentation/plans/plan_create_screen/bloc/plan_create_state.dart new file mode 100644 index 00000000..93e4aaf2 --- /dev/null +++ b/lib/presentation/plans/plan_create_screen/bloc/plan_create_state.dart @@ -0,0 +1,65 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_create_bloc.dart'; + +/// Represents the state of PlanCreate in the application. +/// + +class PlanCreateState extends Equatable { + PlanCreateState({ + this.planModelObj, + }); + + PlanCreateModel? planModelObj; + + @override + List get props => [planModelObj]; + PlanCreateState copyWith({PlanCreateModel? planModelObj}) { + return PlanCreateState( + planModelObj: planModelObj ?? this.planModelObj, + ); + } +} + +class PlanCreateLoadedState extends PlanCreateState { + PlanCreateLoadedState({ + this.planModelObj, + this.name, + this.details, + }); + + PlanCreateModel? planModelObj; + TextEditingController? name; + TextEditingController? details; + @override + List get props => [ + planModelObj, + name, + details, + ]; + PlanCreateLoadedState copyWith({ + PlanCreateModel? planModelObj, + TextEditingController? name, + TextEditingController? details, + }) { + return PlanCreateLoadedState( + planModelObj: planModelObj ?? this.planModelObj, + name: name ?? this.name, + details: details ?? this.details, + ); + } +} + +class PlanCreateErrorState extends PlanCreateState { + final String errorMessage; + + PlanCreateErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class PlanCreateInitialState extends PlanCreateState { + PlanCreateInitialState({required PlanCreateModel planModelObj}) + : super(planModelObj: planModelObj); +} diff --git a/lib/presentation/plans/plan_create_screen/models/plan_create_model.dart b/lib/presentation/plans/plan_create_screen/models/plan_create_model.dart new file mode 100644 index 00000000..b105414c --- /dev/null +++ b/lib/presentation/plans/plan_create_screen/models/plan_create_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [plan_create_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class PlanCreateModel extends Equatable { + final String planID; + + PlanCreateModel({required String planID}) : planID = planID; + + PlanCreateModel copyWith(String planID) { + return PlanCreateModel(planID: planID); + } + + @override + List get props => [planID]; +} diff --git a/lib/presentation/plans/plan_create_screen/plan_create_screen.dart b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart new file mode 100644 index 00000000..97e1fd7f --- /dev/null +++ b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart @@ -0,0 +1,99 @@ +import 'bloc/plan_create_bloc.dart'; +import 'models/plan_create_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class PlanCreateScreen extends StatelessWidget { + const PlanCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final planID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => PlanCreateBloc(PlanCreateState( + planModelObj: PlanCreateModel(planID: planID.toString()))) + ..add(PlanCreateInitialEvent()), + child: PlanCreateScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is PlanCreateInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is PlanCreateLoadedState) { + // Данные загружены, можно строить экран. + return _buildPlanCreate(context, state); + } else if (state is PlanCreateErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildPlanCreate(BuildContext context, PlanCreateLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(CreatePlan()), + child: Text('Создать'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart new file mode 100644 index 00000000..e8fa9eae --- /dev/null +++ b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_bloc.dart @@ -0,0 +1,118 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/plans/plan_edit_screen/models/plan_edit_model.dart'; +part 'plan_edit_event.dart'; +part 'plan_edit_state.dart'; + +/// A bloc that manages the state of a PlanEdit according to the event that is dispatched to it. +class PlanEditBloc extends Bloc { + PlanEditBloc(PlanEditState initialState) : super(initialState) { + on(_onInitialize); + on(_editPlan); + } + + _onInitialize( + PlanEditInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? planID = state.planModelObj?.planID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/show/$planID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is PlanEditLoadedState) { + var currentState = state as PlanEditLoadedState; + emit(currentState.copyWith( + planModelObj: currentState.planModelObj, + plan: currentState.plan, + name: currentState.name, + duration: currentState.duration, + details: currentState.details)); + } else { + emit(PlanEditLoadedState( + plan: data, + name: TextEditingController(text: data['name']), + details: TextEditingController(text: data['details']), + duration: + TextEditingController(text: data['duration'].toString()))); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(PlanEditErrorState('Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(PlanEditErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(PlanEditErrorState('Ошибка при получении данных о чатах: $e')); + } + } + + FutureOr _editPlan(EditPlan event, Emitter emit) async { + if (state is PlanEditLoadedState) { + var currentState = state as PlanEditLoadedState; + int duration = int.parse(currentState.duration!.text) * 7; + print(duration); + var response = await sendPlan(currentState.plan, currentState.name?.text, + currentState.details?.text, duration.toString()); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.planScreen, + arguments: currentState.plan?['id'].toString()); + } else { + print( + 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendPlan( + Map? plan, + String? name, + String? details, + String? duration, + ) async { + int id = plan?['id']; + String url = '${dotenv.env['SERVER_ROOT']!}/plans/update/$id'; + + final Map credentials = { + 'id': id, + 'name': name, + 'details': details, + 'duration': '14', + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_event.dart b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_event.dart new file mode 100644 index 00000000..2596b8aa --- /dev/null +++ b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_edit_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///PlanEdit widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class PlanEditEvent extends Equatable {} + +/// Event that is dispatched when the PlanEdit widget is first created. +class PlanEditInitialEvent extends PlanEditEvent { + @override + List get props => []; +} + +class EditPlan extends PlanEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_state.dart b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_state.dart new file mode 100644 index 00000000..427bf0df --- /dev/null +++ b/lib/presentation/plans/plan_edit_screen/bloc/plan_edit_state.dart @@ -0,0 +1,64 @@ +// ignore_for_file: must_be_immutable + +part of 'plan_edit_bloc.dart'; + +/// Represents the state of PlanEdit in the application. +/// + +class PlanEditState extends Equatable { + PlanEditState({ + this.planModelObj, + }); + + PlanEditModel? planModelObj; + + @override + List get props => [planModelObj]; + PlanEditState copyWith({PlanEditModel? planModelObj}) { + return PlanEditState( + planModelObj: planModelObj ?? this.planModelObj, + ); + } +} + +class PlanEditLoadedState extends PlanEditState { + PlanEditLoadedState( + {this.planModelObj, this.plan, this.name, this.details, this.duration}); + + PlanEditModel? planModelObj; + Map? plan; + TextEditingController? name; + TextEditingController? details; + TextEditingController? duration; + @override + List get props => [planModelObj, plan, name, details, duration]; + PlanEditLoadedState copyWith({ + PlanEditModel? planModelObj, + Map? plan, + TextEditingController? name, + TextEditingController? details, + TextEditingController? duration, + }) { + return PlanEditLoadedState( + planModelObj: planModelObj ?? this.planModelObj, + plan: plan ?? this.plan, + name: name ?? this.name, + details: details ?? this.details, + duration: duration ?? this.duration, + ); + } +} + +class PlanEditErrorState extends PlanEditState { + final String errorMessage; + + PlanEditErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class PlanEditInitialState extends PlanEditState { + PlanEditInitialState({required PlanEditModel planModelObj}) + : super(planModelObj: planModelObj); +} diff --git a/lib/presentation/plans/plan_edit_screen/models/plan_edit_model.dart b/lib/presentation/plans/plan_edit_screen/models/plan_edit_model.dart new file mode 100644 index 00000000..22c3d277 --- /dev/null +++ b/lib/presentation/plans/plan_edit_screen/models/plan_edit_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [plan_edit_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class PlanEditModel extends Equatable { + final String planID; + + PlanEditModel({required String planID}) : planID = planID; + + PlanEditModel copyWith(String planID) { + return PlanEditModel(planID: planID); + } + + @override + List get props => [planID]; +} diff --git a/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart b/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart new file mode 100644 index 00000000..71ac12b7 --- /dev/null +++ b/lib/presentation/plans/plan_edit_screen/plan_edit_screen.dart @@ -0,0 +1,114 @@ +import 'bloc/plan_edit_bloc.dart'; +import 'models/plan_edit_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class PlanEditScreen extends StatelessWidget { + const PlanEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final planID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => PlanEditBloc(PlanEditState( + planModelObj: PlanEditModel(planID: planID.toString()))) + ..add(PlanEditInitialEvent()), + child: PlanEditScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is PlanEditInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is PlanEditLoadedState) { + // Данные загружены, можно строить экран. + return _buildPlanEdit(context, state); + } else if (state is PlanEditErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildPlanEdit(BuildContext context, PlanEditLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.plan?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.name, + decoration: InputDecoration(labelText: 'Заголовок'), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.details, + decoration: InputDecoration(labelText: 'Описание'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(EditPlan()), + child: Text('Сохранить'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart index 93268863..bb9862b4 100644 --- a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart +++ b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart @@ -86,6 +86,16 @@ class PlanSelectScreen extends StatelessWidget { margin: EdgeInsets.only( left: 80.h, top: 12.v, bottom: 9.v), ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.planCreateScreen, + ), + child: Icon(Icons.create_outlined)), + ) ], ), ), @@ -140,6 +150,13 @@ class PlanSelectScreen extends StatelessWidget { AppRoutes.planScreen, arguments: post?['id'].toString(), ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.planEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), )); }, ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 175cd9f9..77362cbf 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -26,6 +26,8 @@ import 'package:eatvio_app/presentation/chat/chat_select_screen/chat_select_scre import 'package:eatvio_app/presentation/feed/feed_screen/feed_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_select_screen/recipe_select_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_select_screen/plan_select_screen.dart'; +import 'package:eatvio_app/presentation/plans/plan_edit_screen/plan_edit_screen.dart'; +import 'package:eatvio_app/presentation/plans/plan_create_screen/plan_create_screen.dart'; import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen.dart'; import 'package:eatvio_app/presentation/diets/diet_edit_screen/diet_edit_screen.dart'; import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart'; @@ -106,7 +108,7 @@ class AppRoutes { static const String dietScreen = '/diet'; - static const String dietEditScreen = 'dietEdit'; + static const String dietEditScreen = '/dietEdit'; static const String dietCreateScreen = '/dietCreate'; @@ -114,6 +116,10 @@ class AppRoutes { static const String planScreen = '/plan'; + static const String planEditScreen = '/planEdit'; + + static const String planCreateScreen = '/planCreate'; + static Map get routes => { dateOfBirthScreen: DateOfBirthScreen.builder, cityOfLivingScreen: CityOfLivingScreen.builder, @@ -146,6 +152,8 @@ class AppRoutes { recipeScreen: RecipeScreen.builder, dietScreen: DietScreen.builder, planScreen: PlanScreen.builder, + planEditScreen: PlanEditScreen.builder, + planCreateScreen: PlanCreateScreen.builder, blogPostScreen: BlogPostScreen.builder, blogPostEditScreen: BlogPostEditScreen.builder, blogPostCreateScreen: BlogPostCreateScreen.builder, From a32117953d61bce228feab0c10f9abbd3af9af69 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 14 May 2024 11:05:31 +0300 Subject: [PATCH 12/15] feat(recipe): Layout state and navigation for Recipe create & update --- .../bloc/recipe_create_bloc.dart | 92 +++++++++++++ .../bloc/recipe_create_event.dart | 21 +++ .../bloc/recipe_create_state.dart | 61 +++++++++ .../models/recipe_create_model.dart | 19 +++ .../recipe_create_screen.dart | 104 +++++++++++++++ .../bloc/recipe_edit_bloc.dart | 122 ++++++++++++++++++ .../bloc/recipe_edit_event.dart | 21 +++ .../bloc/recipe_edit_state.dart | 69 ++++++++++ .../models/recipe_edit_model.dart | 19 +++ .../recipe_edit_screen.dart | 119 +++++++++++++++++ lib/routes/app_routes.dart | 18 ++- 11 files changed, 660 insertions(+), 5 deletions(-) create mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart create mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart create mode 100644 lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart create mode 100644 lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart create mode 100644 lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart create mode 100644 lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart create mode 100644 lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart create mode 100644 lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart create mode 100644 lib/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart create mode 100644 lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart 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 new file mode 100644 index 00000000..fa38bae4 --- /dev/null +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_bloc.dart @@ -0,0 +1,92 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart'; +part 'recipe_create_event.dart'; +part 'recipe_create_state.dart'; + +/// A bloc that manages the state of a RecipeCreate according to the event that is dispatched to it. +class RecipeCreateBloc extends Bloc { + RecipeCreateBloc(RecipeCreateState initialState) : super(initialState) { + on(_onInitialize); + on(_createRecipe); + } + + _onInitialize( + RecipeCreateInitialEvent event, + Emitter emit, + ) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + if (state is RecipeCreateLoadedState) { + var currentState = state as RecipeCreateLoadedState; + emit(currentState.copyWith( + recipeModelObj: currentState.recipeModelObj, + title: currentState.title, + description: currentState.description, + youtube_url: currentState.youtube_url)); + } else { + emit(RecipeCreateLoadedState( + title: TextEditingController(), + description: TextEditingController(), + youtube_url: TextEditingController())); + } + } + + FutureOr _createRecipe( + CreateRecipe event, Emitter emit) async { + if (state is RecipeCreateLoadedState) { + var currentState = state as RecipeCreateLoadedState; + + var response = await sendRecipe( + currentState.title?.text, + currentState.youtube_url?.text, + currentState.description?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.recipeScreen); + } else { + print( + 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendRecipe( + String? title, + String? youtube_url, + String? description, + ) async { + String url = '${dotenv.env['SERVER_ROOT']!}/recipe/create'; + + final Map credentials = { + 'title': title, + 'description': description, + 'youtube_url': youtube_url, + 'status': 'active', + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart new file mode 100644 index 00000000..fcea8078 --- /dev/null +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_create_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///RecipeCreate widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class RecipeCreateEvent extends Equatable {} + +/// Event that is dispatched when the RecipeCreate widget is first created. +class RecipeCreateInitialEvent extends RecipeCreateEvent { + @override + List get props => []; +} + +class CreateRecipe extends RecipeCreateEvent { + @override + List get props => []; +} 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 new file mode 100644 index 00000000..59e5ddf8 --- /dev/null +++ b/lib/presentation/recipe/recipe_create_screen/bloc/recipe_create_state.dart @@ -0,0 +1,61 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_create_bloc.dart'; + +/// Represents the state of RecipeCreate in the application. +/// + +class RecipeCreateState extends Equatable { + RecipeCreateState({ + this.recipeModelObj, + }); + + RecipeCreateModel? recipeModelObj; + + @override + List get props => [recipeModelObj]; + RecipeCreateState copyWith({RecipeCreateModel? recipeModelObj}) { + return RecipeCreateState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + ); + } +} + +class RecipeCreateLoadedState extends RecipeCreateState { + RecipeCreateLoadedState( + {this.recipeModelObj, this.title, this.description, this.youtube_url}); + + RecipeCreateModel? recipeModelObj; + TextEditingController? title; + TextEditingController? description; + TextEditingController? youtube_url; + @override + List get props => [recipeModelObj, title, description, youtube_url]; + RecipeCreateLoadedState copyWith({ + RecipeCreateModel? recipeModelObj, + TextEditingController? title, + TextEditingController? description, + TextEditingController? youtube_url, + }) { + return RecipeCreateLoadedState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + title: title ?? this.title, + description: description ?? this.description, + youtube_url: youtube_url ?? this.youtube_url, + ); + } +} + +class RecipeCreateErrorState extends RecipeCreateState { + final String errorMessage; + + RecipeCreateErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class RecipeCreateInitialState extends RecipeCreateState { + RecipeCreateInitialState({required RecipeCreateModel recipeModelObj}) + : super(recipeModelObj: recipeModelObj); +} diff --git a/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart b/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart new file mode 100644 index 00000000..1883aa86 --- /dev/null +++ b/lib/presentation/recipe/recipe_create_screen/models/recipe_create_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [recipe_create_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class RecipeCreateModel extends Equatable { + final String recipeID; + + RecipeCreateModel({required String recipeID}) : recipeID = recipeID; + + RecipeCreateModel copyWith(String recipeID) { + return RecipeCreateModel(recipeID: recipeID); + } + + @override + List get props => [recipeID]; +} diff --git a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart new file mode 100644 index 00000000..1bfc592c --- /dev/null +++ b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart @@ -0,0 +1,104 @@ +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/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class RecipeCreateScreen extends StatelessWidget { + const RecipeCreateScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final recipeID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => RecipeCreateBloc(RecipeCreateState( + recipeModelObj: RecipeCreateModel(recipeID: recipeID.toString()))) + ..add(RecipeCreateInitialEvent()), + child: RecipeCreateScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is RecipeCreateInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is RecipeCreateLoadedState) { + // Данные загружены, можно строить экран. + return _buildRecipeCreate(context, state); + } else if (state is RecipeCreateErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildRecipeCreate(BuildContext context, RecipeCreateLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(CreateRecipe()), + child: Text('Создать'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart new file mode 100644 index 00000000..3d1ccfa1 --- /dev/null +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart @@ -0,0 +1,122 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart'; +part 'recipe_edit_event.dart'; +part 'recipe_edit_state.dart'; + +/// A bloc that manages the state of a RecipeEdit according to the event that is dispatched to it. +class RecipeEditBloc extends Bloc { + RecipeEditBloc(RecipeEditState initialState) : super(initialState) { + on(_onInitialize); + on(_editRecipe); + } + + _onInitialize( + RecipeEditInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? recipeID = state.recipeModelObj?.recipeID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/show/$recipeID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is RecipeEditLoadedState) { + var currentState = state as RecipeEditLoadedState; + emit(currentState.copyWith( + recipeModelObj: currentState.recipeModelObj, + recipe: currentState.recipe, + title: currentState.title, + description: currentState.description, + youtube_url: currentState.youtube_url)); + } else { + emit(RecipeEditLoadedState( + recipe: data, + title: TextEditingController(text: data['title']), + description: TextEditingController(text: data['description']), + youtube_url: TextEditingController(text: data['youtube_url']))); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(RecipeEditErrorState( + 'Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(RecipeEditErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(RecipeEditErrorState('Ошибка при получении данных о чатах: $e')); + } + } + + FutureOr _editRecipe( + EditRecipe event, Emitter emit) async { + if (state is RecipeEditLoadedState) { + var currentState = state as RecipeEditLoadedState; + + var response = await sendRecipe( + currentState.recipe, + currentState.title?.text, + currentState.youtube_url?.text, + currentState.description?.text, + ); + + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.blogPostScreen, + arguments: currentState.recipe?['id'].toString()); + } else { + print( + 'Failed to edit blog post. Status Code: ${response.statusCode} ${response.body}'); + } + } + } + + FutureOr sendRecipe( + Map? recipe, + String? title, + String? youtube_url, + String? description, + ) async { + int id = recipe?['id']; + String url = '${dotenv.env['SERVER_ROOT']!}/recipe/update/$id'; + + final Map credentials = { + 'id': id, + 'title': title, + 'description': description, + 'youtube_url': youtube_url, + }; + + var response = await http.post( + Uri.parse(url), + headers: { + 'Content-Type': 'application/json', + }, + body: json.encode(credentials), + encoding: utf8, + ); + + return response; + } +} diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart new file mode 100644 index 00000000..bcbe219c --- /dev/null +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_event.dart @@ -0,0 +1,21 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_edit_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///RecipeEdit widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class RecipeEditEvent extends Equatable {} + +/// Event that is dispatched when the RecipeEdit widget is first created. +class RecipeEditInitialEvent extends RecipeEditEvent { + @override + List get props => []; +} + +class EditRecipe extends RecipeEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart new file mode 100644 index 00000000..b13bbe53 --- /dev/null +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart @@ -0,0 +1,69 @@ +// ignore_for_file: must_be_immutable + +part of 'recipe_edit_bloc.dart'; + +/// Represents the state of RecipeEdit in the application. +/// + +class RecipeEditState extends Equatable { + RecipeEditState({ + this.recipeModelObj, + }); + + RecipeEditModel? recipeModelObj; + + @override + List get props => [recipeModelObj]; + RecipeEditState copyWith({RecipeEditModel? recipeModelObj}) { + return RecipeEditState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + ); + } +} + +class RecipeEditLoadedState extends RecipeEditState { + RecipeEditLoadedState( + {this.recipeModelObj, + this.recipe, + this.title, + this.description, + this.youtube_url}); + + RecipeEditModel? recipeModelObj; + Map? recipe; + TextEditingController? title; + TextEditingController? description; + TextEditingController? youtube_url; + @override + List get props => + [recipeModelObj, recipe, title, description, youtube_url]; + RecipeEditLoadedState copyWith({ + RecipeEditModel? recipeModelObj, + Map? recipe, + TextEditingController? title, + TextEditingController? description, + TextEditingController? youtube_url, + }) { + return RecipeEditLoadedState( + recipeModelObj: recipeModelObj ?? this.recipeModelObj, + recipe: recipe ?? this.recipe, + title: title ?? this.title, + description: description ?? this.description, + youtube_url: youtube_url ?? this.youtube_url, + ); + } +} + +class RecipeEditErrorState extends RecipeEditState { + final String errorMessage; + + RecipeEditErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class RecipeEditInitialState extends RecipeEditState { + RecipeEditInitialState({required RecipeEditModel recipeModelObj}) + : super(recipeModelObj: recipeModelObj); +} diff --git a/lib/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart b/lib/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart new file mode 100644 index 00000000..72176186 --- /dev/null +++ b/lib/presentation/recipe/recipe_edit_screen/models/recipe_edit_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [recipe_edit_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class RecipeEditModel extends Equatable { + final String recipeID; + + RecipeEditModel({required String recipeID}) : recipeID = recipeID; + + RecipeEditModel copyWith(String recipeID) { + return RecipeEditModel(recipeID: recipeID); + } + + @override + List get props => [recipeID]; +} diff --git a/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart new file mode 100644 index 00000000..259b75b5 --- /dev/null +++ b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart @@ -0,0 +1,119 @@ +import 'bloc/recipe_edit_bloc.dart'; +import 'models/recipe_edit_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class RecipeEditScreen extends StatelessWidget { + const RecipeEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final recipeID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => RecipeEditBloc(RecipeEditState( + recipeModelObj: RecipeEditModel(recipeID: recipeID.toString()))) + ..add(RecipeEditInitialEvent()), + child: RecipeEditScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is RecipeEditInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is RecipeEditLoadedState) { + // Данные загружены, можно строить экран. + return _buildRecipeEdit(context, state); + } else if (state is RecipeEditErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildRecipeEdit(BuildContext context, RecipeEditLoadedState state) { + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + 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: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.recipe?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + 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.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () => + context.read().add(EditRecipe()), + child: Text('Сохранить'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 77362cbf..7163f5e5 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -33,6 +33,8 @@ import 'package:eatvio_app/presentation/diets/diet_edit_screen/diet_edit_screen. import 'package:eatvio_app/presentation/diets/diet_create_screen/diet_create_screen.dart'; import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_create_screen/recipe_create_screen.dart'; +import 'package:eatvio_app/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart'; @@ -104,6 +106,10 @@ class AppRoutes { static const String recipeScreen = '/recipe'; + static const String recipeEditScreen = '/recipeEdit'; + + static const String recipeCreateScreen = '/recipeCreate'; + static const String dietSelectScreen = '/dietSelect'; static const String dietScreen = '/diet'; @@ -145,19 +151,21 @@ class AppRoutes { diaryEdit: DiaryEditScreen.builder, chatScreen: ChatScreen.builder, chatSelectScreen: ChatSelectScreen.builder, - feedScreen: FeedScreen.builder, recipeSelectScreen: RecipeSelectScreen.builder, - dietSelectScreen: DietSelectScreen.builder, - planSelectScreen: PlanSelectScreen.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, - dietEditScreen: DietEditScreen.builder, - dietCreateScreen: DietCreateScreen.builder, }; } From a440d31ab6f4efa863a530820d2f52ef7e2e2b93 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 14 May 2024 12:14:33 +0300 Subject: [PATCH 13/15] feat(recipe): Main menu buttons and API implementtion --- .../bloc/recipe_create_bloc.dart | 12 ++++++------ .../bloc/recipe_create_state.dart | 10 +++++----- .../recipe_create_screen.dart | 2 +- .../bloc/recipe_edit_bloc.dart | 16 ++++++++++------ .../bloc/recipe_edit_state.dart | 10 +++++----- .../recipe_edit_screen/recipe_edit_screen.dart | 2 +- .../recipe_select_screen.dart | 18 +++++++++++++++++- .../recipe_show_screen/recipe_show_screen.dart | 6 +++--- 8 files changed, 48 insertions(+), 28 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 fa38bae4..d438c245 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,12 @@ class RecipeCreateBloc extends Bloc { recipeModelObj: currentState.recipeModelObj, title: currentState.title, description: currentState.description, - youtube_url: currentState.youtube_url)); + video_url: currentState.video_url)); } else { emit(RecipeCreateLoadedState( title: TextEditingController(), description: TextEditingController(), - youtube_url: TextEditingController())); + video_url: TextEditingController())); } } @@ -51,12 +51,12 @@ class RecipeCreateBloc extends Bloc { var response = await sendRecipe( currentState.title?.text, - currentState.youtube_url?.text, + currentState.video_url?.text, currentState.description?.text, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.recipeScreen); + NavigatorService.pushNamed(AppRoutes.recipeSelectScreen); } else { print( 'Failed to create blog post. Status Code: ${response.statusCode} ${response.body}'); @@ -66,7 +66,7 @@ class RecipeCreateBloc extends Bloc { FutureOr sendRecipe( String? title, - String? youtube_url, + String? video_url, String? description, ) async { String url = '${dotenv.env['SERVER_ROOT']!}/recipe/create'; @@ -74,7 +74,7 @@ class RecipeCreateBloc extends Bloc { final Map credentials = { 'title': title, 'description': description, - 'youtube_url': youtube_url, + 'video_url': video_url, 'status': 'active', }; 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 59e5ddf8..cf5b12e3 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,25 @@ class RecipeCreateState extends Equatable { class RecipeCreateLoadedState extends RecipeCreateState { RecipeCreateLoadedState( - {this.recipeModelObj, this.title, this.description, this.youtube_url}); + {this.recipeModelObj, this.title, this.description, this.video_url}); RecipeCreateModel? recipeModelObj; TextEditingController? title; TextEditingController? description; - TextEditingController? youtube_url; + TextEditingController? video_url; @override - List get props => [recipeModelObj, title, description, youtube_url]; + List get props => [recipeModelObj, title, description, video_url]; RecipeCreateLoadedState copyWith({ RecipeCreateModel? recipeModelObj, TextEditingController? title, TextEditingController? description, - TextEditingController? youtube_url, + TextEditingController? video_url, }) { return RecipeCreateLoadedState( recipeModelObj: recipeModelObj ?? this.recipeModelObj, title: title ?? this.title, description: description ?? this.description, - youtube_url: youtube_url ?? this.youtube_url, + video_url: video_url ?? this.video_url, ); } } 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 1bfc592c..ad4c7edf 100644 --- a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart +++ b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart @@ -81,7 +81,7 @@ class RecipeCreateScreen extends StatelessWidget { ), SizedBox(height: 16.0), TextFormField( - controller: state.youtube_url, + controller: state.video_url, decoration: InputDecoration(labelText: 'Ссылка'), ), SizedBox(height: 16.0), diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart index 3d1ccfa1..8cafde30 100644 --- a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_bloc.dart @@ -47,13 +47,13 @@ class RecipeEditBloc extends Bloc { recipe: currentState.recipe, title: currentState.title, description: currentState.description, - youtube_url: currentState.youtube_url)); + video_url: currentState.video_url)); } else { emit(RecipeEditLoadedState( recipe: data, title: TextEditingController(text: data['title']), description: TextEditingController(text: data['description']), - youtube_url: TextEditingController(text: data['youtube_url']))); + video_url: TextEditingController(text: data['video_url']))); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -78,12 +78,12 @@ class RecipeEditBloc extends Bloc { var response = await sendRecipe( currentState.recipe, currentState.title?.text, - currentState.youtube_url?.text, + currentState.video_url?.text, currentState.description?.text, ); if (response.statusCode == 200) { - NavigatorService.pushNamed(AppRoutes.blogPostScreen, + NavigatorService.pushNamed(AppRoutes.recipeScreen, arguments: currentState.recipe?['id'].toString()); } else { print( @@ -95,7 +95,7 @@ class RecipeEditBloc extends Bloc { FutureOr sendRecipe( Map? recipe, String? title, - String? youtube_url, + String? video_url, String? description, ) async { int id = recipe?['id']; @@ -105,7 +105,11 @@ class RecipeEditBloc extends Bloc { 'id': id, 'title': title, 'description': description, - 'youtube_url': youtube_url, + 'video_url': video_url, + 'steps': + '[{"image": "steps/bT91V52zY2hIkjZezUxO4jW4njfhDfCe2PcMxciZ.jpg", "description": "Excepturi fuga sed quaerat impedit facere recusandae. Nihil exercitationem architecto accusantium ut. Qui consequuntur rerum quam asperiores repellendus molestias."}, {"image": "steps/7vaLlotrw65NXbqaD0dK4OTLVyxMgmB9wL3xBzg3.jpg", "description": "Sit hic sint ullam qui. Odit nihil possimus quo id itaque nisi non."}, {"image": "steps/et7cKUsnQkegcRAE72TS0mfAf2jLJXW93QDAGfjz.jpg", "description": "Ab dolores qui voluptatem sed quasi vero. Culpa minus optio in voluptatibus aut corrupti. Dolor expedita minus aut nesciunt quos quibusdam rerum."}, {"image": "steps/260401J6r71r9kuUt694VcsuMMQKTlQMrlIwfaKo.jpg", "description": "Eos dolores corrupti cupiditate aut. Nam nesciunt eveniet eos nam vitae ut. Quibusdam ab commodi ut natus. Nisi earum porro quidem et eligendi iste doloribus."}, {"image": "steps/Ol10BkOFeZ9EG4BAShgB1wnVwtCimoUMcSNDh2EB.jpg", "description": "Consectetur optio porro nobis voluptas quibusdam. Consequatur dolor dolorem laboriosam magnam iste atque."}, {"image": "steps/8PanRq2aWWdXG1xCYkRyJ0M3Z39y6jMPfnHCqlVi.jpg", "description": "A optio porro eligendi ullam. Sed numquam ullam quasi aperiam inventore corrupti enim. Alias id et consequatur perferendis. Ullam consequatur incidunt nesciunt ut et aut."}, {"image": "steps/19U2sYSGDkprS3L8SIFT3xz59OwQJ5xs0A8etZjr.jpg", "description": "In doloribus vel voluptas dolorum. Temporibus iusto est repellat quo sed. Perspiciatis enim sit beatae in dolor ut. Quis ad qui eligendi nisi. Est suscipit dolore ratione dolores suscipit molestiae."}, {"image": "steps/CRxJtjgQw84kG3teJVtl9GqwWUQ1WKpvOeBbwf9a.jpg", "description": "Aut quia maiores et aut. Quidem ab voluptates blanditiis. Est quam quia repellat exercitationem rerum et iste mollitia. Et sed dolores tempore nam et similique qui."}, {"image": "steps/NoG9rHtvHNLrYI2LujURT3DMQCmEe68UbpiXco2y.jpg", "description": "Nam et dolorum delectus. Labore fuga asperiores nam cumque sit. Eum hic officiis ea asperiores qui. Soluta totam enim quasi explicabo sed aut."}]', + 'ingredients': + '[{"name": "Cheeseburger", "unit": "pcs", "portion": 336}, {"name": "Little Cheeseburger", "unit": "gr", "portion": 623}, {"name": "Bacon Burger", "unit": "ml", "portion": 713}, {"name": "Little Bacon Burger", "unit": "pcs", "portion": 160}, {"name": "Hamburger", "unit": "tablespoon", "portion": 390}, {"name": "Bacon Cheese Dog", "unit": "teespons", "portion": 295}, {"name": "Hamburger", "unit": "pcs", "portion": 813}, {"name": "Cheeseburger", "unit": "gr", "portion": 951}]' }; var response = await http.post( diff --git a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart index b13bbe53..ec001270 100644 --- a/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart +++ b/lib/presentation/recipe/recipe_edit_screen/bloc/recipe_edit_state.dart @@ -27,29 +27,29 @@ class RecipeEditLoadedState extends RecipeEditState { this.recipe, this.title, this.description, - this.youtube_url}); + this.video_url}); RecipeEditModel? recipeModelObj; Map? recipe; TextEditingController? title; TextEditingController? description; - TextEditingController? youtube_url; + TextEditingController? video_url; @override List get props => - [recipeModelObj, recipe, title, description, youtube_url]; + [recipeModelObj, recipe, title, description, video_url]; RecipeEditLoadedState copyWith({ RecipeEditModel? recipeModelObj, Map? recipe, TextEditingController? title, TextEditingController? description, - TextEditingController? youtube_url, + TextEditingController? video_url, }) { return RecipeEditLoadedState( recipeModelObj: recipeModelObj ?? this.recipeModelObj, recipe: recipe ?? this.recipe, title: title ?? this.title, description: description ?? this.description, - youtube_url: youtube_url ?? this.youtube_url, + video_url: video_url ?? this.video_url, ); } } diff --git a/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart index 259b75b5..31a723a9 100644 --- a/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart +++ b/lib/presentation/recipe/recipe_edit_screen/recipe_edit_screen.dart @@ -96,7 +96,7 @@ class RecipeEditScreen extends StatelessWidget { ), SizedBox(height: 16.0), TextFormField( - controller: state.youtube_url, + controller: state.video_url, decoration: InputDecoration(labelText: 'Ссылка'), ), SizedBox(height: 16.0), diff --git a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart index a16b4906..20f1d78e 100644 --- a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart +++ b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart @@ -40,7 +40,6 @@ class RecipeSelectScreen extends StatelessWidget { _buildSetingsScreen(BuildContext context, RecipeSelectLoadedState state) { mediaQueryData = MediaQuery.of(context); - print(state.posts); return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], @@ -86,6 +85,16 @@ class RecipeSelectScreen extends StatelessWidget { margin: EdgeInsets.only( left: 80.h, top: 12.v, bottom: 9.v), ), + Spacer(), + Padding( + padding: const EdgeInsets.only(right: 20.0), + child: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.recipeCreateScreen, + ), + child: Icon(Icons.create_outlined)), + ) ], ), ), @@ -140,6 +149,13 @@ class RecipeSelectScreen extends StatelessWidget { AppRoutes.recipeScreen, arguments: post?['id'].toString(), ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.recipeEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), )); }, ), diff --git a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart index b09c5efa..3576b5a6 100644 --- a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart +++ b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart @@ -102,9 +102,9 @@ class RecipeScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( - onPressed: () { - // Действие для редактирования - }, + onPressed: () => Navigator.pushNamed( + context, AppRoutes.recipeEditScreen, + arguments: state.recipe?['id'].toString()), child: Text('Редактировать'), ), ElevatedButton( From 8770177a5039530d90d4239ea44808bc02981c1b Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 14 May 2024 15:04:24 +0300 Subject: [PATCH 14/15] feat(app): Delete methods for recipes, blog posts, diets and plans --- .../diet_show_screen/bloc/diet_show_bloc.dart | 28 ++++++++++++++++++- .../bloc/diet_show_event.dart | 7 +++++ .../diet_show_screen/diet_show_screen.dart | 7 ++--- .../blog_post_screen/bloc/blog_post_bloc.dart | 27 ++++++++++++++++++ .../bloc/blog_post_event.dart | 7 +++++ .../blog_post_screen/blog_post_screen.dart | 6 ++-- .../plan_select_screen.dart | 1 - .../plan_show_screen/bloc/plan_show_bloc.dart | 28 ++++++++++++++++++- .../bloc/plan_show_event.dart | 7 +++++ .../plan_show_screen/plan_show_screen.dart | 7 ++--- .../recipe_select_screen.dart | 4 +-- .../bloc/recipe_show_bloc.dart | 28 ++++++++++++++++++- .../bloc/recipe_show_event.dart | 7 +++++ .../recipe_show_screen.dart | 7 ++--- 14 files changed, 150 insertions(+), 21 deletions(-) diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart index 064dcca9..a1216bd0 100644 --- a/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -13,6 +15,7 @@ part 'diet_show_state.dart'; class DietBloc extends Bloc { DietBloc(DietState initialState) : super(initialState) { on(_onInitialize); + on(_deleteEntry); } _onInitialize( @@ -44,7 +47,6 @@ class DietBloc extends Bloc { diet: currentState.diet)); } else { emit(DietLoadedState(diet: data)); - print(data); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -59,4 +61,28 @@ class DietBloc extends Bloc { emit(DietErrorState('Ошибка при получении данных о чатах: $e')); } } + + FutureOr _deleteEntry( + DeleteEntry event, Emitter emit) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final int id = event.id; + final response = await http.post( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/diets/delete/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.dietSelectScreen); + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(DietErrorState('Ошибка: ${response.statusCode}')); + } + } } diff --git a/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart b/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart index 3f1d6801..6a131d5c 100644 --- a/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart +++ b/lib/presentation/diets/diet_show_screen/bloc/diet_show_event.dart @@ -14,3 +14,10 @@ class DietInitialEvent extends DietEvent { @override List get props => []; } + +class DeleteEntry extends DietEvent { + DeleteEntry(this.id); + final int id; + @override + List get props => [id]; +} diff --git a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart index ce98505c..af308838 100644 --- a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart +++ b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart @@ -44,7 +44,6 @@ class DietScreen extends StatelessWidget { } _buildDiet(BuildContext context, DietLoadedState state) { - print(state.diet); mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( @@ -110,9 +109,9 @@ class DietScreen extends StatelessWidget { child: Text('Редактировать'), ), ElevatedButton( - onPressed: () { - // Действие для удаления - }, + onPressed: () => context + .read() + .add(DeleteEntry(state.diet?['id'])), child: Text('Удалить'), ), ], diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart index 621beb1c..74afd4d1 100644 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -13,6 +15,7 @@ part 'blog_post_state.dart'; class BlogPostBloc extends Bloc { BlogPostBloc(BlogPostState initialState) : super(initialState) { on(_onInitialize); + on(_deleteEntry); } _onInitialize( @@ -58,4 +61,28 @@ class BlogPostBloc extends Bloc { emit(BlogPostErrorState('Ошибка при получении данных о чатах: $e')); } } + + FutureOr _deleteEntry( + DeleteEntry event, Emitter emit) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final int id = event.id; + final response = await http.post( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/delete/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.feedScreen); + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(BlogPostErrorState('Ошибка: ${response.statusCode}')); + } + } } diff --git a/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart b/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart index d656a785..de77e332 100644 --- a/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart +++ b/lib/presentation/feed/blog_post_screen/bloc/blog_post_event.dart @@ -14,3 +14,10 @@ class BlogPostInitialEvent extends BlogPostEvent { @override List get props => []; } + +class DeleteEntry extends BlogPostEvent { + DeleteEntry(this.id); + final int id; + @override + List get props => [id]; +} diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index 58672875..4db80307 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -111,9 +111,9 @@ class BlogPostScreen extends StatelessWidget { child: Text('Редактировать'), ), ElevatedButton( - onPressed: () { - // Действие для удаления - }, + onPressed: () => context + .read() + .add(DeleteEntry(state.blog_post?['id'])), child: Text('Удалить'), ), ], diff --git a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart index bb9862b4..a88ca9ab 100644 --- a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart +++ b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart @@ -40,7 +40,6 @@ class PlanSelectScreen extends StatelessWidget { _buildSetingsScreen(BuildContext context, PlanSelectLoadedState state) { mediaQueryData = MediaQuery.of(context); - print(state.posts); return SafeArea( child: Scaffold( backgroundColor: Colors.grey[200], diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart index 1416bafa..616b521a 100644 --- a/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -13,6 +15,7 @@ part 'plan_show_state.dart'; class PlanBloc extends Bloc { PlanBloc(PlanState initialState) : super(initialState) { on(_onInitialize); + on(_deleteEntry); } _onInitialize( @@ -44,7 +47,6 @@ class PlanBloc extends Bloc { plan: currentState.plan)); } else { emit(PlanLoadedState(plan: data)); - print(data); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -59,4 +61,28 @@ class PlanBloc extends Bloc { emit(PlanErrorState('Ошибка при получении данных о чатах: $e')); } } + + FutureOr _deleteEntry( + DeleteEntry event, Emitter emit) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final int id = event.id; + final response = await http.post( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/plans/delete/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.planSelectScreen); + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(PlanErrorState('Ошибка: ${response.statusCode}')); + } + } } diff --git a/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart b/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart index 767eefad..fc83e024 100644 --- a/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart +++ b/lib/presentation/plans/plan_show_screen/bloc/plan_show_event.dart @@ -14,3 +14,10 @@ class PlanInitialEvent extends PlanEvent { @override List get props => []; } + +class DeleteEntry extends PlanEvent { + DeleteEntry(this.id); + final int id; + @override + List get props => [id]; +} diff --git a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart index 9da7deca..0dab4173 100644 --- a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart +++ b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart @@ -44,7 +44,6 @@ class PlanScreen extends StatelessWidget { } _buildPlan(BuildContext context, PlanLoadedState state) { - print(state.plan); mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( @@ -108,9 +107,9 @@ class PlanScreen extends StatelessWidget { child: Text('Редактировать'), ), ElevatedButton( - onPressed: () { - // Действие для удаления - }, + onPressed: () => context + .read() + .add(DeleteEntry(state.plan?['id'])), child: Text('Удалить'), ), ], diff --git a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart index 20f1d78e..f11d5c5e 100644 --- a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart +++ b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart @@ -29,7 +29,7 @@ class RecipeSelectScreen extends StatelessWidget { ); } else if (state is RecipeSelectLoadedState) { // Данные загружены, можно строить экран. - return _buildSetingsScreen(context, state); + return _buildRecipeScreen(context, state); } else { // Другие состояния, если они есть. return Container(); // Пустой контейнер для необработанных состояний. @@ -38,7 +38,7 @@ class RecipeSelectScreen extends StatelessWidget { ); } - _buildSetingsScreen(BuildContext context, RecipeSelectLoadedState state) { + _buildRecipeScreen(BuildContext context, RecipeSelectLoadedState state) { mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart index e5c375a6..a65348ed 100644 --- a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -13,6 +15,7 @@ part 'recipe_show_state.dart'; class RecipeBloc extends Bloc { RecipeBloc(RecipeState initialState) : super(initialState) { on(_onInitialize); + on(_deleteEntry); } _onInitialize( @@ -44,7 +47,6 @@ class RecipeBloc extends Bloc { recipe: currentState.recipe)); } else { emit(RecipeLoadedState(recipe: data)); - print(data); } } else { // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку @@ -59,4 +61,28 @@ class RecipeBloc extends Bloc { emit(RecipeErrorState('Ошибка при получении данных о чатах: $e')); } } + + FutureOr _deleteEntry( + DeleteEntry event, Emitter emit) async { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final int id = event.id; + final response = await http.post( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/recipe/delete/$id'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + NavigatorService.pushNamed(AppRoutes.recipeSelectScreen); + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(RecipeErrorState('Ошибка: ${response.statusCode}')); + } + } } diff --git a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart index 489e3597..6257e893 100644 --- a/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart +++ b/lib/presentation/recipe/recipe_show_screen/bloc/recipe_show_event.dart @@ -14,3 +14,10 @@ class RecipeInitialEvent extends RecipeEvent { @override List get props => []; } + +class DeleteEntry extends RecipeEvent { + DeleteEntry(this.id); + final int id; + @override + List get props => [id]; +} diff --git a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart index 3576b5a6..01e944e8 100644 --- a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart +++ b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart @@ -44,7 +44,6 @@ class RecipeScreen extends StatelessWidget { } _buildRecipe(BuildContext context, RecipeLoadedState state) { - print(state.recipe); mediaQueryData = MediaQuery.of(context); return SafeArea( child: Scaffold( @@ -108,9 +107,9 @@ class RecipeScreen extends StatelessWidget { child: Text('Редактировать'), ), ElevatedButton( - onPressed: () { - // Действие для удаления - }, + onPressed: () => context + .read() + .add(DeleteEntry(state.recipe?['id'])), child: Text('Удалить'), ), ], From 660d846b6620102caba7211e8ee80579d4b96b06 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Tue, 14 May 2024 16:46:28 +0300 Subject: [PATCH 15/15] refactor(app): Native analysys and warning refactoring --- .../diet_create_screen.dart | 2 - .../diet_select_screen.dart | 115 +++++++++--------- .../diet_show_screen/diet_show_screen.dart | 46 ------- .../blog_post_create_screen.dart | 2 - .../blog_post_screen/blog_post_screen.dart | 47 ------- .../feed/feed_screen/feed_screen.dart | 115 +++++++++--------- .../plan_create_screen.dart | 2 - .../plan_select_screen.dart | 115 +++++++++--------- .../plan_show_screen/plan_show_screen.dart | 45 ------- .../recipe_create_screen.dart | 2 - .../recipe_select_screen.dart | 115 +++++++++--------- .../recipe_show_screen.dart | 46 ------- 12 files changed, 228 insertions(+), 424 deletions(-) diff --git a/lib/presentation/diets/diet_create_screen/diet_create_screen.dart b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart index df525f60..6896701c 100644 --- a/lib/presentation/diets/diet_create_screen/diet_create_screen.dart +++ b/lib/presentation/diets/diet_create_screen/diet_create_screen.dart @@ -3,8 +3,6 @@ import 'models/diet_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; class DietCreateScreen extends StatelessWidget { const DietCreateScreen({Key? key}) : super(key: key); diff --git a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart index b37dcb7f..c44308d8 100644 --- a/lib/presentation/diets/diet_select_screen/diet_select_screen.dart +++ b/lib/presentation/diets/diet_select_screen/diet_select_screen.dart @@ -101,64 +101,7 @@ class DietSelectScreen extends StatelessWidget { ), ), SizedBox(height: 20), - ListView.builder( - shrinkWrap: true, // Add this line - itemCount: state.posts?.length ?? 0, - itemBuilder: (context, index) { - final post = state.posts?[index]; - return Container( - margin: EdgeInsets.only(bottom: 10), - 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: ListTile( - leading: CachedNetworkImage( - imageUrl: - dotenv.env['STORAGE_URL']! + post['image'], - imageBuilder: (context, imageProvider) => - CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => - CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - title: Text( - post?['name'] ?? '', - style: CustomTextStyles.titleMediumSemiBold, - ), - subtitle: Text(post?['details'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.dietScreen, - arguments: post?['id'].toString(), - ), - trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.dietEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), - )); - }, - ), + _buildDietList(context, state) ], ), ), @@ -169,6 +112,62 @@ class DietSelectScreen extends StatelessWidget { ); } + _buildDietList(BuildContext context, DietSelectLoadedState state) { + return ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['name'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['details'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.dietScreen, + arguments: post?['id'].toString(), + ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.dietEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), + )); + }, + ); + } + Function onTapBack(BuildContext context) { return () { onTapArrowLeft(context); diff --git a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart index af308838..dcc20aea 100644 --- a/lib/presentation/diets/diet_show_screen/diet_show_screen.dart +++ b/lib/presentation/diets/diet_show_screen/diet_show_screen.dart @@ -2,10 +2,6 @@ import 'bloc/diet_show_bloc.dart'; import 'models/diet_show_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_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -124,48 +120,6 @@ class DietScreen extends StatelessWidget { bottomNavigationBar: _buildBottomBar(context))); } - /// Section Widget - _buildAppbar(BuildContext context) { - return CustomAppBar( - leadingWidth: 26.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, - top: 18.v, - bottom: 25.v, - ), - onTap: () => onTapArrowleftone(context), - ), - title: Padding( - padding: EdgeInsets.only(left: 12.h), - child: Row( - children: [ - AppbarTitleImage( - imagePath: ImageConstant.imgIconMoreHorizontal, - margin: EdgeInsets.only(bottom: 3.v), - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - ), - child: Column( - children: [ - AppbarSubtitle( - text: "Валерий Леонтьев", - ), - SizedBox(height: 3.v), - Text("Был в сети вчера", style: theme.textTheme.bodySmall!) - ], - ), - ) - ], - ), - ), - ); - } - ///Handling route based on bottom click actions String getCurrentRoute(BottomBarEnum type) { switch (type) { diff --git a/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart index f19a29a4..6950af55 100644 --- a/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart +++ b/lib/presentation/feed/blog_post_create_screen/blog_post_create_screen.dart @@ -3,8 +3,6 @@ import 'models/blog_post_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; class BlogPostCreateScreen extends StatelessWidget { const BlogPostCreateScreen({Key? key}) : super(key: key); diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index 4db80307..293dfb2b 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -2,11 +2,6 @@ import 'bloc/blog_post_bloc.dart'; import 'models/blog_post_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/custom_outlined_button.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -126,48 +121,6 @@ class BlogPostScreen extends StatelessWidget { bottomNavigationBar: _buildBottomBar(context))); } - /// Section Widget - _buildAppbar(BuildContext context) { - return CustomAppBar( - leadingWidth: 26.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, - top: 18.v, - bottom: 25.v, - ), - onTap: () => onTapArrowleftone(context), - ), - title: Padding( - padding: EdgeInsets.only(left: 12.h), - child: Row( - children: [ - AppbarTitleImage( - imagePath: ImageConstant.imgIconMoreHorizontal, - margin: EdgeInsets.only(bottom: 3.v), - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - ), - child: Column( - children: [ - AppbarSubtitle( - text: "Валерий Леонтьев", - ), - SizedBox(height: 3.v), - Text("Был в сети вчера", style: theme.textTheme.bodySmall!) - ], - ), - ) - ], - ), - ), - ); - } - ///Handling route based on bottom click actions String getCurrentRoute(BottomBarEnum type) { switch (type) { diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart index e0c6b78f..e0712d31 100644 --- a/lib/presentation/feed/feed_screen/feed_screen.dart +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -100,64 +100,7 @@ class FeedScreen extends StatelessWidget { ), ), SizedBox(height: 20), - ListView.builder( - shrinkWrap: true, // Add this line - itemCount: state.posts?.length ?? 0, - itemBuilder: (context, index) { - final post = state.posts?[index]; - return Container( - margin: EdgeInsets.only(bottom: 10), - 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: ListTile( - leading: CachedNetworkImage( - imageUrl: - dotenv.env['STORAGE_URL']! + post['image'], - imageBuilder: (context, imageProvider) => - CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => - CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - title: Text( - post?['title'] ?? '', - style: CustomTextStyles.titleMediumSemiBold, - ), - subtitle: Text(post?['description'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.blogPostScreen, - arguments: post?['id'].toString(), - ), - trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.blogPostEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), - )); - }, - ), + _buildBlogPostList(context, state) ], ), ), @@ -168,6 +111,62 @@ class FeedScreen extends StatelessWidget { ); } + _buildBlogPostList(BuildContext context, FeedLoadedState state) { + return ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['title'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['description'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostScreen, + arguments: post?['id'].toString(), + ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), + )); + }, + ); + } + Function onTapBack(BuildContext context) { return () { onTapArrowLeft(context); diff --git a/lib/presentation/plans/plan_create_screen/plan_create_screen.dart b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart index 97e1fd7f..af04abc7 100644 --- a/lib/presentation/plans/plan_create_screen/plan_create_screen.dart +++ b/lib/presentation/plans/plan_create_screen/plan_create_screen.dart @@ -3,8 +3,6 @@ import 'models/plan_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; class PlanCreateScreen extends StatelessWidget { const PlanCreateScreen({Key? key}) : super(key: key); diff --git a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart index a88ca9ab..7f3486a2 100644 --- a/lib/presentation/plans/plan_select_screen/plan_select_screen.dart +++ b/lib/presentation/plans/plan_select_screen/plan_select_screen.dart @@ -101,64 +101,7 @@ class PlanSelectScreen extends StatelessWidget { ), ), SizedBox(height: 20), - ListView.builder( - shrinkWrap: true, // Add this line - itemCount: state.posts?.length ?? 0, - itemBuilder: (context, index) { - final post = state.posts?[index]; - return Container( - margin: EdgeInsets.only(bottom: 10), - 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: ListTile( - leading: CachedNetworkImage( - imageUrl: - dotenv.env['STORAGE_URL']! + post['image'], - imageBuilder: (context, imageProvider) => - CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => - CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - title: Text( - post?['name'] ?? '', - style: CustomTextStyles.titleMediumSemiBold, - ), - subtitle: Text(post?['details'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.planScreen, - arguments: post?['id'].toString(), - ), - trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.planEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), - )); - }, - ), + _buildPlanList(context, state) ], ), ), @@ -169,6 +112,62 @@ class PlanSelectScreen extends StatelessWidget { ); } + _buildPlanList(BuildContext context, PlanSelectLoadedState state) { + return ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['name'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['details'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.planScreen, + arguments: post?['id'].toString(), + ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.planEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), + )); + }, + ); + } + Function onTapBack(BuildContext context) { return () { onTapArrowLeft(context); diff --git a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart index 0dab4173..d34e1a43 100644 --- a/lib/presentation/plans/plan_show_screen/plan_show_screen.dart +++ b/lib/presentation/plans/plan_show_screen/plan_show_screen.dart @@ -2,10 +2,6 @@ import 'bloc/plan_show_bloc.dart'; import 'models/plan_show_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_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -122,47 +118,6 @@ class PlanScreen extends StatelessWidget { bottomNavigationBar: _buildBottomBar(context))); } - /// Section Widget - _buildAppbar(BuildContext context) { - return CustomAppBar( - leadingWidth: 26.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, - top: 18.v, - bottom: 25.v, - ), - onTap: () => onTapArrowleftone(context), - ), - title: Padding( - padding: EdgeInsets.only(left: 12.h), - child: Row( - children: [ - AppbarTitleImage( - imagePath: ImageConstant.imgIconMoreHorizontal, - margin: EdgeInsets.only(bottom: 3.v), - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - ), - child: Column( - children: [ - AppbarSubtitle( - text: "Валерий Леонтьев", - ), - SizedBox(height: 3.v), - Text("Был в сети вчера", style: theme.textTheme.bodySmall!) - ], - ), - ) - ], - ), - ), - ); - } ///Handling route based on bottom click actions String getCurrentRoute(BottomBarEnum type) { 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 ad4c7edf..82d2bd9a 100644 --- a/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart +++ b/lib/presentation/recipe/recipe_create_screen/recipe_create_screen.dart @@ -3,8 +3,6 @@ import 'models/recipe_create_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; class RecipeCreateScreen extends StatelessWidget { const RecipeCreateScreen({Key? key}) : super(key: key); diff --git a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart index f11d5c5e..0623c053 100644 --- a/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart +++ b/lib/presentation/recipe/recipe_select_screen/recipe_select_screen.dart @@ -101,64 +101,7 @@ class RecipeSelectScreen extends StatelessWidget { ), ), SizedBox(height: 20), - ListView.builder( - shrinkWrap: true, // Add this line - itemCount: state.posts?.length ?? 0, - itemBuilder: (context, index) { - final post = state.posts?[index]; - return Container( - margin: EdgeInsets.only(bottom: 10), - 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: ListTile( - leading: CachedNetworkImage( - imageUrl: - dotenv.env['STORAGE_URL']! + post['image'], - imageBuilder: (context, imageProvider) => - CircleAvatar( - backgroundImage: imageProvider, - radius: 25, - ), - placeholder: (context, url) => - CircularProgressIndicator(), - errorWidget: (context, url, error) => - CircleAvatar( - child: Icon(Icons.person), - backgroundColor: Colors.white, - radius: 25, - ), - ), - title: Text( - post?['title'] ?? '', - style: CustomTextStyles.titleMediumSemiBold, - ), - subtitle: Text(post?['description'] ?? ''), - onTap: () => Navigator.pushNamed( - context, - AppRoutes.recipeScreen, - arguments: post?['id'].toString(), - ), - trailing: GestureDetector( - onTap: () => Navigator.pushNamed( - context, - AppRoutes.recipeEditScreen, - arguments: post?['id'].toString(), - ), - child: Icon(Icons.edit)), - )); - }, - ), + _buildRecipeList(context, state), ], ), ), @@ -169,6 +112,62 @@ class RecipeSelectScreen extends StatelessWidget { ); } + _buildRecipeList(BuildContext context, RecipeSelectLoadedState state) { + return ListView.builder( + shrinkWrap: true, // Add this line + itemCount: state.posts?.length ?? 0, + itemBuilder: (context, index) { + final post = state.posts?[index]; + return Container( + margin: EdgeInsets.only(bottom: 10), + 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: ListTile( + leading: CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + post['image'], + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + title: Text( + post?['title'] ?? '', + style: CustomTextStyles.titleMediumSemiBold, + ), + subtitle: Text(post?['description'] ?? ''), + onTap: () => Navigator.pushNamed( + context, + AppRoutes.recipeScreen, + arguments: post?['id'].toString(), + ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.recipeEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), + )); + }, + ); + } + Function onTapBack(BuildContext context) { return () { onTapArrowLeft(context); diff --git a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart index 01e944e8..ee757764 100644 --- a/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart +++ b/lib/presentation/recipe/recipe_show_screen/recipe_show_screen.dart @@ -2,10 +2,6 @@ import 'bloc/recipe_show_bloc.dart'; import 'models/recipe_show_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_subtitle.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; -import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -122,48 +118,6 @@ class RecipeScreen extends StatelessWidget { bottomNavigationBar: _buildBottomBar(context))); } - /// Section Widget - _buildAppbar(BuildContext context) { - return CustomAppBar( - leadingWidth: 26.h, - leading: AppbarLeadingImage( - imagePath: ImageConstant.imgArrowLeft, - margin: EdgeInsets.only( - left: 16.h, - top: 18.v, - bottom: 25.v, - ), - onTap: () => onTapArrowleftone(context), - ), - title: Padding( - padding: EdgeInsets.only(left: 12.h), - child: Row( - children: [ - AppbarTitleImage( - imagePath: ImageConstant.imgIconMoreHorizontal, - margin: EdgeInsets.only(bottom: 3.v), - ), - Padding( - padding: EdgeInsets.only( - left: 12.h, - top: 2.v, - ), - child: Column( - children: [ - AppbarSubtitle( - text: "Валерий Леонтьев", - ), - SizedBox(height: 3.v), - Text("Был в сети вчера", style: theme.textTheme.bodySmall!) - ], - ), - ) - ], - ), - ), - ); - } - ///Handling route based on bottom click actions String getCurrentRoute(BottomBarEnum type) { switch (type) {