From 76a29ebf6706428b453f581a3d9314d0483e5f7e Mon Sep 17 00:00:00 2001 From: maxxkim Date: Wed, 8 May 2024 14:11:18 +0300 Subject: [PATCH] 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,