forked from eatvio/eatvio_app
refactor: scroll
This commit is contained in:
67
assets/icons/filter.svg
Normal file
67
assets/icons/filter.svg
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Creator: CorelDRAW X8 -->
|
||||
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
width="500px"
|
||||
height="500px"
|
||||
version="1.1"
|
||||
style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5000 5000"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<style
|
||||
type="text/css"
|
||||
id="style1">
|
||||
<![CDATA[
|
||||
.fil0 {fill:#43A6F2;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g
|
||||
id="Слой_x0020_1">
|
||||
<metadata
|
||||
id="CorelCorpID_0Corel-Layer" />
|
||||
|
||||
<g
|
||||
id="g3"><path
|
||||
class="fil0"
|
||||
d="m 1560.7457,700.42366 c 61.6477,182.17783 43.4185,372.49804 -35.5334,532.21314 -78.7129,160.4212 -219.0937,289.7702 -401.2715,351.4179 -62.8442,21.2661 -125.73139,33.1029 -188.42249,36.2168 l -257.58342,-35.6034 c -29.27533,-9.7676 -58.56148,-21.8927 -86.20715,-36.1468 C 431.30648,1469.8083 301.9575,1329.4275 240.3098,1147.2497 178.66221,965.07202 196.18518,774.99073 275.84324,615.03669 354.79505,455.32156 494.9369,325.26645 677.11471,263.61876 c 182.1778,-61.64759 372.49799,-43.41849 532.21309,35.53343 159.7151,78.95191 289.7702,219.09376 351.4179,401.27147 z m -268.9631,416.82184 c 48.814,-99.9374 60.5318,-218.8006 21.8228,-333.19127 C 1275.1354,670.36962 1193.4069,582.341 1093.9367,532.58196 993.99911,483.76798 874.42991,472.28915 760.7452,510.75919 646.35454,549.46814 559.03194,630.95776 509.27291,730.42789 c -49.52012,100.1764 -60.29281,219.50675 -21.82277,333.19131 38.70906,114.3907 119.49243,201.9522 219.6688,251.4723 100.17629,49.5201 218.80067,60.5317 333.19136,21.8228 113.6846,-38.4702 201.9521,-119.4924 251.4723,-219.6688 z"
|
||||
id="path1-2-61"
|
||||
style="clip-rule:evenodd;fill:#43a6f2;fill-rule:nonzero;stroke-width:0.745448;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision" /><rect
|
||||
style="fill:#43a6f2;fill-opacity:1;stroke:#000000;stroke-width:25.7141;stroke-opacity:0"
|
||||
id="rect3"
|
||||
width="3217.4019"
|
||||
height="281.70447"
|
||||
x="1668.475"
|
||||
y="853.69214" /></g><g
|
||||
id="g3-3"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
||||
transform="matrix(-1.039655,0,0,1.039655,5289.2522,1773.6118)"><path
|
||||
class="fil0"
|
||||
d="m 1675.5155,461.38571 c 59.2963,175.22913 41.7624,358.29003 -34.1781,511.91323 -75.7106,154.30236 -210.7369,278.71766 -385.966,338.01396 -60.4472,20.4549 -120.9357,31.8403 -181.2356,34.8354 l -247.7586,-34.2454 c -28.1587,-9.3951 -56.3278,-21.0577 -82.919,-34.7681 -154.3024,-75.7107 -278.7177,-210.737 -338.014,-385.96606 -59.2962,-175.229 -42.4416,-358.06013 34.1781,-511.91313 75.9404,-153.6232 210.7369,-278.7177 385.966,-338.013995 175.2291,-59.2962 358.29,-41.76240041 511.9132,34.1781 153.6232,75.940495 278.7177,210.736995 338.014,385.965995 z m -258.7042,400.92323 c 46.9521,-96.1255 58.2229,-210.455 20.9904,-320.48253 -37.0027,-109.3484 -115.6138,-194.0194 -211.29,-241.8805 -96.1257,-46.9521 -211.1343,-57.9931 -320.48278,-20.9904 -110.02752,37.2325 -194.01942,115.6139 -241.88052,211.29 -47.6313,96.35543 -57.9931,211.13423 -20.9904,320.48263 37.2326,110.0275 114.9347,194.24916 211.2901,241.88046 96.35532,47.6313 210.4551,58.2229 320.4826,20.9904 109.3484,-37.0028 194.2492,-114.93466 241.8806,-211.29006 z"
|
||||
id="path1-2-61-6"
|
||||
style="clip-rule:evenodd;fill:#43a6f2;fill-rule:nonzero;stroke-width:0.717015;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision" /><rect
|
||||
style="fill:#43a6f2;fill-opacity:1;stroke:#000000;stroke-width:24.7333;stroke-opacity:0"
|
||||
id="rect3-1"
|
||||
width="3094.6824"
|
||||
height="270.95956"
|
||||
x="1779.1356"
|
||||
y="608.80817" /></g><g
|
||||
id="g3-1"
|
||||
style="clip-rule:evenodd;fill-rule:evenodd;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision"
|
||||
transform="matrix(1.039655,0,0,1.039655,-155.31078,3382.0033)"><path
|
||||
class="fil0"
|
||||
d="m 1675.5155,461.38571 c 59.2963,175.22913 41.7624,358.29003 -34.1781,511.91323 -75.7106,154.30236 -210.7369,278.71766 -385.966,338.01396 -60.4472,20.4549 -120.9357,31.8403 -181.2356,34.8354 l -247.7586,-34.2454 c -28.1587,-9.3951 -56.3278,-21.0577 -82.919,-34.7681 -154.3024,-75.7107 -278.7177,-210.737 -338.014,-385.96606 -59.2962,-175.229 -42.4416,-358.06013 34.1781,-511.91313 75.9404,-153.6232 210.7369,-278.7177 385.966,-338.013995 175.2291,-59.2962 358.29,-41.76240041 511.9132,34.1781 153.6232,75.940495 278.7177,210.736995 338.014,385.965995 z m -258.7042,400.92323 c 46.9521,-96.1255 58.2229,-210.455 20.9904,-320.48253 -37.0027,-109.3484 -115.6138,-194.0194 -211.29,-241.8805 -96.1257,-46.9521 -211.1343,-57.9931 -320.48278,-20.9904 -110.02752,37.2325 -194.01942,115.6139 -241.88052,211.29 -47.6313,96.35543 -57.9931,211.13423 -20.9904,320.48263 37.2326,110.0275 114.9347,194.24916 211.2901,241.88046 96.35532,47.6313 210.4551,58.2229 320.4826,20.9904 109.3484,-37.0028 194.2492,-114.93466 241.8806,-211.29006 z"
|
||||
id="path1-2-61-9"
|
||||
style="clip-rule:evenodd;fill:#43a6f2;fill-rule:nonzero;stroke-width:0.717015;image-rendering:optimizeQuality;shape-rendering:geometricPrecision;text-rendering:geometricPrecision" /><rect
|
||||
style="fill:#43a6f2;fill-opacity:1;stroke:#000000;stroke-width:24.7333;stroke-opacity:0"
|
||||
id="rect3-4"
|
||||
width="3094.6824"
|
||||
height="270.95956"
|
||||
x="1779.1356"
|
||||
y="608.80817" /></g></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
@@ -1,22 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Creator: CorelDRAW X8 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="500px" height="500px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5000 5000"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
width="500px"
|
||||
height="500px"
|
||||
version="1.1"
|
||||
style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5000 5000"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<style
|
||||
type="text/css"
|
||||
id="style1">
|
||||
<![CDATA[
|
||||
.fil1 {fill:none}
|
||||
.fil0 {fill:#43A6F2;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Слой_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_3124155549248">
|
||||
<path class="fil0" d="M2291 4109c29,26 61,46 97,60 35,14 73,21 112,21 39,0 77,-7 112,-21 37,-15 71,-36 100,-63l3 -2 0 0 1542 -1372c126,-112 225,-238 292,-379 66,-140 101,-298 101,-475 0,-297 -112,-566 -293,-760 -178,-190 -424,-308 -694,-308 -271,0 -517,118 -695,308 -181,194 -293,463 -293,760 0,97 -78,175 -175,175 -97,0 -175,-78 -175,-175 0,-297 -112,-566 -293,-760 -178,-190 -424,-308 -695,-308 -270,0 -516,118 -694,308 -181,194 -293,463 -293,760 0,177 35,335 101,475 67,141 166,267 292,379l1542 1372 6 5zm-32 385c-74,-29 -142,-72 -202,-126l-1546 -1375c-161,-143 -288,-306 -375,-491 -89,-187 -136,-394 -136,-624 0,-389 148,-742 388,-998 242,-260 578,-420 949,-420 372,0 708,160 950,420 83,88 154,188 213,297 59,-109 130,-209 213,-297 242,-260 578,-420 950,-420 371,0 707,160 949,420 240,256 388,609 388,998 0,230 -47,437 -136,624 -87,185 -214,348 -375,491l-1542 1372 0 0c-61,55 -131,99 -206,129 -75,30 -157,46 -241,46 -84,0 -166,-16 -241,-46z"/>
|
||||
<rect class="fil1" width="5000" height="5000"/>
|
||||
<g
|
||||
id="Слой_x0020_1">
|
||||
<metadata
|
||||
id="CorelCorpID_0Corel-Layer" />
|
||||
<g
|
||||
id="_3124155549248">
|
||||
<path
|
||||
class="fil0"
|
||||
d="M2291 4109c29,26 61,46 97,60 35,14 73,21 112,21 39,0 77,-7 112,-21 37,-15 71,-36 100,-63l3 -2 0 0 1542 -1372c126,-112 225,-238 292,-379 66,-140 101,-298 101,-475 0,-297 -112,-566 -293,-760 -178,-190 -424,-308 -694,-308 -271,0 -517,118 -695,308 -181,194 -293,463 -293,760 0,97 -78,175 -175,175 -97,0 -175,-78 -175,-175 0,-297 -112,-566 -293,-760 -178,-190 -424,-308 -695,-308 -270,0 -516,118 -694,308 -181,194 -293,463 -293,760 0,177 35,335 101,475 67,141 166,267 292,379l1542 1372 6 5zm-32 385c-74,-29 -142,-72 -202,-126l-1546 -1375c-161,-143 -288,-306 -375,-491 -89,-187 -136,-394 -136,-624 0,-389 148,-742 388,-998 242,-260 578,-420 949,-420 372,0 708,160 950,420 83,88 154,188 213,297 59,-109 130,-209 213,-297 242,-260 578,-420 950,-420 371,0 707,160 949,420 240,256 388,609 388,998 0,230 -47,437 -136,624 -87,185 -214,348 -375,491l-1542 1372 0 0c-61,55 -131,99 -206,129 -75,30 -157,46 -241,46 -84,0 -166,-16 -241,-46z"
|
||||
id="path1" />
|
||||
<rect
|
||||
class="fil1"
|
||||
width="5000"
|
||||
height="5000"
|
||||
id="rect1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,22 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Creator: CorelDRAW X8 -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="500px" height="500px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5000 5000"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
width="500px"
|
||||
height="500px"
|
||||
version="1.1"
|
||||
style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5000 5000"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1">
|
||||
<style
|
||||
type="text/css"
|
||||
id="style1">
|
||||
<![CDATA[
|
||||
.fil1 {fill:none}
|
||||
.fil0 {fill:#43A6F2}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Слой_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_3124216520000">
|
||||
<path class="fil0" d="M2259 4494c-74,-29 -142,-72 -202,-126l-1546 -1375c-161,-143 -288,-306 -375,-491 -89,-187 -136,-394 -136,-624 0,-389 148,-742 388,-998 242,-260 578,-420 949,-420 372,0 708,160 950,420 83,88 155,188 213,297 59,-109 130,-209 213,-297 242,-260 578,-420 950,-420 371,0 707,160 949,420 240,256 388,609 388,998 0,230 -47,437 -136,624 -87,185 -214,348 -375,491l-1542 1372 0 0c-61,55 -131,99 -206,129 -75,30 -157,46 -241,46 -84,0 -166,-16 -241,-46z"/>
|
||||
<rect class="fil1" width="5000" height="5000"/>
|
||||
<g
|
||||
id="Слой_x0020_1">
|
||||
<metadata
|
||||
id="CorelCorpID_0Corel-Layer" />
|
||||
<g
|
||||
id="_3124216520000">
|
||||
<path
|
||||
class="fil0"
|
||||
d="M2259 4494c-74,-29 -142,-72 -202,-126l-1546 -1375c-161,-143 -288,-306 -375,-491 -89,-187 -136,-394 -136,-624 0,-389 148,-742 388,-998 242,-260 578,-420 949,-420 372,0 708,160 950,420 83,88 155,188 213,297 59,-109 130,-209 213,-297 242,-260 578,-420 950,-420 371,0 707,160 949,420 240,256 388,609 388,998 0,230 -47,437 -136,624 -87,185 -214,348 -375,491l-1542 1372 0 0c-61,55 -131,99 -206,129 -75,30 -157,46 -241,46 -84,0 -166,-16 -241,-46z"
|
||||
id="path1" />
|
||||
<rect
|
||||
class="fil1"
|
||||
width="5000"
|
||||
height="5000"
|
||||
id="rect1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -2,11 +2,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/models/comment_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||
import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -31,7 +31,6 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
commentsCubit.getBlogPostComments(widget.blogPost.id);
|
||||
isLiked = widget.blogPost.wasLikedByMe;
|
||||
totalLikes = widget.blogPost.totalLikes;
|
||||
super.initState();
|
||||
@@ -40,8 +39,20 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<CommentsCubit>(
|
||||
create: (BuildContext context) => commentsCubit,
|
||||
child: BlocBuilder<CommentsCubit, CommentsState>(
|
||||
create: (BuildContext context) => commentsCubit
|
||||
..getComments(
|
||||
widget.blogPost.id.toString(),
|
||||
CommentSourceType.blogPost,
|
||||
true,
|
||||
),
|
||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
||||
listener: (BuildContext context, CommentsState state) {
|
||||
if (state is CommentsStateLoaded) {
|
||||
setState(() {
|
||||
replyId = state.replyId;
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, CommentsState state) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
@@ -61,6 +72,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
if (replyId != null)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
replyId = null;
|
||||
textEditingController.clear();
|
||||
@@ -83,7 +95,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
),
|
||||
if (replyId != null) SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: mediaQueryData.size.width - (replyId == null ? 64 : 104),
|
||||
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
|
||||
child: TextFormField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
@@ -137,11 +149,13 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (textEditingController.text.length > 2) {
|
||||
commentsCubit.sendBlogPostComment(
|
||||
id: widget.blogPost.id,
|
||||
commentsCubit.sendComment(
|
||||
id: widget.blogPost.id.toString(),
|
||||
commentText: textEditingController.text,
|
||||
parentCommentId: replyId,
|
||||
commentSourceType: CommentSourceType.blogPost,
|
||||
);
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
replyId = null;
|
||||
@@ -310,7 +324,13 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: CupertinoActivityIndicator(),
|
||||
),
|
||||
if (state is CommentsStateLoaded) _buildComments(state.comment),
|
||||
if (state is CommentsStateLoaded)
|
||||
CommentsWidget(
|
||||
id: widget.blogPost.id.toString(),
|
||||
commentSourceType: CommentSourceType.blogPost,
|
||||
comment: state.comment,
|
||||
replyId: replyId,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -320,102 +340,6 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildComments(CommentModel comment) {
|
||||
return Column(
|
||||
children: [
|
||||
for (int i = 0; i < comment.data.length; i++) ...[
|
||||
_comment(comment.data[i]),
|
||||
if (comment.data[i].childrenComments.isNotEmpty) SizedBox(height: 8),
|
||||
for (int j = 0; j < comment.data[i].childrenComments.length; j++)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40),
|
||||
child: _comment(
|
||||
comment.data[i].childrenComments[j],
|
||||
12,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_comment(Comment comment, [double radius = 16]) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: replyId == comment.id ? appTheme.blueA400.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(comment.authorProfileIcon),
|
||||
radius: radius,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
comment.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
comment.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
comment.createdAt.format('dd MMMM', 'ru'),
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyId = comment.id;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Ответить',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_iconButton({required IconData icon, String? text, Function()? onTap, Color? iconColor, Color? backgroundColor, Color? textColor}) {
|
||||
return InkWell(
|
||||
onTap: onTap ?? () {},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
@@ -7,7 +8,6 @@ import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lazy_load_scrollview/lazy_load_scrollview.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
final String chatId;
|
||||
@@ -25,6 +25,24 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final scrollController = ScrollController();
|
||||
final TextEditingController textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController.addListener(_onScroll);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_onScroll() {
|
||||
if (scrollController.offset >= scrollController.position.maxScrollExtent && !scrollController.position.outOfRange) {
|
||||
context.read<ChatRoomCubit>().loadNextPage(widget.chatId);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<ChatRoomCubit>(
|
||||
create: (BuildContext context) => sl()..initChat(widget.chatId),
|
||||
@@ -116,40 +134,35 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
child: Container(
|
||||
color: Color(0xFFF0F2F5),
|
||||
width: double.maxFinite,
|
||||
child: LazyLoadScrollView(
|
||||
onEndOfPage: () => context.read<ChatRoomCubit>().loadNextPage(widget.chatId),
|
||||
isLoading: state.isLoading,
|
||||
scrollOffset: MediaQuery.of(context).size.height.toInt(),
|
||||
child: CustomScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
controller: scrollController,
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index < state.chatMessages.length) {
|
||||
return ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(child: CupertinoActivityIndicator()),
|
||||
);
|
||||
}
|
||||
},
|
||||
childCount: state.chatMessages.length + (state.isLoading ? 1 : 0),
|
||||
),
|
||||
child: CustomScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
controller: scrollController,
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index < state.chatMessages.length) {
|
||||
return ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(child: CupertinoActivityIndicator()),
|
||||
);
|
||||
}
|
||||
},
|
||||
childCount: state.chatMessages.length + (state.isLoading ? 1 : 0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/models/comment_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||
import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -28,17 +27,23 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
int? replyId;
|
||||
final textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
commentsCubit.getDietsComments(widget.diet.id);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<CommentsCubit>(
|
||||
create: (BuildContext context) => commentsCubit,
|
||||
child: BlocBuilder<CommentsCubit, CommentsState>(
|
||||
create: (BuildContext context) => commentsCubit
|
||||
..getComments(
|
||||
widget.diet.id.toString(),
|
||||
CommentSourceType.diet,
|
||||
true,
|
||||
),
|
||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
||||
listener: (BuildContext context, CommentsState state) {
|
||||
if (state is CommentsStateLoaded) {
|
||||
setState(() {
|
||||
replyId = state.replyId;
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, CommentsState state) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
@@ -80,6 +85,7 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
if (replyId != null)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
replyId = null;
|
||||
textEditingController.clear();
|
||||
@@ -156,11 +162,13 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (textEditingController.text.length > 2) {
|
||||
commentsCubit.sendDietsComment(
|
||||
id: widget.diet.id,
|
||||
commentsCubit.sendComment(
|
||||
id: widget.diet.id.toString(),
|
||||
commentText: textEditingController.text,
|
||||
parentCommentId: replyId,
|
||||
commentSourceType: CommentSourceType.diet,
|
||||
);
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
replyId = null;
|
||||
@@ -187,25 +195,6 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
// floatingActionButton: SizedBox(
|
||||
// width: MediaQuery.of(context).size.width - 32,
|
||||
// child: FloatingActionButton.extended(
|
||||
// heroTag: 'nextButtonRegistration',
|
||||
// highlightElevation: 0,
|
||||
// elevation: 0,
|
||||
// focusElevation: 0,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// ),
|
||||
// backgroundColor: appTheme.greenA700,
|
||||
// onPressed: () {},
|
||||
// label: Text(
|
||||
// 'Начать диету',
|
||||
// style: theme.textTheme.titleSmall,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
appBar: AppBar(
|
||||
actions: [
|
||||
InkWell(
|
||||
@@ -340,7 +329,13 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: CupertinoActivityIndicator(),
|
||||
),
|
||||
if (state is CommentsStateLoaded) _buildComments(state.comment),
|
||||
if (state is CommentsStateLoaded)
|
||||
CommentsWidget(
|
||||
id: widget.diet.id.toString(),
|
||||
commentSourceType: CommentSourceType.diet,
|
||||
comment: state.comment,
|
||||
replyId: replyId,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -446,100 +441,4 @@ class _AboutDietScreenState extends State<AboutDietScreen> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_buildComments(CommentModel comment) {
|
||||
return Column(
|
||||
children: [
|
||||
for (int i = 0; i < comment.data.length; i++) ...[
|
||||
_comment(comment.data[i]),
|
||||
if (comment.data[i].childrenComments.isNotEmpty)
|
||||
for (int j = 0; j < comment.data[i].childrenComments.length; j++)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40),
|
||||
child: _comment(
|
||||
comment.data[i].childrenComments[j],
|
||||
12,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_comment(Comment diet, [double radius = 16]) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: replyId == diet.id ? appTheme.blueA400.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(diet.authorProfileIcon),
|
||||
radius: radius,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
diet.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
diet.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
diet.createdAt.format('dd MMMM', 'ru'),
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyId = diet.id;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Ответить',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
|
||||
class CommentsCubit extends Cubit<CommentsState> {
|
||||
@@ -9,79 +10,60 @@ class CommentsCubit extends Cubit<CommentsState> {
|
||||
required this.feedRepository,
|
||||
}) : super(CommentsState());
|
||||
|
||||
getBlogPostComments(int id) async {
|
||||
emit(CommentsState.loading());
|
||||
setReplyId(int? replyId) {
|
||||
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
|
||||
}
|
||||
|
||||
getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
|
||||
if (isFirstFetch) emit(CommentsState.loading());
|
||||
try {
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getBlogPostComments(id)));
|
||||
switch (commentSourceType) {
|
||||
case CommentSourceType.blogPost:
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getBlogPostComments(id)));
|
||||
break;
|
||||
case CommentSourceType.diet:
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getDietsComments(id)));
|
||||
break;
|
||||
case CommentSourceType.recipe:
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id)));
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
getRecipesComments(String id) async {
|
||||
emit(CommentsState.loading());
|
||||
try {
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id)));
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
getDietsComments(int id) async {
|
||||
emit(CommentsState.loading());
|
||||
try {
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getDietsComments(id)));
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
sendBlogPostComment({
|
||||
required int id,
|
||||
required String commentText,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
try {
|
||||
await feedRepository.sendBlogPostComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getBlogPostComments(id)));
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
sendRecipesComment({
|
||||
sendComment({
|
||||
required String id,
|
||||
required String commentText,
|
||||
required CommentSourceType commentSourceType,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
try {
|
||||
await feedRepository.sendRecipesComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id)));
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
sendDietsComment({
|
||||
required int id,
|
||||
required String commentText,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
try {
|
||||
await feedRepository.sendDietsComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
emit(CommentsState.loaded(comment: await feedRepository.getDietsComments(id)));
|
||||
switch (commentSourceType) {
|
||||
case CommentSourceType.blogPost:
|
||||
await feedRepository.sendBlogPostComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
break;
|
||||
case CommentSourceType.diet:
|
||||
await feedRepository.sendDietsComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
break;
|
||||
case CommentSourceType.recipe:
|
||||
await feedRepository.sendRecipesComment(
|
||||
id: id,
|
||||
commentText: commentText,
|
||||
parentCommentId: parentCommentId,
|
||||
);
|
||||
break;
|
||||
}
|
||||
getComments(id, commentSourceType);
|
||||
} catch (e) {
|
||||
emit(CommentsState.error(errorMessage: e.toString()));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ abstract class CommentsState with _$CommentsState {
|
||||
|
||||
const factory CommentsState.loaded({
|
||||
required CommentModel comment,
|
||||
int? replyId,
|
||||
}) = CommentsStateLoaded;
|
||||
|
||||
const factory CommentsState.error({
|
||||
|
||||
@@ -20,7 +20,7 @@ mixin _$CommentsState {
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(CommentModel comment) loaded,
|
||||
required TResult Function(CommentModel comment, int? replyId) loaded,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@@ -28,7 +28,7 @@ mixin _$CommentsState {
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(CommentModel comment)? loaded,
|
||||
TResult? Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@@ -36,7 +36,7 @@ mixin _$CommentsState {
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(CommentModel comment)? loaded,
|
||||
TResult Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
@@ -126,7 +126,7 @@ class _$CommentsStateImpl implements _CommentsState {
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(CommentModel comment) loaded,
|
||||
required TResult Function(CommentModel comment, int? replyId) loaded,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return $default();
|
||||
@@ -137,7 +137,7 @@ class _$CommentsStateImpl implements _CommentsState {
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(CommentModel comment)? loaded,
|
||||
TResult? Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return $default?.call();
|
||||
@@ -148,7 +148,7 @@ class _$CommentsStateImpl implements _CommentsState {
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(CommentModel comment)? loaded,
|
||||
TResult Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -241,7 +241,7 @@ class _$CommentsStateLoadingImpl implements CommentsStateLoading {
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(CommentModel comment) loaded,
|
||||
required TResult Function(CommentModel comment, int? replyId) loaded,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return loading();
|
||||
@@ -252,7 +252,7 @@ class _$CommentsStateLoadingImpl implements CommentsStateLoading {
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(CommentModel comment)? loaded,
|
||||
TResult? Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return loading?.call();
|
||||
@@ -263,7 +263,7 @@ class _$CommentsStateLoadingImpl implements CommentsStateLoading {
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(CommentModel comment)? loaded,
|
||||
TResult Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -321,7 +321,7 @@ abstract class _$$CommentsStateLoadedImplCopyWith<$Res> {
|
||||
$Res Function(_$CommentsStateLoadedImpl) then) =
|
||||
__$$CommentsStateLoadedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({CommentModel comment});
|
||||
$Res call({CommentModel comment, int? replyId});
|
||||
|
||||
$CommentModelCopyWith<$Res> get comment;
|
||||
}
|
||||
@@ -338,12 +338,17 @@ class __$$CommentsStateLoadedImplCopyWithImpl<$Res>
|
||||
@override
|
||||
$Res call({
|
||||
Object? comment = null,
|
||||
Object? replyId = freezed,
|
||||
}) {
|
||||
return _then(_$CommentsStateLoadedImpl(
|
||||
comment: null == comment
|
||||
? _value.comment
|
||||
: comment // ignore: cast_nullable_to_non_nullable
|
||||
as CommentModel,
|
||||
replyId: freezed == replyId
|
||||
? _value.replyId
|
||||
: replyId // ignore: cast_nullable_to_non_nullable
|
||||
as int?,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -359,14 +364,16 @@ class __$$CommentsStateLoadedImplCopyWithImpl<$Res>
|
||||
/// @nodoc
|
||||
|
||||
class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
const _$CommentsStateLoadedImpl({required this.comment});
|
||||
const _$CommentsStateLoadedImpl({required this.comment, this.replyId});
|
||||
|
||||
@override
|
||||
final CommentModel comment;
|
||||
@override
|
||||
final int? replyId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CommentsState.loaded(comment: $comment)';
|
||||
return 'CommentsState.loaded(comment: $comment, replyId: $replyId)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -374,11 +381,12 @@ class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$CommentsStateLoadedImpl &&
|
||||
(identical(other.comment, comment) || other.comment == comment));
|
||||
(identical(other.comment, comment) || other.comment == comment) &&
|
||||
(identical(other.replyId, replyId) || other.replyId == replyId));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, comment);
|
||||
int get hashCode => Object.hash(runtimeType, comment, replyId);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@@ -392,10 +400,10 @@ class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(CommentModel comment) loaded,
|
||||
required TResult Function(CommentModel comment, int? replyId) loaded,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return loaded(comment);
|
||||
return loaded(comment, replyId);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -403,10 +411,10 @@ class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(CommentModel comment)? loaded,
|
||||
TResult? Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return loaded?.call(comment);
|
||||
return loaded?.call(comment, replyId);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -414,12 +422,12 @@ class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(CommentModel comment)? loaded,
|
||||
TResult Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loaded != null) {
|
||||
return loaded(comment);
|
||||
return loaded(comment, replyId);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
@@ -463,10 +471,12 @@ class _$CommentsStateLoadedImpl implements CommentsStateLoaded {
|
||||
}
|
||||
|
||||
abstract class CommentsStateLoaded implements CommentsState {
|
||||
const factory CommentsStateLoaded({required final CommentModel comment}) =
|
||||
_$CommentsStateLoadedImpl;
|
||||
const factory CommentsStateLoaded(
|
||||
{required final CommentModel comment,
|
||||
final int? replyId}) = _$CommentsStateLoadedImpl;
|
||||
|
||||
CommentModel get comment;
|
||||
int? get replyId;
|
||||
@JsonKey(ignore: true)
|
||||
_$$CommentsStateLoadedImplCopyWith<_$CommentsStateLoadedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
@@ -540,7 +550,7 @@ class _$CommentsStateErrorImpl implements CommentsStateError {
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(CommentModel comment) loaded,
|
||||
required TResult Function(CommentModel comment, int? replyId) loaded,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return error(errorMessage);
|
||||
@@ -551,7 +561,7 @@ class _$CommentsStateErrorImpl implements CommentsStateError {
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(CommentModel comment)? loaded,
|
||||
TResult? Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return error?.call(errorMessage);
|
||||
@@ -562,7 +572,7 @@ class _$CommentsStateErrorImpl implements CommentsStateError {
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(CommentModel comment)? loaded,
|
||||
TResult Function(CommentModel comment, int? replyId)? loaded,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
|
||||
class FeedCubit extends Cubit<FeedState> {
|
||||
final FeedRepository feedRepository;
|
||||
int _currentPage = 1;
|
||||
final int _lastPage = 7;
|
||||
int currentPage = 1;
|
||||
late int lastPage;
|
||||
bool isLoading = false;
|
||||
|
||||
FeedCubit({required this.feedRepository}) : super(const FeedState.loading());
|
||||
@@ -13,6 +13,7 @@ class FeedCubit extends Cubit<FeedState> {
|
||||
loadFeedBlogPostList() async {
|
||||
try {
|
||||
final feedBlogPosts = await feedRepository.getFeedBlogPosts();
|
||||
lastPage = feedBlogPosts.meta.lastPage;
|
||||
emit(
|
||||
FeedState.loaded(
|
||||
feedBlogPostList: feedBlogPosts.feedBlogPostList,
|
||||
@@ -25,26 +26,29 @@ class FeedCubit extends Cubit<FeedState> {
|
||||
}
|
||||
|
||||
loadNextPage() async {
|
||||
print('start');
|
||||
try {
|
||||
if (_lastPage < _currentPage) {
|
||||
_currentPage++;
|
||||
final feedBlogPosts = await feedRepository.getFeedBlogPosts(page: _currentPage);
|
||||
emit(
|
||||
FeedState.loaded(
|
||||
feedBlogPostList: [
|
||||
...(state as FeedStateLoaded).feedBlogPostList,
|
||||
...feedBlogPosts.feedBlogPostList,
|
||||
],
|
||||
isLoading: false,
|
||||
),
|
||||
);
|
||||
if (currentPage < lastPage && !isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
emit((state as FeedStateLoaded).copyWith(isLoading: isLoading));
|
||||
|
||||
try {
|
||||
if (currentPage < lastPage) {
|
||||
currentPage++;
|
||||
final feedBlogPosts = await feedRepository.getFeedBlogPosts(page: currentPage);
|
||||
isLoading = false;
|
||||
emit(
|
||||
FeedState.loaded(
|
||||
feedBlogPostList: [
|
||||
...(state as FeedStateLoaded).feedBlogPostList,
|
||||
...feedBlogPosts.feedBlogPostList,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
emit(FeedState.error(errorMessage: e.toString()));
|
||||
}
|
||||
} catch (e) {
|
||||
print('error bro');
|
||||
print(e.toString());
|
||||
} finally {
|
||||
print('end');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,27 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
final searchController = TextEditingController();
|
||||
final List<String> chipsLabelList = ['Все', 'Рецепты', 'Диеты', 'Планы питания', 'Консультанты'];
|
||||
int _selectedChipIndex = 0;
|
||||
final blogPostsScrollController = ScrollController();
|
||||
final recipesScrollController = ScrollController();
|
||||
final dietsScrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
blogPostsScrollController.addListener(_onScroll);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
_onScroll() {
|
||||
if (blogPostsScrollController.offset >= blogPostsScrollController.position.maxScrollExtent && !blogPostsScrollController.position.outOfRange) {
|
||||
context.read<FeedCubit>().loadNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
blogPostsScrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -68,6 +89,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
),
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: NestedScrollView(
|
||||
controller: blogPostsScrollController,
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return [
|
||||
SliverAppBar(
|
||||
@@ -108,47 +130,6 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildDiets() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<DietsCubit>().loadDiets(),
|
||||
child: BlocBuilder<DietsCubit, DietsState>(
|
||||
builder: (BuildContext context, DietsState state) {
|
||||
if (state is DietsStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return DietsWidget(diet: state.diets.data[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.diets.data.length,
|
||||
);
|
||||
} else if (state is DietsStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildAllBlogPosts() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<FeedCubit>().loadFeedBlogPostList(),
|
||||
@@ -158,14 +139,17 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == state.feedBlogPostList.length) {
|
||||
return CupertinoActivityIndicator();
|
||||
}
|
||||
return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.feedBlogPostList.length,
|
||||
itemCount: state.isLoading ? state.feedBlogPostList.length + 1 : state.feedBlogPostList.length,
|
||||
);
|
||||
} else if (state is FeedStateError) {
|
||||
return Scaffold(
|
||||
@@ -231,6 +215,47 @@ class _FeedScreenState extends State<FeedScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_buildDiets() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<DietsCubit>().loadDiets(),
|
||||
child: BlocBuilder<DietsCubit, DietsState>(
|
||||
builder: (BuildContext context, DietsState state) {
|
||||
if (state is DietsStateLoaded) {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return DietsWidget(diet: state.diets.data[index]);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return SizedBox(height: 8);
|
||||
},
|
||||
itemCount: state.diets.data.length,
|
||||
);
|
||||
} else if (state is DietsStateError) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Text(state.errorMessage),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
body: Center(
|
||||
child: CupertinoActivityIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_chipGroup() {
|
||||
return PreferredSize(
|
||||
preferredSize: Size(MediaQuery.of(context).size.width, 32),
|
||||
|
||||
@@ -124,6 +124,8 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
|
||||
child: Hero(
|
||||
tag: baseUrl + '/storage/' + widget.feedBlogPostModel.image,
|
||||
child: CachedNetworkImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
imageUrl: baseUrl + '/storage/' + widget.feedBlogPostModel.image,
|
||||
errorWidget: (context, str, object) => SizedBox.shrink(),
|
||||
),
|
||||
|
||||
119
lib/features/feed/presentation/widgets/comments_widget.dart
Normal file
119
lib/features/feed/presentation/widgets/comments_widget.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/models/comment_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum CommentSourceType { blogPost, recipe, diet }
|
||||
|
||||
class CommentsWidget extends StatefulWidget {
|
||||
final String id;
|
||||
final CommentSourceType commentSourceType;
|
||||
final CommentModel comment;
|
||||
final int? replyId;
|
||||
|
||||
const CommentsWidget({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.commentSourceType,
|
||||
required this.comment,
|
||||
this.replyId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CommentsWidget> createState() => _CommentsWidgetState();
|
||||
}
|
||||
|
||||
class _CommentsWidgetState extends State<CommentsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
for (int i = 0; i < widget.comment.data.length; i++) ...[
|
||||
_comment(widget.comment.data[i]),
|
||||
if (widget.comment.data[i].childrenComments.isNotEmpty) SizedBox(height: 8),
|
||||
for (int j = 0; j < widget.comment.data[i].childrenComments.length; j++)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40),
|
||||
child: _comment(widget.comment.data[i].childrenComments[j], 12, true),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_comment(Comment comment, [double radius = 16, bool isChild = false]) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.replyId == comment.id ? appTheme.blueA400.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(comment.authorProfileIcon),
|
||||
radius: radius,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
comment.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
comment.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
comment.createdAt.format('dd MMMM', 'ru'),
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
if (!isChild)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CommentsCubit>().setReplyId(comment.id);
|
||||
},
|
||||
child: Text(
|
||||
'Ответить',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ class _DietsWidgetState extends State<DietsWidget> {
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + widget.diet.image,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
||||
@@ -34,6 +34,8 @@ class _RecipesWidgetState extends State<RecipesWidget> {
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: baseUrl + '/storage/' + widget.recipes.image,
|
||||
fit: BoxFit.fitWidth,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
||||
@@ -40,7 +40,7 @@ class FeedRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentModel> getBlogPostComments(int id) async {
|
||||
Future<CommentModel> getBlogPostComments(String id) async {
|
||||
try {
|
||||
return (await feedService.getBlogPostComments(id: id));
|
||||
} catch (e) {
|
||||
@@ -49,7 +49,7 @@ class FeedRepository {
|
||||
}
|
||||
|
||||
Future<void> sendBlogPostComment({
|
||||
required int id,
|
||||
required String id,
|
||||
required String commentText,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
@@ -64,7 +64,7 @@ class FeedRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<CommentModel> getDietsComments(int id) async {
|
||||
Future<CommentModel> getDietsComments(String id) async {
|
||||
try {
|
||||
return (await feedService.getDietsComments(id: id));
|
||||
} catch (e) {
|
||||
@@ -73,7 +73,7 @@ class FeedRepository {
|
||||
}
|
||||
|
||||
Future<void> sendDietsComment({
|
||||
required int id,
|
||||
required String id,
|
||||
required String commentText,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
|
||||
@@ -27,24 +27,24 @@ abstract class FeedService {
|
||||
|
||||
@GET('/api/blogPosts/{id}/comments/show')
|
||||
Future<CommentModel> getBlogPostComments({
|
||||
@Path() required int id,
|
||||
@Path() required String id,
|
||||
});
|
||||
|
||||
@POST('/api/blogPosts/{id}/comment/create')
|
||||
Future<void> sendBlogPostComment({
|
||||
@Path() required int id,
|
||||
@Path() required String id,
|
||||
@Part() required String body,
|
||||
@Part() int? parentCommentId,
|
||||
});
|
||||
|
||||
@GET('/api/diets/{id}/comments/show')
|
||||
Future<CommentModel> getDietsComments({
|
||||
@Path() required int id,
|
||||
@Path() required String id,
|
||||
});
|
||||
|
||||
@POST('/api/diets/{id}/comment/create')
|
||||
Future<void> sendDietsComment({
|
||||
@Path() required int id,
|
||||
@Path() required String id,
|
||||
@Part() required String body,
|
||||
@Part() int? parentCommentId,
|
||||
});
|
||||
|
||||
@@ -128,7 +128,7 @@ class _FeedService implements FeedService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CommentModel> getBlogPostComments({required int id}) async {
|
||||
Future<CommentModel> getBlogPostComments({required String id}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
@@ -162,7 +162,7 @@ class _FeedService implements FeedService {
|
||||
|
||||
@override
|
||||
Future<void> sendBlogPostComment({
|
||||
required int id,
|
||||
required String id,
|
||||
required String body,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
@@ -201,7 +201,7 @@ class _FeedService implements FeedService {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CommentModel> getDietsComments({required int id}) async {
|
||||
Future<CommentModel> getDietsComments({required String id}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
@@ -235,7 +235,7 @@ class _FeedService implements FeedService {
|
||||
|
||||
@override
|
||||
Future<void> sendDietsComment({
|
||||
required int id,
|
||||
required String id,
|
||||
required String body,
|
||||
int? parentCommentId,
|
||||
}) async {
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_wi
|
||||
import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
final Author? author;
|
||||
@@ -118,13 +120,20 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: SvgPicture.asset(
|
||||
height: 24,
|
||||
width: 24,
|
||||
'assets/icons/share.svg',
|
||||
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
|
||||
GestureDetector(
|
||||
onTap: () => Share.shareUri(
|
||||
Uri.parse(
|
||||
baseUrl + '/ru/profile/' + state.profile.user.slug,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 16),
|
||||
child: SvgPicture.asset(
|
||||
height: 24,
|
||||
width: 24,
|
||||
'assets/icons/share.svg',
|
||||
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -147,18 +156,21 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: 16),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8),
|
||||
width: (MediaQuery.of(context).size.width - 40) / 2,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
'Сообщение',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
GestureDetector(
|
||||
onTap: () => GoRouter.of(context).pushNamed('chatScreen', ),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8),
|
||||
width: (MediaQuery.of(context).size.width - 40) / 2,
|
||||
decoration: BoxDecoration(
|
||||
color: appTheme.blueA400.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
'Сообщение',
|
||||
style: TextStyle(
|
||||
color: appTheme.blueA400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:eatvio_app/core/app_export.dart';
|
||||
import 'package:eatvio_app/features/feed/models/comment_model.dart';
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart' as step;
|
||||
import 'package:eatvio_app/features/feed/models/recipes_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
|
||||
import 'package:eatvio_app/locator_service.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -26,21 +25,24 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
final textEditingController = TextEditingController();
|
||||
int? replyId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
commentsCubit.getRecipesComments(widget.recipes.slug);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<CommentsCubit>(
|
||||
create: (BuildContext context) => commentsCubit,
|
||||
child: BlocBuilder<CommentsCubit, CommentsState>(
|
||||
builder: (BuildContext context, CommentsState state) {
|
||||
if (state is CommentsStateError) {
|
||||
print(state.errorMessage);
|
||||
create: (BuildContext context) => commentsCubit
|
||||
..getComments(
|
||||
widget.recipes.slug,
|
||||
CommentSourceType.recipe,
|
||||
true,
|
||||
),
|
||||
child: BlocConsumer<CommentsCubit, CommentsState>(
|
||||
listener: (BuildContext context, CommentsState state) {
|
||||
if (state is CommentsStateLoaded) {
|
||||
setState(() {
|
||||
replyId = state.replyId;
|
||||
});
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, CommentsState state) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
bottomSheet: Container(
|
||||
@@ -59,6 +61,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
if (replyId != null)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
replyId = null;
|
||||
textEditingController.clear();
|
||||
@@ -81,7 +84,7 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
),
|
||||
if (replyId != null) SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: mediaQueryData.size.width - (replyId == null ? 64 : 104),
|
||||
width: MediaQuery.of(context).size.width - (replyId == null ? 64 : 104),
|
||||
child: TextFormField(
|
||||
onChanged: (_) {
|
||||
setState(() {});
|
||||
@@ -135,11 +138,13 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (textEditingController.text.length > 2) {
|
||||
commentsCubit.sendRecipesComment(
|
||||
commentsCubit.sendComment(
|
||||
id: widget.recipes.slug,
|
||||
commentText: textEditingController.text,
|
||||
parentCommentId: replyId,
|
||||
commentSourceType: CommentSourceType.recipe,
|
||||
);
|
||||
context.read<CommentsCubit>().setReplyId(null);
|
||||
setState(() {
|
||||
textEditingController.clear();
|
||||
replyId = null;
|
||||
@@ -300,17 +305,16 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => _step(widget.recipes.steps[index], index + 1),
|
||||
separatorBuilder: (context, index) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Container(
|
||||
height: 0.4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
separatorBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Container(
|
||||
height: 0.4,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
itemCount: widget.recipes.steps.length,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
@@ -343,7 +347,13 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: CupertinoActivityIndicator(),
|
||||
),
|
||||
if (state is CommentsStateLoaded) _buildComments(state.comment),
|
||||
if (state is CommentsStateLoaded)
|
||||
CommentsWidget(
|
||||
id: widget.recipes.slug,
|
||||
commentSourceType: CommentSourceType.recipe,
|
||||
comment: state.comment,
|
||||
replyId: replyId,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -406,103 +416,4 @@ class _AboutRecipesScreenState extends State<AboutRecipesScreen> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_buildComments(CommentModel comment) {
|
||||
return Column(
|
||||
children: [
|
||||
for (int i = 0; i < comment.data.length; i++) ...[
|
||||
_comment(comment.data[i]),
|
||||
if (comment.data[i].childrenComments.isNotEmpty)
|
||||
for (int j = 0; j < comment.data[i].childrenComments.length; j++)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 40),
|
||||
child: _comment(
|
||||
comment.data[i].childrenComments[j],
|
||||
12,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
_comment(Comment comment, [double radius = 16]) {
|
||||
return Container(
|
||||
width: MediaQuery
|
||||
.of(context)
|
||||
.size
|
||||
.width,
|
||||
decoration: BoxDecoration(
|
||||
color: replyId == comment.id ? appTheme.blueA400.withOpacity(0.1) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
foregroundImage: NetworkImage(comment.authorProfileIcon),
|
||||
radius: radius,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
comment.authorName,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.grey.shade600,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
comment.body,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
comment.createdAt.format('dd MMMM', 'ru'),
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
replyId = comment.id;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Ответить',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ class DietCreateScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildDietCreate(BuildContext context, DietCreateLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -39,7 +39,6 @@ class DietEditScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildDietEdit(BuildContext context, DietEditLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -40,7 +40,6 @@ class DietScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildDiet(BuildContext context, DietLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -43,7 +43,6 @@ class BlogPostEditScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildBlogPostEdit(BuildContext context, BlogPostEditLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Container(
|
||||
|
||||
@@ -38,7 +38,6 @@ class PlanCreateScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildPlanCreate(BuildContext context, PlanCreateLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -39,7 +39,6 @@ class PlanEditScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildPlanEdit(BuildContext context, PlanEditLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -40,7 +40,6 @@ class PlanScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildPlan(BuildContext context, PlanLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -58,7 +58,6 @@ class MyProfileScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildMyProfileScreen(BuildContext context, MyProfileLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
|
||||
@@ -45,7 +45,6 @@ class RecipeEditScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildRecipeEdit(BuildContext context, RecipeEditLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
List<Widget> steps = [];
|
||||
if (state.steps != null) {
|
||||
for (int i = 0; i < state.steps!.length; i++) {
|
||||
|
||||
@@ -44,7 +44,6 @@ class RecipeScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
_buildRecipe(BuildContext context, RecipeLoadedState state) {
|
||||
mediaQueryData = MediaQuery.of(context);
|
||||
List<Widget> steps = [];
|
||||
if (state.steps != null) {
|
||||
for (int i = 0; i < state.steps!.length; i++) {
|
||||
|
||||
@@ -38,11 +38,10 @@ dependencies:
|
||||
dismissible_page: ^1.0.2
|
||||
url_launcher: ^6.3.0
|
||||
easy_debounce: ^2.0.3
|
||||
lazy_load_scrollview: ^1.3.0
|
||||
badges: ^3.1.2
|
||||
google_fonts: ^6.2.1
|
||||
location: ^6.0.2
|
||||
syncfusion_flutter_charts: ^26.2.12
|
||||
syncfusion_flutter_charts: ^26.2.13
|
||||
table_calendar: ^3.1.2
|
||||
flutter_keyboard_visibility: ^6.0.0
|
||||
share_plus: ^10.0.0
|
||||
|
||||
Reference in New Issue
Block a user