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

@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.9816 0.935472C27.9805 0.930804 27.9787 0.926137 27.9776 0.92147C27.9577 0.828707 27.9239 0.738861 27.8813 0.651932C27.8702 0.629762 27.8591 0.608176 27.8469 0.586006C27.7967 0.49791 27.7384 0.413315 27.6631 0.338054C27.5878 0.262794 27.5027 0.204452 27.4146 0.154278C27.3936 0.142027 27.3726 0.131525 27.3504 0.12044C27.2611 0.0766842 27.1695 0.0410959 27.0738 0.0212598C27.0709 0.0206764 27.0692 0.020093 27.0668 0.0195095C26.9671 -0.000326583 26.8656 -0.00324366 26.764 0.00317391C26.7419 0.00434074 26.7197 0.0066744 26.6975 0.00959148C26.5949 0.0218432 26.4928 0.0434296 26.3953 0.0831018L0.725638 10.5846C0.267657 10.7719 -0.0223005 11.2275 0.0016195 11.7211C0.0255395 12.2152 0.358086 12.6405 0.832403 12.7823L11.8986 16.1025L15.2183 27.1687C15.3606 27.6425 15.7859 27.975 16.2795 27.9989C16.2987 27.9995 16.3174 28.0001 16.3367 28.0001C16.8081 28.0001 17.2363 27.7154 17.4166 27.2749L27.918 1.60465C27.918 1.60407 27.918 1.60407 27.918 1.60348C27.9571 1.5078 27.9781 1.40804 27.9904 1.30711C27.9933 1.28202 27.9962 1.25752 27.9974 1.23243C28.0032 1.13325 28.0009 1.03349 27.9816 0.935472ZM4.66369 11.4947L21.9118 4.4383L12.5036 13.847L4.66369 11.4947ZM16.5053 23.3375L14.1535 15.4969L23.5611 6.08936L16.5053 23.3375Z" fill="#323F4B"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -106,6 +106,8 @@ class ImageConstant {
static String imgMic = '$imagePath/img_mic.svg';
static String imgSend = '$imagePath/img_send.svg';
static String imgAttach = '$imagePath/img_attach.svg';
static String imgExit = '$imagePath/img_nav_exit.svg';

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)),
],
),
);
}
}