diff --git a/android/.kotlin/errors/errors-1733227862976.log b/android/.kotlin/errors/errors-1733227862976.log new file mode 100644 index 00000000..1219b509 --- /dev/null +++ b/android/.kotlin/errors/errors-1733227862976.log @@ -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 + diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 6facfbb2..c7d39c15 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -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().runInitialization(); + await sl().runInitialization(); return null; }, pageBuilder: (context, state) => NoTransitionPage(child: ProfileScreen()), diff --git a/lib/features/auth/auth_screen/auth_screen.dart b/lib/features/auth/auth_screen/auth_screen.dart index 87bc13dd..220d61a5 100644 --- a/lib/features/auth/auth_screen/auth_screen.dart +++ b/lib/features/auth/auth_screen/auth_screen.dart @@ -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 { final token = res['token']; await TokenStorage.saveTokenInStorage(token); GoRouter.of(context).go('/my-profile-screen'); + sl().getMyProfile(); } else { showDialog( context: context, diff --git a/lib/features/blog_post/presentation/pages/blog_post_screen.dart b/lib/features/blog_post/presentation/pages/blog_post_screen.dart index dd9b1c78..3f88ece9 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -304,7 +304,7 @@ class _BlogPostScreenState extends State { isLiked ? await context.read().likePost(widget.blogPost.id) : await context.read().unlikedPost(widget.blogPost.id); - await context.read().loadFeedBlogPostList(); + sl().updateFeedBlogPostList(widget.blogPost, isLiked); }, ), SizedBox(width: 8), diff --git a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart index 802f9a18..2af91d22 100644 --- a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart +++ b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart @@ -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 { required this.feedRepository, }) : super(const FeedState.loading()); + updateFeedBlogPostList(FeedBlogPostModel feedBlogPostModel, bool wasLikedByMe) { + List 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 { diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 611a9015..c439de95 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -184,7 +184,7 @@ class _FeedScreenState extends State { 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); diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart index ee2d66aa..9d796fa6 100644 --- a/lib/features/feed/presentation/widgets/blog_post_widget.dart +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -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 { 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( + 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().likePost(widget.feedBlogPostModel.id) - : await context.read().unlikedPost(widget.feedBlogPostModel.id); - await context.read().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().likePost(state.feedBlogPostList[widget.index].id) + : await context.read().unlikedPost(state.feedBlogPostList[widget.index].id); + await context.read().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(); + }, ); } } diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 36d6628a..81a7cc7a 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -122,11 +122,11 @@ class _ProfileScreenState extends State { ), ), _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 { 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, diff --git a/lib/main.dart b/lib/main.dart index 195c45de..97522447 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,7 +28,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider(create: (context) => sl()), + BlocProvider(create: (context) => sl()..runInitialization()), BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()), BlocProvider(create: (context) => sl()..loadFeedBlogPostList()),