fix: feed like sync

This commit is contained in:
Alex
2024-12-04 01:29:02 +03:00
parent 6cf6e32fe6
commit 5e6f4cc84b
9 changed files with 221 additions and 195 deletions

View File

@@ -0,0 +1,4 @@
kotlin version: 2.0.21
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
1. Kotlin compile daemon is ready

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart';
import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart';
@@ -40,7 +41,6 @@ import 'package:eatvio_app/features/settings/presentation/pages/physical_form_sc
import 'package:eatvio_app/features/settings/presentation/pages/pressure_screen.dart';
import 'package:eatvio_app/features/settings/presentation/pages/settings_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class AppRouter {
@@ -77,7 +77,7 @@ class AppRouter {
path: '/my-profile-screen',
name: 'myProfileScreen',
redirect: (context, state) async {
await context.read<InitializationCubit>().runInitialization();
await sl<InitializationCubit>().runInitialization();
return null;
},
pageBuilder: (context, state) => NoTransitionPage(child: ProfileScreen()),

View File

@@ -1,9 +1,11 @@
import 'dart:convert';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -191,6 +193,7 @@ class _AuthScreenState extends State<AuthScreen> {
final token = res['token'];
await TokenStorage.saveTokenInStorage(token);
GoRouter.of(context).go('/my-profile-screen');
sl<ProfileAvatarCubit>().getMyProfile();
} else {
showDialog(
context: context,

View File

@@ -304,7 +304,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
isLiked
? await context.read<FeedCubit>().likePost(widget.blogPost.id)
: await context.read<FeedCubit>().unlikedPost(widget.blogPost.id);
await context.read<FeedCubit>().loadFeedBlogPostList();
sl<FeedCubit>().updateFeedBlogPostList(widget.blogPost, isLiked);
},
),
SizedBox(width: 8),

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -13,6 +14,21 @@ class FeedCubit extends Cubit<FeedState> {
required this.feedRepository,
}) : super(const FeedState.loading());
updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) {
List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
final int index = feedBlogPostList.indexOf(feedBlogPostModel);
feedBlogPostList[index] = feedBlogPostModel.copyWith(
wasLikedByMe: wasLikedByMe,
totalLikes: wasLikedByMe == false ? feedBlogPostList[index].totalLikes - 1 : feedBlogPostList[index].totalLikes + 1,
);
emit(
FeedState.loaded(
feedBlogPostList: feedBlogPostList,
isLoading: isLoading,
),
);
}
loadFeedBlogPostList({
String? query,
}) async {

View File

@@ -184,7 +184,7 @@ class _FeedScreenState extends State<FeedScreen> {
if (index == state.feedBlogPostList.length) {
return const Loader();
}
return BlogPostWidget(feedBlogPostModel: state.feedBlogPostList[index]);
return BlogPostWidget(index: index);
},
separatorBuilder: (context, index) {
return SizedBox(height: 8);

View File

@@ -4,9 +4,9 @@ import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/subscribe_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -16,11 +16,11 @@ import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart';
class BlogPostWidget extends StatefulWidget {
final FeedBlogPostModel feedBlogPostModel;
final int index;
const BlogPostWidget({
super.key,
required this.feedBlogPostModel,
required this.index,
});
@override
@@ -31,206 +31,209 @@ class _BlogPostWidgetState extends State<BlogPostWidget> {
late bool isLiked;
late int totalLikes;
@override
void initState() {
isLiked = widget.feedBlogPostModel.wasLikedByMe;
totalLikes = widget.feedBlogPostModel.totalLikes;
super.initState();
}
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: widget.feedBlogPostModel,
),
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
return BlocBuilder<FeedCubit, FeedState>(
builder: (BuildContext context, FeedState state) {
if (state is FeedStateLoaded) {
isLiked = state.feedBlogPostList[widget.index].wasLikedByMe;
totalLikes = state.feedBlogPostList[widget.index].totalLikes;
return InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.feedBlogPostList[widget.index],
),
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 20,
backgroundColor: Colors.black12,
foregroundImage: NetworkImage(
widget.feedBlogPostModel.author.authorProfileIcon,
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: Colors.black12,
foregroundImage: NetworkImage(
state.feedBlogPostList[widget.index].author.authorProfileIcon,
),
),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.feedBlogPostList[widget.index].author.authorName,
style: TextStyle(
height: 1,
color: Colors.black87,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
'${calculateDifference(state.feedBlogPostList[widget.index].createdAt)} в ${state.feedBlogPostList[widget.index].createdAt.hour.toString().padLeft(2, '0')}:${state.feedBlogPostList[widget.index].createdAt.minute.toString().padLeft(2, '0')}',
style: TextStyle(
height: 1,
color: Colors.black45,
),
),
],
),
const Spacer(),
if (!state.feedBlogPostList[widget.index].author.isEntitySubscribedByMe && !state.feedBlogPostList[widget.index].author.authorIsMe)
SubscribeButtonWidget(
slug: state.feedBlogPostList[widget.index].author.authorSlug,
),
],
),
),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.feedBlogPostModel.author.authorName,
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty)
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
state.feedBlogPostList[widget.index].title,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
if (state.feedBlogPostList[widget.index].title.isNotEmpty && state.feedBlogPostList[widget.index].description.isNotEmpty) SizedBox(height: 8),
if (state.feedBlogPostList[widget.index].description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Html(
data: state.feedBlogPostList[widget.index].description.length > 200
? state.feedBlogPostList[widget.index].description.substring(0, 200)
: state.feedBlogPostList[widget.index].description,
style: {
"*": Style(
color: Colors.black87,
),
},
),
),
if (state.feedBlogPostList[widget.index].description.length > 200)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Читать полностью...',
style: TextStyle(
height: 1,
color: Colors.black87,
fontWeight: FontWeight.w500,
color: appTheme.blueA400,
decoration: TextDecoration.underline,
),
),
const SizedBox(height: 4),
Text(
'${calculateDifference(widget.feedBlogPostModel.createdAt)} в ${widget.feedBlogPostModel.createdAt.hour.toString().padLeft(2, '0')}:${widget.feedBlogPostModel.createdAt.minute.toString().padLeft(2, '0')}',
style: TextStyle(
height: 1,
color: Colors.black45,
),
if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty) SizedBox(height: 8),
GestureDetector(
onTap: () {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: [baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image],
index: 0,
),
),
],
),
const Spacer(),
if (!widget.feedBlogPostModel.author.isEntitySubscribedByMe && !widget.feedBlogPostModel.author.authorIsMe)
SubscribeButtonWidget(
slug: widget.feedBlogPostModel.author.authorSlug,
),
],
),
),
if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text(
widget.feedBlogPostModel.title,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
if (widget.feedBlogPostModel.title.isNotEmpty && widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8),
if (widget.feedBlogPostModel.description.isNotEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Html(
data: widget.feedBlogPostModel.description.length > 200
? widget.feedBlogPostModel.description.substring(0, 200)
: widget.feedBlogPostModel.description,
style: {
"*": Style(
color: Colors.black87,
),
},
),
),
if (widget.feedBlogPostModel.description.length > 200)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'Читать полностью...',
style: TextStyle(
height: 1,
color: appTheme.blueA400,
decoration: TextDecoration.underline,
),
),
),
if (widget.feedBlogPostModel.title.isNotEmpty || widget.feedBlogPostModel.description.isNotEmpty) SizedBox(height: 8),
GestureDetector(
onTap: () {
context.pushTransparentRoute(
rootNavigator: true,
PhotoViewScreen(
imageUrls: [baseUrl + '/storage/' + widget.feedBlogPostModel.image],
index: 0,
),
);
},
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(),
),
),
),
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
FeedIconButton(
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: totalLikes.toString(),
iconColor: isLiked ? Colors.red.withOpacity(0.8) : null,
backgroundColor: isLiked ? Colors.red.withOpacity(0.1) : null,
textColor: isLiked ? Colors.red.withOpacity(0.8) : null,
onTap: () async {
setState(() {
isLiked = !isLiked;
isLiked ? totalLikes++ : totalLikes--;
});
isLiked
? await context.read<FeedCubit>().likePost(widget.feedBlogPostModel.id)
: await context.read<FeedCubit>().unlikedPost(widget.feedBlogPostModel.id);
await context.read<FeedCubit>().loadFeedBlogPostList();
);
},
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
'assets/icons/comment.svg',
height: 20,
width: 20,
colorFilter: ColorFilter.mode(
appTheme.gray600,
BlendMode.srcIn,
),
),
text: widget.feedBlogPostModel.totalComments.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: widget.feedBlogPostModel,
),
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
height: 20,
width: 20,
'assets/icons/share.svg',
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
),
onTap: () => Share.shareUri(
Uri.parse(
baseUrl + '/ru/blog_post/' + widget.feedBlogPostModel.id.toString(),
child: Hero(
tag: baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image,
child: CachedNetworkImage(
fit: BoxFit.fitWidth,
width: MediaQuery.of(context).size.width,
imageUrl: baseUrl + '/storage/' + state.feedBlogPostList[widget.index].image,
errorWidget: (context, str, object) => SizedBox.shrink(),
),
),
),
Spacer(),
Icon(
Icons.visibility_rounded,
size: 16,
color: appTheme.gray500,
),
SizedBox(width: 4),
Text(
widget.feedBlogPostModel.totalViews.toString(),
style: TextStyle(
fontSize: 12,
color: appTheme.gray500,
fontWeight: FontWeight.w400,
SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
FeedIconButton(
icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
text: totalLikes.toString(),
iconColor: isLiked ? Colors.red.withOpacity(0.8) : null,
backgroundColor: isLiked ? Colors.red.withOpacity(0.1) : null,
textColor: isLiked ? Colors.red.withOpacity(0.8) : null,
onTap: () async {
setState(() {
isLiked = !isLiked;
isLiked ? totalLikes++ : totalLikes--;
});
isLiked
? await context.read<FeedCubit>().likePost(state.feedBlogPostList[widget.index].id)
: await context.read<FeedCubit>().unlikedPost(state.feedBlogPostList[widget.index].id);
await context.read<FeedCubit>().loadFeedBlogPostList();
},
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
'assets/icons/comment.svg',
height: 20,
width: 20,
colorFilter: ColorFilter.mode(
appTheme.gray600,
BlendMode.srcIn,
),
),
text: state.feedBlogPostList[widget.index].totalComments.toString(),
onTap: () => GoRouter.of(context).pushNamed(
'blogPostScreen',
extra: state.feedBlogPostList[widget.index],
),
),
SizedBox(width: 8),
FeedIconButton(
svgIcon: SvgPicture.asset(
height: 20,
width: 20,
'assets/icons/share.svg',
colorFilter: ColorFilter.mode(appTheme.gray600, BlendMode.srcIn),
),
onTap: () => Share.shareUri(
Uri.parse(
baseUrl + '/ru/blog_post/' + state.feedBlogPostList[widget.index].id.toString(),
),
),
),
Spacer(),
Icon(
Icons.visibility_rounded,
size: 16,
color: appTheme.gray500,
),
SizedBox(width: 4),
Text(
state.feedBlogPostList[widget.index].totalViews.toString(),
style: TextStyle(
fontSize: 12,
color: appTheme.gray500,
fontWeight: FontWeight.w400,
),
),
],
),
),
SizedBox(height: 8),
],
),
),
SizedBox(height: 8),
],
),
),
);
}
return const SizedBox.shrink();
},
);
}
}

View File

@@ -122,11 +122,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
),
),
_subscriptionsAndSubscribers(state),
if (state.profile.badges.isNotEmpty) _badges(state),
_socialLinks(state),
_education(state),
_diplomasAndCertificates(state),
_activePlans(state),
if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)),
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _socialLinks(state)),
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _education(state)),
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _diplomasAndCertificates(state)),
if (state.profile.role == 'consultant') Padding(padding: EdgeInsets.only(bottom: 16), child: _activePlans(state)),
_blogPosts(state),
],
),
@@ -706,7 +706,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => BlogPostWidget(
feedBlogPostModel: state.blogPosts[index],
index: index,
),
separatorBuilder: (context, index) => SizedBox(height: 8),
itemCount: state.blogPosts.length,

View File

@@ -28,7 +28,7 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<InitializationCubit>(create: (context) => sl<InitializationCubit>()),
BlocProvider<InitializationCubit>(create: (context) => sl<InitializationCubit>()..runInitialization()),
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),