From 96ad5a7cfb0ff98eff7c266b959ce18d532a5b33 Mon Sep 17 00:00:00 2001 From: maxxkim Date: Mon, 13 May 2024 11:53:42 +0300 Subject: [PATCH] Blog post edit layouts and BloC --- .../bloc/blog_post_edit_bloc.dart | 63 +++++++++ .../bloc/blog_post_edit_event.dart | 16 +++ .../bloc/blog_post_edit_state.dart | 69 ++++++++++ .../blog_post_edit_screen.dart | 128 ++++++++++++++++++ .../models/blog_post_edit_model.dart | 19 +++ .../blog_post_screen/blog_post_screen.dart | 9 +- .../feed/feed_screen/feed_screen.dart | 7 + lib/routes/app_routes.dart | 4 + 8 files changed, 312 insertions(+), 3 deletions(-) create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart create mode 100644 lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart new file mode 100644 index 00000000..8928b58b --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_bloc.dart @@ -0,0 +1,63 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:hive/hive.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '/core/app_export.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart'; +part 'blog_post_edit_event.dart'; +part 'blog_post_edit_state.dart'; + +/// A bloc that manages the state of a BlogPostEdit according to the event that is dispatched to it. +class BlogPostEditBloc extends Bloc { + BlogPostEditBloc(BlogPostEditState initialState) : super(initialState) { + on(_onInitialize); + } + + _onInitialize( + BlogPostEditInitialEvent event, + Emitter emit, + ) async { + try { + final box = await Hive.openBox('token'); + final token = box.get('token'); + if (token == null) { + NavigatorService.popAndPushNamed( + AppRoutes.authScreen, + ); + } + final String? blog_postID = state.blog_postModelObj?.blog_postID; + final response = await http.get( + Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/show/$blog_postID'), + headers: { + 'Authorization': 'Bearer $token', + }, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (data is Map) { + if (state is BlogPostEditLoadedState) { + var currentState = state as BlogPostEditLoadedState; + emit(currentState.copyWith( + blog_postModelObj: currentState.blog_postModelObj, + blog_post: currentState.blog_post)); + } else { + emit(BlogPostEditLoadedState(blog_post: data)); + print(data); + } + } else { + // Один или несколько ключевых полей отсутствуют, обрабатываем ошибку + emit(BlogPostEditErrorState( + 'Некоторые необходимые данные отсутствуют.')); + } + } else { + // Статус ответа не равен 200, обрабатываем ошибку + emit(BlogPostEditErrorState('Ошибка сервера: ${response.statusCode}')); + } + } catch (e) { + // Ошибка при декодировании JSON или другая ошибка выполнения + emit(BlogPostEditErrorState('Ошибка при получении данных о чатах: $e')); + } + } +} diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart new file mode 100644 index 00000000..debaa1d4 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_event.dart @@ -0,0 +1,16 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_edit_bloc.dart'; + +/// Abstract class for all events that can be dispatched from the +///BlogPostEdit widget. +/// +/// Events must be immutable and implement the [Equatable] interface. +@immutable +abstract class BlogPostEditEvent extends Equatable {} + +/// Event that is dispatched when the BlogPostEdit widget is first created. +class BlogPostEditInitialEvent extends BlogPostEditEvent { + @override + List get props => []; +} diff --git a/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart new file mode 100644 index 00000000..321448ee --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/bloc/blog_post_edit_state.dart @@ -0,0 +1,69 @@ +// ignore_for_file: must_be_immutable + +part of 'blog_post_edit_bloc.dart'; + +/// Represents the state of BlogPostEdit in the application. +/// + +class BlogPostEditState extends Equatable { + BlogPostEditState({ + this.blog_postModelObj, + }); + + BlogPostEditModel? blog_postModelObj; + + @override + List get props => [blog_postModelObj]; + BlogPostEditState copyWith({BlogPostEditModel? blog_postModelObj}) { + return BlogPostEditState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + ); + } +} + +class BlogPostEditLoadedState extends BlogPostEditState { + BlogPostEditLoadedState( + {this.blog_postModelObj, + this.blog_post, + this.title, + this.description, + this.youtube_url}); + + BlogPostEditModel? blog_postModelObj; + Map? blog_post; + TextEditingController? title; + TextEditingController? description; + TextEditingController? youtube_url; + @override + List get props => + [blog_postModelObj, blog_post, title, description, youtube_url]; + BlogPostEditLoadedState copyWith({ + BlogPostEditModel? blog_postModelObj, + Map? blog_post, + TextEditingController? title, + TextEditingController? description, + TextEditingController? youtube_url, + }) { + return BlogPostEditLoadedState( + blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj, + blog_post: blog_post ?? this.blog_post, + title: title ?? this.title, + description: description ?? this.description, + youtube_url: youtube_url ?? this.youtube_url, + ); + } +} + +class BlogPostEditErrorState extends BlogPostEditState { + final String errorMessage; + + BlogPostEditErrorState(this.errorMessage); + + @override + List get props => [errorMessage]; +} + +class BlogPostEditInitialState extends BlogPostEditState { + BlogPostEditInitialState({required BlogPostEditModel blog_postModelObj}) + : super(blog_postModelObj: blog_postModelObj); +} diff --git a/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart new file mode 100644 index 00000000..9a452d31 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart @@ -0,0 +1,128 @@ +import 'bloc/blog_post_edit_bloc.dart'; +import 'models/blog_post_edit_model.dart'; +import 'package:eatvio_app/core/app_export.dart'; +import 'package:flutter/material.dart'; +import 'package:eatvio_app/widgets/custom_bottom_bar.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class BlogPostEditScreen extends StatelessWidget { + const BlogPostEditScreen({Key? key}) : super(key: key); + static Widget builder(BuildContext context) { + final blog_postID = ModalRoute.of(context)!.settings.arguments; + return BlocProvider( + create: (context) => BlogPostEditBloc(BlogPostEditState( + blog_postModelObj: + BlogPostEditModel(blog_postID: blog_postID.toString()))) + ..add(BlogPostEditInitialEvent()), + child: BlogPostEditScreen()); + } + + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BlogPostEditInitialState) { + // Это начальное состояние, данные ещё не загружены. + return Container( + color: Colors.white, + ); + } else if (state is BlogPostEditLoadedState) { + // Данные загружены, можно строить экран. + return _buildBlogPostEdit(context, state); + } else if (state is BlogPostEditErrorState) { + // Произошла ошибка при загрузке данных. + return Center(child: Text(state.errorMessage)); + } else { + // Другие состояния, если они есть. + return Container(); // Пустой контейнер для необработанных состояний. + } + }, + ); + } + + _buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) { + print(state.blog_post); + mediaQueryData = MediaQuery.of(context); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Container( + width: double.maxFinite, + decoration: AppDecoration.fillGray, + child: ListView(children: [ + SizedBox(height: 30.v), + Container( + margin: EdgeInsets.only( + left: 16.h, + right: 20.h, + bottom: 10.h, + ), + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(12)), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + CachedNetworkImage( + imageUrl: dotenv.env['STORAGE_URL']! + + (state.blog_post?['image'] ?? ''), + imageBuilder: (context, imageProvider) => CircleAvatar( + backgroundImage: imageProvider, + radius: 25, + ), + placeholder: (context, url) => + CircularProgressIndicator(), + errorWidget: (context, url, error) => CircleAvatar( + child: Icon(Icons.person), + backgroundColor: Colors.white, + radius: 25, + ), + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.title, + decoration: InputDecoration(labelText: 'Заголовок'), + initialValue: state + .blog_post?['title'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.description, + decoration: InputDecoration(labelText: 'Описание'), + initialValue: state.blog_post?[ + 'description'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + TextFormField( + controller: state.youtube_url, + decoration: InputDecoration(labelText: 'Ссылка'), + initialValue: state.blog_post?[ + 'youtube_url'], // Если есть начальное значение + ), + SizedBox(height: 16.0), + // Здесь нужно добавить виджет для загрузки картинки + ElevatedButton( + onPressed: () { + // Действия по сохранению записи в блоге + }, + child: Text('Сохранить'), + ) + ]), + ) + ]), + ), + bottomNavigationBar: _buildBottomBar(context))); + } +} + +_buildBottomBar(BuildContext context) { + return CustomBottomBar(onChanged: (BottomBarEnum type) {}); +} diff --git a/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart b/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart new file mode 100644 index 00000000..f9ef98c2 --- /dev/null +++ b/lib/presentation/feed/blog_post_edit_screen/models/blog_post_edit_model.dart @@ -0,0 +1,19 @@ +// ignore_for_file: must_be_immutable + +import 'package:equatable/equatable.dart'; + +/// This class defines the variables used in the [blog_post_edit_screen], +/// and is typically used to hold data that is passed between different parts of the application. + +class BlogPostEditModel extends Equatable { + final String blog_postID; + + BlogPostEditModel({required String blog_postID}) : blog_postID = blog_postID; + + BlogPostEditModel copyWith(String blog_postID) { + return BlogPostEditModel(blog_postID: blog_postID); + } + + @override + List get props => [blog_postID]; +} diff --git a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart index 31260611..357ba232 100644 --- a/lib/presentation/feed/blog_post_screen/blog_post_screen.dart +++ b/lib/presentation/feed/blog_post_screen/blog_post_screen.dart @@ -3,6 +3,7 @@ import 'models/blog_post_model.dart'; import 'package:eatvio_app/core/app_export.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/widgets/app_bar/custom_app_bar.dart'; +import 'package:eatvio_app/widgets/custom_outlined_button.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_subtitle.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_leading_image.dart'; import 'package:eatvio_app/widgets/app_bar/appbar_title_image.dart'; @@ -103,9 +104,11 @@ class BlogPostScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( - onPressed: () { - // Действие для редактирования - }, + onPressed: () => Navigator.pushNamed( + context, + AppRoutes.blogPostEditScreen, + arguments: state.blog_post?['id'].toString(), + ), child: Text('Редактировать'), ), ElevatedButton( diff --git a/lib/presentation/feed/feed_screen/feed_screen.dart b/lib/presentation/feed/feed_screen/feed_screen.dart index 6a4c668d..55a8215e 100644 --- a/lib/presentation/feed/feed_screen/feed_screen.dart +++ b/lib/presentation/feed/feed_screen/feed_screen.dart @@ -139,6 +139,13 @@ class FeedScreen extends StatelessWidget { AppRoutes.blogPostScreen, arguments: post?['id'].toString(), ), + trailing: GestureDetector( + onTap: () => Navigator.pushNamed( + context, + AppRoutes.blogPostEditScreen, + arguments: post?['id'].toString(), + ), + child: Icon(Icons.edit)), )); }, ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 39b58b96..25a9037b 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -30,6 +30,7 @@ import 'package:eatvio_app/presentation/diets/diet_show_screen/diet_show_screen. import 'package:eatvio_app/presentation/plans/plan_show_screen/plan_show_screen.dart'; import 'package:eatvio_app/presentation/recipe/recipe_show_screen/recipe_show_screen.dart'; import 'package:eatvio_app/presentation/feed/blog_post_screen/blog_post_screen.dart'; +import 'package:eatvio_app/presentation/feed/blog_post_edit_screen/blog_post_edit_screen.dart'; class AppRoutes { // REGISTRATION @@ -90,6 +91,8 @@ class AppRoutes { static const String blogPostScreen = '/blogPost'; + static const String blogPostEditScreen = '/blogPostEdit'; + static const String recipeSelectScreen = '/recipeSelect'; static const String recipeScreen = '/recipe'; @@ -135,5 +138,6 @@ class AppRoutes { dietScreen: DietScreen.builder, planScreen: PlanScreen.builder, blogPostScreen: BlogPostScreen.builder, + blogPostEditScreen: BlogPostEditScreen.builder, }; }