forked from eatvio/eatvio_app
Merge pull request 'Новый функционал ленты и верстка:' (#58) from maxxkim/eatvio_app:diary into main
Reviewed-on: eatvio/eatvio_app#58 Reviewed-by: apoloz <anatoliy.poloz@gmail.com> fixes #57 fixes #56 fixes #50
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,3 +0,0 @@
|
||||
//import 'package:eatvio_app/core/app_export.dart';
|
||||
|
||||
class ApiClient {}
|
||||
@@ -7,7 +7,6 @@ import 'package:eatvio_app/presentation/chat/chat_select_screen/models/chat_entr
|
||||
/// and is typically used to hold data that is passed between different parts of the application.
|
||||
class ChatSelectModel extends Equatable {
|
||||
ChatSelectModel({this.chatList = const []});
|
||||
|
||||
List<ChatItemModel> chatList;
|
||||
ChatSelectModel copyWith({List<ChatItemModel>? chatList}) {
|
||||
return ChatSelectModel(
|
||||
|
||||
@@ -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';
|
||||
@@ -16,6 +18,7 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
|
||||
BlogPostBloc(BlogPostState initialState) : super(initialState) {
|
||||
on<BlogPostInitialEvent>(_onInitialize);
|
||||
on<DeleteEntry>(_deleteEntry);
|
||||
on<CreateComment>(_createComment);
|
||||
}
|
||||
|
||||
_onInitialize(
|
||||
@@ -44,9 +47,17 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
|
||||
var currentState = state as BlogPostLoadedState;
|
||||
emit(currentState.copyWith(
|
||||
blog_postModelObj: currentState.blog_postModelObj,
|
||||
blog_post: currentState.blog_post));
|
||||
blog_post: currentState.blog_post,
|
||||
author: currentState.author,
|
||||
comments: currentState.comments,
|
||||
token: currentState.token));
|
||||
} else {
|
||||
emit(BlogPostLoadedState(blog_post: data));
|
||||
emit(BlogPostLoadedState(
|
||||
blog_post: data,
|
||||
token: token,
|
||||
commentController: TextEditingController(),
|
||||
author: await _fetchAuthor(data['id']),
|
||||
comments: fillCommentList(await _fetchComments(data['id']))));
|
||||
}
|
||||
} else {
|
||||
// Один или несколько ключевых полей отсутствуют, обрабатываем ошибку
|
||||
@@ -62,6 +73,27 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _createComment(
|
||||
CreateComment event, Emitter<BlogPostState> emit) async {
|
||||
if (state is BlogPostLoadedState) {
|
||||
var currentState = state as BlogPostLoadedState;
|
||||
// Отправляем данные по HTTP маршруту и ждем ответ
|
||||
var response = await _sendCommentData(
|
||||
currentState.token, event.body, currentState.blog_post?['id']);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var updatedComments = fillCommentList(
|
||||
await _fetchComments(currentState.blog_post?['id']));
|
||||
var updatedState = currentState.copyWith(
|
||||
comments: updatedComments,
|
||||
commentController:
|
||||
TextEditingController(), // Clear the comment input field
|
||||
);
|
||||
emit(updatedState); // Emit the updated state to trigger a screen update
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _deleteEntry(
|
||||
DeleteEntry event, Emitter<BlogPostState> emit) async {
|
||||
final box = await Hive.openBox('token');
|
||||
@@ -85,4 +117,75 @@ class BlogPostBloc extends Bloc<BlogPostEvent, BlogPostState> {
|
||||
emit(BlogPostErrorState('Ошибка: ${response.statusCode}'));
|
||||
}
|
||||
}
|
||||
|
||||
_fetchAuthor(int id) async {
|
||||
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/getAuthor/$id'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
final data = jsonDecode(response.body);
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<List> _fetchComments(int id) async {
|
||||
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/getComments/$id'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
final data = jsonDecode(response.body);
|
||||
return data;
|
||||
}
|
||||
|
||||
List<CommentModel> fillCommentList(List<dynamic> comments) {
|
||||
List<CommentModel> list = [];
|
||||
for (int i = 0; i < comments.length; i++) {
|
||||
print(comments[i]);
|
||||
list.add(CommentModel(
|
||||
id: comments[i]['id'].toString(),
|
||||
userImage: comments[i]['profile_photo_path'],
|
||||
username: comments[i]['name'],
|
||||
dateText: DateFormat('dd.MM')
|
||||
.format(DateTime.parse(comments[i]['updated_at'])),
|
||||
greetingText: comments[i]['body']));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<http.Response> _sendCommentData(
|
||||
String? token, String body, int id) async {
|
||||
String url = dotenv.env['SERVER_ROOT']! + '/feed/createComment/$id';
|
||||
final Map<String, dynamic> credentials = {
|
||||
'body': body,
|
||||
};
|
||||
|
||||
var response = await http.post(Uri.parse(url),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
body: json.encode(credentials),
|
||||
encoding: utf8);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,3 +21,24 @@ class DeleteEntry extends BlogPostEvent {
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
class FetchAuthor extends BlogPostEvent {
|
||||
FetchAuthor(this.id);
|
||||
final int id;
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
class FetchComments extends BlogPostEvent {
|
||||
FetchComments(this.id);
|
||||
final int id;
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
class CreateComment extends BlogPostEvent {
|
||||
CreateComment(this.body);
|
||||
final String body;
|
||||
@override
|
||||
List<Object?> get props => [body];
|
||||
}
|
||||
|
||||
@@ -22,18 +22,36 @@ class BlogPostState extends Equatable {
|
||||
}
|
||||
|
||||
class BlogPostLoadedState extends BlogPostState {
|
||||
BlogPostLoadedState({this.blog_postModelObj, this.blog_post});
|
||||
BlogPostLoadedState(
|
||||
{this.blog_postModelObj,
|
||||
this.blog_post,
|
||||
this.author,
|
||||
this.comments,
|
||||
this.token,
|
||||
this.commentController});
|
||||
|
||||
BlogPostModel? blog_postModelObj;
|
||||
Map<String, dynamic>? blog_post;
|
||||
Map<String, dynamic>? author;
|
||||
List<dynamic>? comments;
|
||||
TextEditingController? commentController;
|
||||
String? token;
|
||||
@override
|
||||
List<Object?> get props => [blog_postModelObj, blog_post];
|
||||
List<Object?> get props => [blog_postModelObj, blog_post, author, comments];
|
||||
BlogPostLoadedState copyWith(
|
||||
{BlogPostModel? blog_postModelObj, Map<String, dynamic>? blog_post}) {
|
||||
{BlogPostModel? blog_postModelObj,
|
||||
Map<String, dynamic>? blog_post,
|
||||
Map<String, dynamic>? author,
|
||||
TextEditingController? commentController,
|
||||
String? token,
|
||||
List<dynamic>? comments}) {
|
||||
return BlogPostLoadedState(
|
||||
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
|
||||
blog_post: blog_post ?? this.blog_post,
|
||||
);
|
||||
blog_postModelObj: blog_postModelObj ?? this.blog_postModelObj,
|
||||
blog_post: blog_post ?? this.blog_post,
|
||||
author: author ?? this.author,
|
||||
comments: comments ?? this.comments,
|
||||
token: token ?? this.token,
|
||||
commentController: commentController ?? this.commentController);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import 'bloc/blog_post_bloc.dart';
|
||||
import 'models/blog_post_model.dart';
|
||||
import 'widgets/comment_widget.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.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,97 +54,326 @@ 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_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: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
state.blog_post?['title'],
|
||||
style: CustomTextStyles.titleMediumBlack,
|
||||
),
|
||||
Spacer(),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (value) => tapMenu(value, context, state),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'edit',
|
||||
child: Text('Редактировать'),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
_buildImage(state),
|
||||
SizedBox(height: 16.0),
|
||||
Text(
|
||||
state.blog_post?['description'],
|
||||
style: CustomTextStyles.bodyMediumBluegray800,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
DateFormat('dd.MM.yyyy')
|
||||
.format(DateTime.parse(state.blog_post?['updated_at'])),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
)),
|
||||
SizedBox(height: 16.0),
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
child: Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(Icons.favorite_outline),
|
||||
),
|
||||
Text(
|
||||
"Нравится",
|
||||
style: CustomTextStyles.titleMediumSemiBold,
|
||||
)
|
||||
]),
|
||||
),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 24),
|
||||
child: Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Icon(Icons.share_outlined),
|
||||
),
|
||||
Text(
|
||||
"Поделиться",
|
||||
style: CustomTextStyles.titleMediumSemiBold,
|
||||
)
|
||||
]))
|
||||
]),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
_buildImage(BlogPostLoadedState state) {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: dotenv.env['STORAGE_URL']! + (state.blog_post?['image'] ?? ''),
|
||||
imageBuilder: (context, imageProvider) => Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
|
||||
child: Image(
|
||||
image: imageProvider,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 48.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 4.0),
|
||||
Text(
|
||||
'24',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 4.0),
|
||||
Text(
|
||||
'24',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 48.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.comment_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 4.0),
|
||||
Text(
|
||||
'${state.comments?.length}',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
void tapMenu(String value, BuildContext context, BlogPostLoadedState state) {
|
||||
if (value == 'edit') {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutes.blogPostEditScreen,
|
||||
arguments: state.blog_post?['id'].toString(),
|
||||
);
|
||||
} else if (value == 'delete') {
|
||||
context.read<BlogPostBloc>().add(DeleteEntry(state.blog_post?['id']));
|
||||
}
|
||||
}
|
||||
|
||||
///Handling page based on route
|
||||
Widget getCurrentPage(
|
||||
BuildContext context,
|
||||
String currentRoute,
|
||||
) {
|
||||
switch (currentRoute) {
|
||||
case AppRoutes.blogPostScreen:
|
||||
return BlogPostScreen.builder(context);
|
||||
default:
|
||||
return BlogPostScreen();
|
||||
_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(
|
||||
controller: state.commentController,
|
||||
maxLines: 4,
|
||||
),
|
||||
SizedBox(height: 15.h),
|
||||
CustomOutlinedButton(
|
||||
text: "Оставить отзыв",
|
||||
margin: EdgeInsets.only(bottom: 5),
|
||||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
onPressed: () => context
|
||||
.read<BlogPostBloc>()
|
||||
.add(CreateComment(state.commentController!.text)),
|
||||
// Установите прозрачный фон
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildCommentSection(BuildContext context, BlogPostLoadedState state) {
|
||||
if (state.comments != null && state.comments!.isNotEmpty) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (int i = 0; i < state.comments!.length; i++)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
CommentWidget(state.comments![i]),
|
||||
SizedBox(height: 10.0), // Separator
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// Return a message or placeholder widget when there are no comments
|
||||
return Center(
|
||||
child: Text('No comments available'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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: 30.v,
|
||||
width: 30.h,
|
||||
margin: EdgeInsets.only(left: 5)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ class FeedBloc extends Bloc<FeedEvent, FeedState> {
|
||||
}
|
||||
final response = await http.get(
|
||||
Uri.parse(dotenv.env['SERVER_ROOT']! + '/feed/index'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
);
|
||||
print('Got response');
|
||||
if (response.statusCode == 200) {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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:eatvio_app/presentation/feed/feed_screen/widgets/feed_search_view.dart';
|
||||
import 'package:eatvio_app/presentation/feed/feed_screen/widgets/feed_scroll_item.dart';
|
||||
import 'package:eatvio_app/widgets/custom_bottom_bar.dart';
|
||||
import 'package:eatvio_app/widgets/custom_outlined_button.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class FeedScreen extends StatelessWidget {
|
||||
static Widget builder(BuildContext context) {
|
||||
@@ -28,7 +29,7 @@ class FeedScreen extends StatelessWidget {
|
||||
);
|
||||
} else if (state is FeedLoadedState) {
|
||||
// Данные загружены, можно строить экран.
|
||||
return _buildSetingsScreen(context, state);
|
||||
return _buildFeedScreen(context, state);
|
||||
} else {
|
||||
// Другие состояния, если они есть.
|
||||
return Container(); // Пустой контейнер для необработанных состояний.
|
||||
@@ -37,7 +38,7 @@ class FeedScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSetingsScreen(BuildContext context, FeedLoadedState state) {
|
||||
_buildFeedScreen(BuildContext context, FeedLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
@@ -52,53 +53,10 @@ class FeedScreen extends StatelessWidget {
|
||||
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),
|
||||
),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 20.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutes.blogPostCreateScreen,
|
||||
),
|
||||
child: Icon(Icons.create_outlined)),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
_buildSearch(),
|
||||
SizedBox(height: 20),
|
||||
_buildMenu(context, state),
|
||||
SizedBox(height: 20),
|
||||
_buildBlogPostList(context, state)
|
||||
],
|
||||
@@ -111,60 +69,202 @@ class FeedScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSearch() {
|
||||
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: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
CustomSearchView(
|
||||
hintText: "Поиск записей",
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 5,
|
||||
child: CustomOutlinedButton(
|
||||
text: "Найти запись",
|
||||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8), // Add some spacing between the buttons
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: CustomOutlinedButton(
|
||||
text: "+",
|
||||
buttonStyle: CustomButtonStyles.outlinePrimaryTL15,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildBlogPostList(BuildContext context, FeedLoadedState state) {
|
||||
return 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),
|
||||
List<Widget> postWidgets = [];
|
||||
|
||||
if (state.posts != null) {
|
||||
for (var post in state.posts!) {
|
||||
postWidgets.add(_buildFeedEntry(post, context));
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: postWidgets,
|
||||
);
|
||||
}
|
||||
|
||||
_buildFeedEntry(post, BuildContext context) {
|
||||
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: Column(children: [
|
||||
ListTile(
|
||||
leading: CachedNetworkImage(
|
||||
imageUrl: dotenv.env['STORAGE_URL']! +
|
||||
(post["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,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
post?['author']['name'] ?? '',
|
||||
style: CustomTextStyles.titleMediumSemiBold,
|
||||
),
|
||||
subtitle: Text(
|
||||
DateFormat('dd.MM.yyyy')
|
||||
.format(DateTime.parse(post?['post']['updated_at'])),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutes.blogPostScreen,
|
||||
arguments: post?['post']['id'].toString(),
|
||||
),
|
||||
trailing: PopupMenuButton<String>(
|
||||
onSelected: (value) => print("tap"),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'edit',
|
||||
child: Text('Редактировать'),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
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(),
|
||||
),
|
||||
trailing: GestureDetector(
|
||||
onTap: () => Navigator.pushNamed(
|
||||
context,
|
||||
AppRoutes.blogPostEditScreen,
|
||||
arguments: post?['id'].toString(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Spacer(),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
post?['post']['title'],
|
||||
style: CustomTextStyles.titleMediumBlack,
|
||||
),
|
||||
child: Icon(Icons.edit)),
|
||||
));
|
||||
},
|
||||
);
|
||||
),
|
||||
Spacer(),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8.0),
|
||||
Text(
|
||||
post?['post']['description'],
|
||||
style: CustomTextStyles.bodyMediumBluegray800,
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
CachedNetworkImage(
|
||||
imageUrl: dotenv.env['STORAGE_URL']! +
|
||||
(post?['post']['image'] ?? ''),
|
||||
imageBuilder: (context, imageProvider) => Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8.0)),
|
||||
child: Image(
|
||||
image: imageProvider,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
),
|
||||
SizedBox(height: 16.0),
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 4),
|
||||
child: Row(children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: Icon(Icons.comment_outlined),
|
||||
),
|
||||
Text(
|
||||
"Комментировать",
|
||||
)
|
||||
]),
|
||||
),
|
||||
Spacer(),
|
||||
Row(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Icon(Icons.favorite_outline),
|
||||
),
|
||||
Text(
|
||||
"Нравится",
|
||||
)
|
||||
]),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 4),
|
||||
child: Icon(Icons.share_outlined),
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
),
|
||||
]));
|
||||
}
|
||||
|
||||
Function onTapBack(BuildContext context) {
|
||||
@@ -180,4 +280,35 @@ class FeedScreen extends StatelessWidget {
|
||||
onTapArrowLeft(BuildContext context) {
|
||||
NavigatorService.goBack();
|
||||
}
|
||||
|
||||
_buildMenu(BuildContext context, FeedLoadedState state) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
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: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
ScrollItem(title: 'Все'),
|
||||
ScrollItem(title: 'Рецепты'),
|
||||
ScrollItem(title: 'Диеты'),
|
||||
ScrollItem(title: 'Планы'),
|
||||
ScrollItem(title: 'Записи'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
|
||||
class ScrollItem extends StatelessWidget {
|
||||
final String title;
|
||||
|
||||
const ScrollItem({required this.title});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
margin: EdgeInsets.only(right: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: CustomTextStyles.titleMediumSemiBold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
151
lib/presentation/feed/feed_screen/widgets/feed_search_view.dart
Normal file
151
lib/presentation/feed/feed_screen/widgets/feed_search_view.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
|
||||
class CustomSearchView extends StatelessWidget {
|
||||
CustomSearchView(
|
||||
{Key? key,
|
||||
this.alignment,
|
||||
this.width,
|
||||
this.scrollPadding,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
this.textStyle,
|
||||
this.textInputType = TextInputType.text,
|
||||
this.maxLines,
|
||||
this.hintText,
|
||||
this.hintStyle,
|
||||
this.prefix,
|
||||
this.prefixConstraints,
|
||||
this.suffix,
|
||||
this.suffixConstraints,
|
||||
this.contentPadding,
|
||||
this.borderDecoration,
|
||||
this.fillColor,
|
||||
this.filled = true,
|
||||
this.validator,
|
||||
this.onChanged})
|
||||
: super(
|
||||
key: key,
|
||||
);
|
||||
|
||||
final Alignment? alignment;
|
||||
|
||||
final double? width;
|
||||
|
||||
final TextEditingController? scrollPadding;
|
||||
|
||||
final TextEditingController? controller;
|
||||
|
||||
final FocusNode? focusNode;
|
||||
|
||||
final bool? autofocus;
|
||||
|
||||
final TextStyle? textStyle;
|
||||
|
||||
final TextInputType? textInputType;
|
||||
|
||||
final int? maxLines;
|
||||
|
||||
final String? hintText;
|
||||
|
||||
final TextStyle? hintStyle;
|
||||
|
||||
final Widget? prefix;
|
||||
|
||||
final BoxConstraints? prefixConstraints;
|
||||
|
||||
final Widget? suffix;
|
||||
|
||||
final BoxConstraints? suffixConstraints;
|
||||
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
final InputBorder? borderDecoration;
|
||||
|
||||
final Color? fillColor;
|
||||
|
||||
final bool? filled;
|
||||
|
||||
final FormFieldValidator<String>? validator;
|
||||
|
||||
final Function(String)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return alignment != null
|
||||
? Align(
|
||||
alignment: alignment ?? Alignment.center,
|
||||
child: searchViewWidget(context))
|
||||
: searchViewWidget(context);
|
||||
}
|
||||
|
||||
searchViewWidget(BuildContext context) => SizedBox(
|
||||
width: width ?? double.maxFinite,
|
||||
child: TextFormField(
|
||||
scrollPadding:
|
||||
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
onTapOutside: (event) => onTapOutside(),
|
||||
autofocus: autofocus!,
|
||||
style: textStyle ?? CustomTextStyles.bodySmallMulishBluegray800,
|
||||
keyboardType: textInputType,
|
||||
maxLines: maxLines ?? 1,
|
||||
decoration: decoration,
|
||||
validator: validator,
|
||||
onChanged: (String value) => onChanged?.call(value),
|
||||
),
|
||||
);
|
||||
|
||||
void onTapOutside() {
|
||||
if (focusNode != null) {
|
||||
focusNode?.unfocus();
|
||||
} else {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
}
|
||||
|
||||
InputDecoration get decoration => InputDecoration(
|
||||
hintText: hintText ?? "",
|
||||
hintStyle: hintStyle ?? CustomTextStyles.bodySmallMulishBluegray800,
|
||||
prefixIcon: prefix ??
|
||||
SizedBox(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Icon(Icons.search)),
|
||||
),
|
||||
prefixIconConstraints: prefixConstraints ??
|
||||
BoxConstraints(
|
||||
maxHeight: 40.v,
|
||||
),
|
||||
suffixIconConstraints: suffixConstraints ??
|
||||
BoxConstraints(
|
||||
maxHeight: 40.v,
|
||||
),
|
||||
isDense: true,
|
||||
contentPadding: contentPadding ??
|
||||
EdgeInsets.only(
|
||||
top: 11.v,
|
||||
right: 11.h,
|
||||
bottom: 11.v,
|
||||
),
|
||||
fillColor: fillColor ?? Colors.grey[200],
|
||||
filled: filled,
|
||||
border: borderDecoration ??
|
||||
OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.h),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: borderDecoration ??
|
||||
OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.h),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
focusedBorder: borderDecoration ??
|
||||
OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8.h),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user