feat(feed): Comment and author info display
This commit is contained in:
3
assets/images/img_send.svg
Normal file
3
assets/images/img_send.svg
Normal 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 |
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user