feat(feed): Comment and author info display

This commit is contained in:
2024-05-16 16:04:04 +03:00
parent fc8301bbe1
commit 02cd34a4ad
7 changed files with 363 additions and 102 deletions

View File

@@ -1,10 +1,12 @@
import 'dart:async';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart';
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 'package:intl/intl.dart';
import 'dart:convert';
import '/core/app_export.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/blog_post_model.dart';
@@ -48,12 +50,10 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
author: currentState.author,
comments: currentState.comments));
} else {
emit(BlogPostLoadedState(blog_post: data));
final author = await _fetchAuthor(data['id']);
emit(BlogPostLoadedState(blog_post: data, author: author));
final comments = [await _fetchComments(data['id'])];
emit(BlogPostLoadedState(
blog_post: data, author: author, comments: comments));
blog_post: data,
author: await _fetchAuthor(data['id']),
comments: fillCommentList(await _fetchComments(data['id']))));
}
} else {
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
@@ -109,13 +109,11 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
'Authorization': 'Bearer $token',
},
);
print(response.body);
final data = jsonDecode(response.body);
print(data);
return data;
}
_fetchComments(int id) async {
Future<List> _fetchComments(int id) async {
final box = await Hive.openBox('token');
final token = box.get('token');
if (token == null) {
@@ -129,9 +127,21 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
'Authorization': 'Bearer $token',
},
);
print(response.body);
final data = jsonDecode(response.body);
print(data);
return data;
}
List<CommentModel> fillCommentList(List<dynamic> comments) {
List<CommentModel> list = [];
for (int i = 0; i < comments.length; i++) {
list.add(CommentModel(
id: comments[i]['id'].toString(),
userImage: '',
username: 'Вася Пупкин',
dateText: DateFormat('dd.MM')
.format(DateTime.parse(comments[i]['updated_at'])),
greetingText: comments[i]['body']));
}
return list;
}
}

View File

@@ -1,8 +1,12 @@
import 'bloc/blog_post_bloc.dart';
import 'models/blog_post_model.dart';
import 'models/comment_model.dart';
import 'widgets/comment_widget.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:eatvio_app/widgets/custom_edit_text_form_field.dart';
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -50,98 +54,203 @@ class BlogPostScreen extends StatelessWidget {
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: () => Navigator.pushNamed(
context,
AppRoutes.blogPostEditScreen,
arguments: state.blog_post?['id'].toString(),
),
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<BlogPostBloc>()
.add(DeleteEntry(state.blog_post?['id'])),
child: Text('Удалить'),
),
],
)
]),
)
SizedBox(height: 15.v),
_buildHeaderRow(context, state),
SizedBox(height: 15.v),
_buildBlogPostData(context, state),
SizedBox(height: 15.v),
_buildTextField(context, state),
SizedBox(height: 15.v),
_buildCommentSection(context, state),
],
),
),
bottomNavigationBar: _buildBottomBar(context)));
}
///Handling route based on bottom click actions
String getCurrentRoute(BottomBarEnum type) {
switch (type) {
case BottomBarEnum.tf:
return AppRoutes.blogPostScreen;
default:
return "/";
}
_buildHeaderRow(BuildContext context, BlogPostLoadedState state) {
final String name = state.author?['name'];
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h),
padding: EdgeInsets.symmetric(
horizontal: 24.h,
vertical: 12.v,
),
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: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CustomImageView(
imagePath: ImageConstant.imgArrowLeft,
onTap: () => NavigatorService.goBack()),
Padding(
padding: EdgeInsets.only(top: 1.v),
child: Row(
children: [
Align(
alignment: Alignment.centerRight,
child: Text(
"$name",
style: CustomTextStyles.titleMediumBlack,
),
),
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! +
(state.author?['profile_photo_path'] ?? ''),
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,
),
),
],
),
),
],
),
);
}
///Handling page based on route
Widget getCurrentPage(
BuildContext context,
String currentRoute,
) {
switch (currentRoute) {
case AppRoutes.blogPostScreen:
return BlogPostScreen.builder(context);
default:
return BlogPostScreen();
}
_buildBlogPostData(BuildContext context, BlogPostLoadedState state) {
return 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) => ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
child: Image(
image: imageProvider,
),
),
placeholder: (context, url) => CircularProgressIndicator(),
),
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: () => Navigator.pushNamed(
context,
AppRoutes.blogPostEditScreen,
arguments: state.blog_post?['id'].toString(),
),
child: Text('Редактировать'),
),
ElevatedButton(
onPressed: () => context
.read<BlogPostBloc>()
.add(DeleteEntry(state.blog_post?['id'])),
child: Text('Удалить'),
),
],
)
]),
);
}
_buildTextField(BuildContext context, BlogPostLoadedState state) {
return 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: [
Column(
children: [
CustomTextFormField(
maxLines: 8,
),
SizedBox(height: 15.h),
CustomOutlinedButton(
text: "Оставить отзыв",
margin: EdgeInsets.only(bottom: 5),
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
decoration: BoxDecoration(
color: Colors.transparent,
),
onPressed: () => print("tap"),
// Установите прозрачный фон
)
],
),
],
),
);
}
_buildCommentSection(BuildContext context, BlogPostLoadedState state) {
return ListView.separated(
shrinkWrap: true,
separatorBuilder: (context, index) {
return SizedBox(
height: 10.v,
);
},
itemCount: state.comments?.length ?? 0,
itemBuilder: (context, index) {
CommentModel model = state.comments?[index];
return CommentWidget(
model,
);
});
}
_buildBottomBar(BuildContext context) {

View File

@@ -1,19 +1,20 @@
// 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.
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart';
class BlogPostModel extends Equatable {
final String blog_postID;
BlogPostModel({required this.blog_postID, this.comments = const []});
List<CommentModel> comments;
BlogPostModel({required String blog_postID}) : blog_postID = blog_postID;
BlogPostModel copyWith(String blog_postID) {
return BlogPostModel(blog_postID: blog_postID);
BlogPostModel copyWith({String? blog_postID, List<CommentModel>? comments}) {
return BlogPostModel(
blog_postID: blog_postID ?? this.blog_postID,
comments: comments ?? this.comments,
);
}
@override
List<Object?> get props => [blog_postID];
List<Object?> get props => [blog_postID, comments];
}

View File

@@ -0,0 +1,29 @@
import 'package:eatvio_app/core/app_export.dart';
/// This class is used in the [userprofile_item_widget] screen.
// ignore_for_file: must_be_immutable
class CommentModel {
CommentModel(
{this.userImage,
this.username,
this.dateText,
this.greetingText,
required this.id}) {
userImage = userImage ?? ImageConstant.imageNotFound;
username = username;
dateText = dateText;
greetingText = greetingText;
id = id;
}
String? userImage;
String? username;
String? dateText;
String? greetingText;
String id;
}

View File

@@ -0,0 +1,107 @@
// ignore_for_file: must_be_immutable
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:eatvio_app/core/app_export.dart';
import 'package:eatvio_app/presentation/feed/blog_post_screen/models/comment_model.dart';
import 'package:cached_network_image/cached_network_image.dart';
class CommentWidget extends StatelessWidget {
CommentWidget(this.commentModelObj, {Key? key})
: super(
key: key,
);
CommentModel commentModelObj;
@override
Widget build(BuildContext context) {
return 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: Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, // Move the main axis alignment to space between
crossAxisAlignment:
CrossAxisAlignment.center, // Center the items vertically
children: [
CachedNetworkImage(
imageUrl: dotenv.env['STORAGE_URL']! + commentModelObj.userImage!,
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
radius: 25,
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => CircleAvatar(
child: Icon(Icons.person),
backgroundColor: Colors.white,
radius: 25,
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: 12.h,
top: 2.v,
bottom: 11.v,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 227.h,
child: Row(
children: [
Text(
commentModelObj.username!.length <= 15
? commentModelObj.username!
: commentModelObj.username!.substring(0, 15) +
'...',
style: theme.textTheme.titleMedium,
),
Spacer(),
Padding(
padding: EdgeInsets.only(left: 6.h),
child: Text(
commentModelObj.dateText!,
style: theme.textTheme.bodyMedium,
),
)
],
),
),
SizedBox(height: 6.v),
Text(
commentModelObj.greetingText!,
style: CustomTextStyles.bodyMediumBluegray800,
),
],
),
),
),
CustomImageView(
imagePath: ImageConstant.imgSend,
height: 40.v,
width: 40.h,
margin: EdgeInsets.only(left: 5)),
],
),
);
}
}