From fef4451f4e70584782e020dc948e5bb52a1ce066 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Wed, 17 Sep 2025 17:00:44 +0300 Subject: [PATCH 1/4] fix(profile): Issue-523 --- ios/Runner.xcodeproj/project.pbxproj | 6 +- ios/Runner/Info.plist | 2 +- lib/core/theme/theme_helper.dart | 2 + .../auth/presentation/pages/auth_screen.dart | 4 ++ .../my_finance/models/card_model.dart | 3 + .../profile/models/profile_model.dart | 1 + .../presentation/pages/profile_screen.dart | 60 +++++++++---------- pubspec.yaml | 2 +- .../services/profile_service_test.dart | 13 ++-- 9 files changed, 49 insertions(+), 44 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1fee690d..8e9edd22 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -388,7 +388,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -539,7 +539,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -578,7 +578,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Distribution: ITVIO, OOO (VL68C36HYS)"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 73; + CURRENT_PROJECT_VERSION = 74; DEVELOPMENT_TEAM = VL68C36HYS; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f0f66aff..7e82c5b2 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -21,7 +21,7 @@ CFBundleSignature ???? CFBundleVersion - 73 + 74 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart index c0c0af29..c202129e 100644 --- a/lib/core/theme/theme_helper.dart +++ b/lib/core/theme/theme_helper.dart @@ -204,6 +204,8 @@ class PrimaryColors { Color get commentBg => const Color(0XFFFFFDD8); Color get cardNotificationsBG => const Color(0xFFA4A4A4); + + Color get grey7B => const Color(0xFF7B7B7B); } PrimaryColors get appTheme => ThemeHelper().themeColor(); diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart index 8a4e711b..ea457d6c 100644 --- a/lib/features/auth/presentation/pages/auth_screen.dart +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_element, duplicate_ignore + import 'dart:async'; import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; @@ -365,6 +367,7 @@ class _AuthScreenState extends State { } } +// ignore: unused_element class _SocialButton extends StatelessWidget { const _SocialButton({required this.onTap, required this.child, required this.backgroundColor, required this.tooltip}); @@ -391,6 +394,7 @@ class _SocialButton extends StatelessWidget { } } +// ignore: unused_element void _onVkPressed() async { final accessToken = await SocialAuthFacade.signInWithVk(); if (accessToken == null) return; diff --git a/lib/features/my_finance/models/card_model.dart b/lib/features/my_finance/models/card_model.dart index 0435ea56..d248cace 100644 --- a/lib/features/my_finance/models/card_model.dart +++ b/lib/features/my_finance/models/card_model.dart @@ -15,6 +15,9 @@ abstract class CardModel with _$CardModel { } bool parseBool(dynamic json) { + if (json == null) { + throw Exception('parseBool: json is null'); + } if (json == 1 || json == true || json == '1') { return true; } diff --git a/lib/features/profile/models/profile_model.dart b/lib/features/profile/models/profile_model.dart index 03fd57f3..17eaa671 100644 --- a/lib/features/profile/models/profile_model.dart +++ b/lib/features/profile/models/profile_model.dart @@ -19,6 +19,7 @@ abstract class ProfileModel with _$ProfileModel { @JsonKey(name: "updated_at") DateTime? updatedAt, @JsonKey(name: "is_public_profile", fromJson: parseBool) required bool isPublicProfile, @JsonKey(name: "is_blocked_by_me", fromJson: parseBool) required bool isBlockedByMe, + @JsonKey(name: "is_blocked_by_author", fromJson: parseBool) required bool isBlockedByAuthor, @JsonKey(name: "is_blocked_by_user", fromJson: parseBool, defaultValue: false) required bool isBlockedByUser, @JsonKey(name: 'is_entity_subscribed_by_me') required bool isEntitySubscribedByMe, @JsonKey(name: "slug") required String slug, diff --git a/lib/features/profile/presentation/pages/profile_screen.dart b/lib/features/profile/presentation/pages/profile_screen.dart index 98966234..11ae9b7a 100644 --- a/lib/features/profile/presentation/pages/profile_screen.dart +++ b/lib/features/profile/presentation/pages/profile_screen.dart @@ -5,6 +5,7 @@ import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/permission_handler/permission_handler.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; @@ -93,30 +94,6 @@ class _ProfileScreenState extends State { isFirstFetch = false; } - // Проверяем, заблокировал ли пользователь меня - if (state.profile.isBlockedByUser) { - return Scaffold( - appBar: AppBar(), - body: Center( - child: Padding( - padding: const EdgeInsets.all(32.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.block, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - 'Пользователь ограничил доступ к своей странице', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[600]), - ), - ], - ), - ), - ), - ); - } - return widget.author == null || widget.author!.authorIsMe ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { return Scaffold( @@ -198,11 +175,34 @@ class _ProfileScreenState extends State { ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: const EdgeInsets.only(bottom: 16), child: _badges(state)), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _socialLinks(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _education(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe) _diplomasAndCertificates(state), - if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !hidePlans && !state.profile.isBlockedByMe) _activePlans(state), - if (state.blogPosts.isNotEmpty) + if (state.profile.isBlockedByAuthor) + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.block, size: 48, color: appTheme.grey7B), + const SizedBox(height: 16), + Center( + child: Text( + 'Пользователь ограничил доступ\nк своей странице', + textAlign: TextAlign.center, + style: TextStyles.textMSemibold.copyWith(color: appTheme.grey7B), + ), + ), + ], + ), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe && !state.profile.isBlockedByAuthor) + _socialLinks(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe && !state.profile.isBlockedByAuthor) + _education(state), + if (state.profile.roles.contains('consultant') && state.consultantInfo != null && !state.profile.isBlockedByMe && !state.profile.isBlockedByAuthor) + _diplomasAndCertificates(state), + if (state.profile.roles.contains('consultant') && + state.consultantInfo != null && + !hidePlans && + !state.profile.isBlockedByMe && + !state.profile.isBlockedByAuthor) + _activePlans(state), + if (state.blogPosts.isNotEmpty && !state.profile.isBlockedByAuthor && !state.profile.isBlockedByMe) const Padding( padding: EdgeInsets.only(left: 8, top: 16, bottom: 8), child: Text( @@ -210,7 +210,7 @@ class _ProfileScreenState extends State { style: TextStyle(color: Color(0xFF323F4B), fontSize: 24, fontWeight: FontWeight.w600), ), ), - _blogPosts(state), + if (state.blogPosts.isNotEmpty && !state.profile.isBlockedByAuthor && !state.profile.isBlockedByMe) _blogPosts(state), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 6861a603..5fec1116 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.2.13+73 +version: 1.2.14+74 publish_to: 'none' environment: diff --git a/test/features/profile/services/profile_service_test.dart b/test/features/profile/services/profile_service_test.dart index 8c666344..5c773bc3 100644 --- a/test/features/profile/services/profile_service_test.dart +++ b/test/features/profile/services/profile_service_test.dart @@ -30,6 +30,7 @@ void main() { badges: [], subscriptions: [], subscribers: [], + isBlockedByAuthor: false, ), ), ); @@ -72,23 +73,17 @@ void main() { }); test('getBlogPostByAuthorSlug', () async { - when(mockProfileService.getBlogPostByAuthorSlug(slug: '')).thenAnswer( - (_) async => [], - ); + when(mockProfileService.getBlogPostByAuthorSlug(slug: '')).thenAnswer((_) async => []); expect(await mockProfileService.getBlogPostByAuthorSlug(slug: 'slug'), isA>()); }); test('getWeight', () async { - when(mockProfileService.getWeight(period: '')).thenAnswer( - (_) async => [], - ); + when(mockProfileService.getWeight(period: '')).thenAnswer((_) async => []); expect(await mockProfileService.getWeight(period: ''), isA>()); }); test('getCircumferences', () async { - when(mockProfileService.getCircumferences(period: '')).thenAnswer( - (_) async => [], - ); + when(mockProfileService.getCircumferences(period: '')).thenAnswer((_) async => []); expect(await mockProfileService.getCircumferences(period: ''), isA>()); }); }); From e6df89c98ad0587f26a2a21c869cb7a0cc1b8449 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Fri, 19 Sep 2025 15:55:07 +0300 Subject: [PATCH 2/4] fix(feed): Issue-591 --- .../presentation/cubit/blog_post_cubit.dart | 22 +++++ .../presentation/pages/blog_post_screen.dart | 90 +++++++++-------- .../widgets/consultation_card_widget.dart | 3 +- .../bloc/feed_cubit/feed_cubit.dart | 14 ++- .../feed/presentation/pages/feed_screen.dart | 5 - .../widgets/blog_post_widget.dart | 99 +++++-------------- .../my_finance/models/card_model.dart | 3 - 7 files changed, 100 insertions(+), 136 deletions(-) diff --git a/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart b/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart index 59665002..02196288 100644 --- a/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart +++ b/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart @@ -18,4 +18,26 @@ class BlogPostCubit extends Cubit { emit(state.copyWith(isLoading: false)); } } + + Future toggleLike() async { + final current = state.blogPostModel; + if (current == null) return; + + // optimistic update + final wasLiked = current.wasLikedByMe; + final updated = current.copyWith(wasLikedByMe: !wasLiked, totalLikes: wasLiked ? current.totalLikes - 1 : current.totalLikes + 1); + emit(state.copyWith(blogPostModel: updated)); + + try { + if (wasLiked) { + await feedRepository.unlikedBlogPost(id: current.id); + } else { + await feedRepository.likeBlogPost(id: current.id); + } + } catch (e, st) { + // rollback on error + log(e.toString(), stackTrace: st); + emit(state.copyWith(blogPostModel: current)); + } + } } 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 25636a57..61b64f9e 100644 --- a/lib/features/blog_post/presentation/pages/blog_post_screen.dart +++ b/lib/features/blog_post/presentation/pages/blog_post_screen.dart @@ -32,8 +32,6 @@ class _BlogPostScreenState extends State { final CommentsCubit commentsCubit = sl(); final textEditingController = TextEditingController(); int? replyId; - bool isLiked = false; - int totalLikes = 0; @override void initState() { @@ -60,26 +58,30 @@ class _BlogPostScreenState extends State { builder: (BuildContext context, CommentsState state) { if (blogPostState.isLoading) { return Scaffold( - appBar: AppBar( - leading: BackButton( - onPressed: () { - if (context.canPop()) { - GoRouter.of(context).pop(); - return; - } - GoRouter.of(context).go('/feed-screen'); - }, - ), + appBar: AppBar( + leading: BackButton( + onPressed: () { + if (context.canPop()) { + GoRouter.of(context).pop(); + return; + } + GoRouter.of(context).go('/feed-screen'); + }, ), - body: const Loader()); + ), + body: const Loader(), + ); } - isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false; - totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0; + final bool isLiked = blogPostState.blogPostModel?.wasLikedByMe ?? false; + final int totalLikes = blogPostState.blogPostModel?.totalLikes ?? 0; return Scaffold( resizeToAvoidBottomInset: true, bottomSheet: Container( padding: const EdgeInsets.symmetric(vertical: 12) + (Platform.isIOS ? const EdgeInsets.only(bottom: 16) : EdgeInsets.zero), - decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1)))), + decoration: BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), + ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -114,24 +116,14 @@ class _BlogPostScreenState extends State { maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.black, - height: 1.2, - ), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2), decoration: InputDecoration( isDense: false, // убираем true, чтобы padding учитывался корректно contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен hintText: 'Комментарий', - hintStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - height: 1.2, - color: Color(0xFFAEAEB2), - ), + hintStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), @@ -196,7 +188,7 @@ class _BlogPostScreenState extends State { id: widget.id, feedBlogPostModel: blogPostState.blogPostModel, userSlug: blogPostState.blogPostModel?.author.authorSlug ?? '', - ), + ), const SizedBox(width: 16), ], ), @@ -215,9 +207,7 @@ class _BlogPostScreenState extends State { children: [ CircleAvatar( radius: 16, - foregroundImage: NetworkImage( - baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? ''), - ), + foregroundImage: NetworkImage(baseUrl + (blogPostState.blogPostModel?.author.authorProfileIcon ?? '')), backgroundImage: const AssetImage('assets/images/no_avatar.png'), ), const SizedBox(width: 8), @@ -242,30 +232,32 @@ class _BlogPostScreenState extends State { if (blogPostState.blogPostModel?.description.isNotEmpty ?? false) Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: Html(data: blogPostState.blogPostModel?.description.replaceAll('\n', '
'), style: {"*": Style(color: Colors.black87)}), + child: Html( + data: blogPostState.blogPostModel?.description.replaceAll('\n', '
'), + style: {"*": Style(color: Colors.black87)}, + ), ), const SizedBox(height: 8), if (blogPostState.blogPostModel?.image.isNotEmpty ?? false) - CachedNetworkImage( - imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}', - ), + CachedNetworkImage(imageUrl: '$baseUrl/storage/${blogPostState.blogPostModel?.image ?? ''}'), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ _iconButton( - icon: Icons.favorite_border_rounded, + icon: isLiked ? Icons.favorite_rounded : Icons.favorite_border_rounded, text: totalLikes.toString(), iconColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null, backgroundColor: isLiked ? Colors.red.withValues(alpha: 0.1) : null, textColor: isLiked ? Colors.red.withValues(alpha: 0.8) : null, onTap: () async { - setState(() { - isLiked = !isLiked; - isLiked ? totalLikes++ : totalLikes--; - }); - isLiked ? await context.read().likePost(widget.id) : await context.read().unlikedPost(widget.id); - sl().updateFeedBlogPostList(blogPostState.blogPostModel, isLiked); + await context.read().toggleLike(); + // ignore: use_build_context_synchronously + final updated = context.read().state.blogPostModel; + if (updated != null) { + // ignore: use_build_context_synchronously + context.read().updateFeedBlogPostList(updated, updated.wasLikedByMe); + } }, ), const SizedBox(width: 8), @@ -284,8 +276,10 @@ class _BlogPostScreenState extends State { const Spacer(), Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500), const SizedBox(width: 4), - Text(blogPostState.blogPostModel?.totalViews.toString() ?? '', - style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400)), + Text( + blogPostState.blogPostModel?.totalViews.toString() ?? '', + style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400), + ), ], ), ), @@ -332,7 +326,11 @@ class _BlogPostScreenState extends State { children: [ Icon(icon, size: 20, color: iconColor ?? appTheme.gray600), if (text != null) const SizedBox(width: 8), - if (text != null) Text(text, style: TextStyle(color: textColor ?? appTheme.gray600, fontWeight: FontWeight.w500)), + if (text != null) + Text( + text, + style: TextStyle(color: textColor ?? appTheme.gray600, fontWeight: FontWeight.w500), + ), ], ), ), diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart index 3d870e8d..f9fa4143 100644 --- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart +++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart @@ -1,5 +1,4 @@ import 'package:collection/collection.dart'; -import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/custom_divider.dart'; @@ -66,7 +65,7 @@ class _ConsultationCardWidgetState extends State { children: [ Row( children: [ - CircleAvatar(radius: 30, backgroundImage: NetworkImage(baseUrl + widget.consultationsModel.userProfilePhotoUrl)), + CircleAvatar(radius: 30, backgroundImage: NetworkImage(widget.consultationsModel.userProfilePhotoUrl)), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, 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 947b9a5d..d1bf399f 100644 --- a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart +++ b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart @@ -15,13 +15,12 @@ class FeedCubit extends Cubit { FeedCubit({required this.feedRepository}) : super(const FeedState.loading()); void updateFeedBlogPostList(FeedBlogPostModel? feedBlogPostModel, bool wasLikedByMe) { - if (feedBlogPostModel == null || isClosed) return; - 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, - ); + if (feedBlogPostModel == null || isClosed || state is! FeedStateLoaded) return; + final List feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList]; + final int index = feedBlogPostList.indexWhere((e) => e.id == feedBlogPostModel.id); + if (index == -1) return; + final current = feedBlogPostList[index]; + feedBlogPostList[index] = current.copyWith(wasLikedByMe: wasLikedByMe, totalLikes: wasLikedByMe ? current.totalLikes + 1 : current.totalLikes - 1); if (!isClosed) { emit(FeedState.loaded(feedBlogPostList: feedBlogPostList, isLoading: isLoading)); } @@ -81,5 +80,4 @@ class FeedCubit extends Cubit { Future viewRecipe(String slug) async => await feedRepository.viewRecipe(slug); Future viewDiet(int id) async => await feedRepository.viewDiet(id); Future viewPlan(String slug) async => await feedRepository.viewPlan(slug); - } diff --git a/lib/features/feed/presentation/pages/feed_screen.dart b/lib/features/feed/presentation/pages/feed_screen.dart index 844788fe..796b4a3b 100644 --- a/lib/features/feed/presentation/pages/feed_screen.dart +++ b/lib/features/feed/presentation/pages/feed_screen.dart @@ -36,7 +36,6 @@ class FeedScreen extends StatelessWidget { providers: [ BlocProvider(create: (context) => sl()..loadRecipesList()), BlocProvider(create: (context) => sl()..getProfile()), - BlocProvider(create: (context) => sl()..loadFeedBlogPostList()), BlocProvider(create: (context) => sl()..loadDiets()), if (!hidePlans) BlocProvider(create: (context) => sl()..loadPlans()), BlocProvider(create: (context) => sl()..getConsultants()), @@ -78,8 +77,6 @@ class _FeedScreenContentState extends State { if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {} } - - @override void dispose() { blogPostsScrollController.dispose(); @@ -90,8 +87,6 @@ class _FeedScreenContentState extends State { super.dispose(); } - - @override Widget build(BuildContext context) { FlutterNativeSplash.remove(); diff --git a/lib/features/feed/presentation/widgets/blog_post_widget.dart b/lib/features/feed/presentation/widgets/blog_post_widget.dart index 23eaeded..e001a038 100644 --- a/lib/features/feed/presentation/widgets/blog_post_widget.dart +++ b/lib/features/feed/presentation/widgets/blog_post_widget.dart @@ -21,10 +21,7 @@ import 'package:share_plus/share_plus.dart'; class BlogPostWidget extends StatefulWidget { final int index; - const BlogPostWidget({ - super.key, - required this.index, - }); + const BlogPostWidget({super.key, required this.index}); @override State createState() => _BlogPostWidgetState(); @@ -44,10 +41,7 @@ class _BlogPostWidgetState extends State { totalLikes = state.feedBlogPostList[widget.index].totalLikes; return InkWell( borderRadius: BorderRadius.circular(12), - onTap: () => GoRouter.of(context).pushNamed( - 'blogPostScreen', - extra: state.feedBlogPostList[widget.index].id, - ), + onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.feedBlogPostList[widget.index].id), child: Material( borderRadius: BorderRadius.circular(12), color: Colors.white, @@ -95,21 +89,12 @@ class _BlogPostWidgetState extends State { Text( state.feedBlogPostList[widget.index].author.authorName, overflow: TextOverflow.ellipsis, - style: const TextStyle( - height: 1, - color: Colors.black87, - fontWeight: FontWeight.w500, - fontSize: 16, - ), + style: const TextStyle(height: 1, color: Colors.black87, fontWeight: FontWeight.w500, fontSize: 16), ), 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: const TextStyle( - height: 1, - color: Colors.black45, - fontSize: 14, - ), + style: const TextStyle(height: 1, color: Colors.black45, fontSize: 14), ), ], ), @@ -119,9 +104,7 @@ class _BlogPostWidgetState extends State { !state.feedBlogPostList[widget.index].author.authorIsMe) Padding( padding: const EdgeInsets.only(right: 8), - child: SubscribeButtonWidget( - slug: state.feedBlogPostList[widget.index].author.authorSlug, - ), + child: SubscribeButtonWidget(slug: state.feedBlogPostList[widget.index].author.authorSlug), ), // Кнопка меню CustomPopup( @@ -141,11 +124,7 @@ class _BlogPostWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 12), child: Text( state.feedBlogPostList[widget.index].title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87), ), ), ], @@ -156,15 +135,12 @@ class _BlogPostWidgetState extends State { 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).replaceAll('\n', '
'), - style: { - "*": Style( - fontSize: FontSize(16), - color: Colors.black87, - ), - }, + data: + (state.feedBlogPostList[widget.index].description.length > 200 + ? state.feedBlogPostList[widget.index].description.substring(0, 200) + : state.feedBlogPostList[widget.index].description) + .replaceAll('\n', '
'), + style: {"*": Style(fontSize: FontSize(16), color: Colors.black87)}, ), ), if (state.feedBlogPostList[widget.index].description.length > 200) @@ -172,12 +148,7 @@ class _BlogPostWidgetState extends State { padding: EdgeInsets.symmetric(horizontal: 12), child: Text( 'Показать ещё', - style: TextStyle( - height: 1, - color: Color(0xFF8B8B8B), - fontSize: 14, - fontWeight: FontWeight.w600, - ), + style: TextStyle(height: 1, color: Color(0xFF8B8B8B), fontSize: 14, fontWeight: FontWeight.w600), ), ), if (state.feedBlogPostList[widget.index].title.isNotEmpty || state.feedBlogPostList[widget.index].description.isNotEmpty) @@ -186,10 +157,7 @@ class _BlogPostWidgetState extends State { onTap: () { context.pushTransparentRoute( rootNavigator: true, - PhotoViewScreen( - imageUrls: ['$baseUrl/storage/${state.feedBlogPostList[widget.index].image}'], - index: 0, - ), + PhotoViewScreen(imageUrls: ['$baseUrl/storage/${state.feedBlogPostList[widget.index].image}'], index: 0), ); }, child: Hero( @@ -218,11 +186,14 @@ class _BlogPostWidgetState extends State { isLiked = !isLiked; isLiked ? totalLikes++ : totalLikes--; }); - isLiked - ? await context.read().likePost(state.feedBlogPostList[widget.index].id) - : await context.read().unlikedPost(state.feedBlogPostList[widget.index].id); - if (!context.mounted) return; - await context.read().loadFeedBlogPostList(); + // Update feed list immediately by ID (no reload) + context.read().updateFeedBlogPostList(state.feedBlogPostList[widget.index], isLiked); + // Fire network call without introducing UI delay + if (isLiked) { + await context.read().likePost(state.feedBlogPostList[widget.index].id); + } else { + await context.read().unlikedPost(state.feedBlogPostList[widget.index].id); + } }, ), const SizedBox(width: 8), @@ -231,16 +202,10 @@ class _BlogPostWidgetState extends State { 'assets/icons/comment.svg', height: 20, width: 20, - colorFilter: ColorFilter.mode( - appTheme.gray600, - BlendMode.srcIn, - ), + 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].id, - ), + onTap: () => GoRouter.of(context).pushNamed('blogPostScreen', extra: state.feedBlogPostList[widget.index].id), ), const SizedBox(width: 8), FeedIconButton( @@ -254,28 +219,18 @@ class _BlogPostWidgetState extends State { final box = context.findRenderObject() as RenderBox?; SharePlus.instance.share( ShareParams( - uri: Uri.parse( - '$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}', - ), + uri: Uri.parse('$baseUrl/ru/blog_post/${state.feedBlogPostList[widget.index].id}'), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, ), ); }, ), const Spacer(), - Icon( - Icons.visibility_rounded, - size: 16, - color: appTheme.gray500, - ), + Icon(Icons.visibility_rounded, size: 16, color: appTheme.gray500), const SizedBox(width: 4), Text( state.feedBlogPostList[widget.index].totalViews.toString(), - style: TextStyle( - fontSize: 12, - color: appTheme.gray500, - fontWeight: FontWeight.w400, - ), + style: TextStyle(fontSize: 12, color: appTheme.gray500, fontWeight: FontWeight.w400), ), ], ), diff --git a/lib/features/my_finance/models/card_model.dart b/lib/features/my_finance/models/card_model.dart index d248cace..0435ea56 100644 --- a/lib/features/my_finance/models/card_model.dart +++ b/lib/features/my_finance/models/card_model.dart @@ -15,9 +15,6 @@ abstract class CardModel with _$CardModel { } bool parseBool(dynamic json) { - if (json == null) { - throw Exception('parseBool: json is null'); - } if (json == 1 || json == true || json == '1') { return true; } From ad777c695bd38fd3a308b095c853bf4f8474e62a Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Mon, 22 Sep 2025 17:08:41 +0300 Subject: [PATCH 3/4] fix(consultations): Issue-393 --- .../widgets/mood_rating_widget.dart | 93 +++++-------------- .../widgets/satiety_rating_widget.dart | 93 +++++-------------- .../diary/repositories/diary_repository.dart | 48 +++++++++- .../presentation/user_details_screen.dart | 4 +- 4 files changed, 99 insertions(+), 139 deletions(-) diff --git a/lib/features/diary/presentation/widgets/mood_rating_widget.dart b/lib/features/diary/presentation/widgets/mood_rating_widget.dart index 41640b78..d943eac2 100644 --- a/lib/features/diary/presentation/widgets/mood_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/mood_rating_widget.dart @@ -23,9 +23,12 @@ class _MoodRatingWidgetState extends State { builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { final selectedIndex = state.selectedMeal - 1; - if (isFirstFetch[selectedIndex]) { - currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].mood.toDouble() + 1; - isFirstFetch[selectedIndex] = false; + final meals = state.diary.meals; + if (selectedIndex >= 0 && selectedIndex < meals.length) { + if (isFirstFetch[selectedIndex]) { + currentSelectedRating[selectedIndex] = meals[selectedIndex].mood.toDouble() + 1; + isFirstFetch[selectedIndex] = false; + } } return Padding( padding: const EdgeInsets.only(top: 24), @@ -35,21 +38,17 @@ class _MoodRatingWidgetState extends State { Text( state.isConsultantView ? 'Настроение пользователя после приема пищи' : 'Ваше настроение после приёма пищи', textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - ), + style: const TextStyle(fontSize: 16), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset( - 'assets/icons/mood_1.svg', - height: 32, - width: 32, - ), + SvgPicture.asset('assets/icons/mood_1.svg', height: 32, width: 32), RatingBar.builder( ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, - initialRating: state.diary.meals[selectedIndex].mood.toDouble() + 1, + initialRating: (selectedIndex >= 0 && selectedIndex < state.diary.meals.length) + ? state.diary.meals[selectedIndex].mood.toDouble() + 1 + : 3, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), updateOnDrag: true, @@ -63,20 +62,11 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFC82222), border: currentSelectedRating[selectedIndex] == 1 - ? Border.all( - width: 2, - color: const Color(0xFFAA3333), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFFAA3333), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 1 - ? const BorderRadius.horizontal( - left: Radius.circular(8), - right: Radius.circular(2), - ) - : const BorderRadius.horizontal( - left: Radius.circular(8), - ), + ? const BorderRadius.horizontal(left: Radius.circular(8), right: Radius.circular(2)) + : const BorderRadius.horizontal(left: Radius.circular(8)), ), ); case 1: @@ -87,11 +77,7 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFFF3E3E), border: currentSelectedRating[selectedIndex] == 2 - ? Border.all( - width: 2, - color: const Color(0xFF550000), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF550000), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null, ), @@ -104,11 +90,7 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFF8C926), border: currentSelectedRating[selectedIndex] == 3 - ? Border.all( - width: 2, - color: const Color(0xFF8C5D00), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF8C5D00), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null, ), @@ -121,11 +103,7 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFFFF3BF), border: currentSelectedRating[selectedIndex] == 4 - ? Border.all( - width: 2, - color: const Color(0xFFB5911B), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFFB5911B), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null, ), @@ -138,11 +116,7 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFF62E833), border: currentSelectedRating[selectedIndex] == 5 - ? Border.all( - width: 2, - color: const Color(0xFF276918), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF276918), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null, ), @@ -155,20 +129,11 @@ class _MoodRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFF4DCE20), border: currentSelectedRating[selectedIndex] == 6 - ? Border.all( - width: 2, - color: const Color(0xFF184D12), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF184D12), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 6 - ? const BorderRadius.horizontal( - left: Radius.circular(2), - right: Radius.circular(8), - ) - : const BorderRadius.horizontal( - right: Radius.circular(8), - ), + ? const BorderRadius.horizontal(left: Radius.circular(2), right: Radius.circular(8)) + : const BorderRadius.horizontal(right: Radius.circular(8)), ), ); } @@ -177,20 +142,12 @@ class _MoodRatingWidgetState extends State { setState(() { currentSelectedRating[selectedIndex] = value; }); - EasyDebounce.debounce( - 'change-satiety', - const Duration(milliseconds: 250), - () { - context.read().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1); - }, - ); + EasyDebounce.debounce('change-satiety', const Duration(milliseconds: 250), () { + context.read().fillMood(diaryId: state.diary.id, mood: currentSelectedRating[selectedIndex].toInt() - 1); + }); }, ), - SvgPicture.asset( - 'assets/icons/mood_2.svg', - height: 32, - width: 32, - ), + SvgPicture.asset('assets/icons/mood_2.svg', height: 32, width: 32), ], ), ], diff --git a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart index d473f691..d36ac75b 100644 --- a/lib/features/diary/presentation/widgets/satiety_rating_widget.dart +++ b/lib/features/diary/presentation/widgets/satiety_rating_widget.dart @@ -23,9 +23,12 @@ class _SatietyRatingWidgetState extends State { builder: (BuildContext context, DiaryState state) { if (state is DiaryStateLoaded) { final selectedIndex = state.selectedMeal - 1; - if (isFirstFetch[selectedIndex]) { - currentSelectedRating[selectedIndex] = state.diary.meals[selectedIndex].satiety.toDouble() + 1; - isFirstFetch[selectedIndex] = false; + final meals = state.diary.meals; + if (selectedIndex >= 0 && selectedIndex < meals.length) { + if (isFirstFetch[selectedIndex]) { + currentSelectedRating[selectedIndex] = meals[selectedIndex].satiety.toDouble() + 1; + isFirstFetch[selectedIndex] = false; + } } return Padding( padding: const EdgeInsets.only(top: 24), @@ -35,21 +38,17 @@ class _SatietyRatingWidgetState extends State { Text( state.isConsultantView ? 'Сытость пользователя после приема пищи' : 'Ваша сытость после приёма пищи', textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - ), + style: const TextStyle(fontSize: 16), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset( - 'assets/icons/satiety_emoji_left.svg', - height: 32, - width: 32, - ), + SvgPicture.asset('assets/icons/satiety_emoji_left.svg', height: 32, width: 32), RatingBar.builder( ignoreGestures: state.isConsultantView || state.isDiaryFinished || state.isDiaryCommented, - initialRating: state.diary.meals[selectedIndex].satiety.toDouble() + 1, + initialRating: (selectedIndex >= 0 && selectedIndex < state.diary.meals.length) + ? state.diary.meals[selectedIndex].satiety.toDouble() + 1 + : 3, itemCount: 6, itemSize: MediaQuery.of(context).size.width / 6 - (124 / 6), updateOnDrag: true, @@ -63,20 +62,11 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFC82222), border: currentSelectedRating[selectedIndex] == 1 - ? Border.all( - width: 2, - color: const Color(0xFFAA3333), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFFAA3333), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 1 - ? const BorderRadius.horizontal( - left: Radius.circular(8), - right: Radius.circular(2), - ) - : const BorderRadius.horizontal( - left: Radius.circular(8), - ), + ? const BorderRadius.horizontal(left: Radius.circular(8), right: Radius.circular(2)) + : const BorderRadius.horizontal(left: Radius.circular(8)), ), ); case 1: @@ -87,11 +77,7 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFFF3E3E), border: currentSelectedRating[selectedIndex] == 2 - ? Border.all( - width: 2, - color: const Color(0xFF550000), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF550000), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 2 ? BorderRadius.circular(2) : null, ), @@ -104,11 +90,7 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFF8C926), border: currentSelectedRating[selectedIndex] == 3 - ? Border.all( - width: 2, - color: const Color(0xFF8C5D00), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF8C5D00), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 3 ? BorderRadius.circular(2) : null, ), @@ -121,11 +103,7 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFFFFF3BF), border: currentSelectedRating[selectedIndex] == 4 - ? Border.all( - width: 2, - color: const Color(0xFFB5911B), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFFB5911B), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 4 ? BorderRadius.circular(2) : null, ), @@ -138,11 +116,7 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFF62E833), border: currentSelectedRating[selectedIndex] == 5 - ? Border.all( - width: 2, - color: const Color(0xFF276918), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF276918), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 5 ? BorderRadius.circular(2) : null, ), @@ -155,20 +129,11 @@ class _SatietyRatingWidgetState extends State { decoration: BoxDecoration( color: const Color(0xFF4DCE20), border: currentSelectedRating[selectedIndex] == 6 - ? Border.all( - width: 2, - color: const Color(0xFF184D12), - strokeAlign: BorderSide.strokeAlignOutside, - ) + ? Border.all(width: 2, color: const Color(0xFF184D12), strokeAlign: BorderSide.strokeAlignOutside) : null, borderRadius: currentSelectedRating[selectedIndex] == 6 - ? const BorderRadius.horizontal( - left: Radius.circular(2), - right: Radius.circular(8), - ) - : const BorderRadius.horizontal( - right: Radius.circular(8), - ), + ? const BorderRadius.horizontal(left: Radius.circular(2), right: Radius.circular(8)) + : const BorderRadius.horizontal(right: Radius.circular(8)), ), ); } @@ -178,20 +143,12 @@ class _SatietyRatingWidgetState extends State { setState(() { currentSelectedRating[selectedIndex] = value; }); - EasyDebounce.debounce( - 'change-satiety', - const Duration(milliseconds: 250), - () { - context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1); - }, - ); + EasyDebounce.debounce('change-satiety', const Duration(milliseconds: 250), () { + context.read().fillSatiety(diaryId: state.diary.id, satiety: currentSelectedRating[selectedIndex].toInt() - 1); + }); }, ), - SvgPicture.asset( - 'assets/icons/satiety_emoji_right.svg', - height: 32, - width: 32, - ), + SvgPicture.asset('assets/icons/satiety_emoji_right.svg', height: 32, width: 32), ], ), ], diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index 0165ad7f..f3ff8bb5 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -82,7 +82,53 @@ class DiaryRepository { } Future getDiaryDay(int diaryId) async { - return (await diaryService.getDiaryDay(diary: diaryId)).diary; + // Align with getDiaryByDate: enrich portions with glossary nutritional info + final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId); + final DiaryDayModel diaryDay = diaryWrapper.diary; + Set ids = {}; + for (var meal in diaryDay.meals) { + for (var portion in meal.portions) { + if (portion.productNutritionalValueId != null) { + ids.add(portion.productNutritionalValueId!); + } + } + } + if (ids.isNotEmpty) { + final glossary = await getGlossariesIndex(ids: ids.toList()); + final List updatedMeals = List.from(diaryDay.meals); + for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) { + final meal = updatedMeals[mealIndex]; + final updatedPortions = meal.portions.map((portion) { + if (portion.productNutritionalValueId != null) { + try { + final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId); + return portion.copyWith( + productCaloriesInfo: ProductCaloriesInfoModel( + id: glossaryItem.id, + productName: glossaryItem.name, + calories: glossaryItem.energyOn100Gr, + proteins: glossaryItem.proteinsOn100Gr, + fats: glossaryItem.fatsOn100Gr, + carbohydrates: glossaryItem.carbohydratesOn100Gr, + energyOnPortion: glossaryItem.energyOnPortion, + proteinsOnPortion: glossaryItem.proteinsOnPortion, + fatsOnPortion: glossaryItem.fatsOnPortion, + carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion, + ), + ); + } catch (e) { + return portion; + } + } + return portion; + }).toList(); + + updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions); + } + final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals); + return updatedDiaryDay; + } + return diaryDay; } Future getNutritionData(int index) async { diff --git a/lib/features/user_details/presentation/user_details_screen.dart b/lib/features/user_details/presentation/user_details_screen.dart index 4907e3dd..46e986f4 100644 --- a/lib/features/user_details/presentation/user_details_screen.dart +++ b/lib/features/user_details/presentation/user_details_screen.dart @@ -231,7 +231,7 @@ class _UserDetailsContentState extends State { children: [ Text(s.has_hypo, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHypo.toString())), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHypo ? 'да' : 'нет')), ], ), ), @@ -242,7 +242,7 @@ class _UserDetailsContentState extends State { children: [ Text(s.has_hyper, style: TextStyles.textSMedium.copyWith(color: appTheme.black)), const SizedBox(height: 4), - CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHyper.toString())), + CustomTextFormField(readOnly: true, controller: TextEditingController(text: profileDetails.hasHyper ? 'да' : 'нет')), ], ), ), From 052c4fedca39a480f28a6fffd8b01ddc11123931 Mon Sep 17 00:00:00 2001 From: mso7nueh Date: Tue, 23 Sep 2025 23:47:38 +0300 Subject: [PATCH 4/4] feat(hms): Issue-660 --- android/build.gradle | 1 + lib/core/firebase_api.dart | 47 +++++++++++++++++++++++++++++++++++++- pubspec.yaml | 1 + 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index d2ffbffa..9db63328 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,6 +2,7 @@ allprojects { repositories { google() mavenCentral() + maven { url 'https://developer.huawei.com/repo/' } } } diff --git a/lib/core/firebase_api.dart b/lib/core/firebase_api.dart index 572b56f6..2ba89290 100644 --- a/lib/core/firebase_api.dart +++ b/lib/core/firebase_api.dart @@ -6,13 +6,24 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:huawei_push/huawei_push.dart'; class FirebaseApi { final _firebaseMessaging = FirebaseMessaging.instance; Future getToken() async { await _firebaseMessaging.requestPermission(); - return await _firebaseMessaging.getToken(); + + String? token = await _firebaseMessaging.getToken(); + + if (token == null && Platform.isAndroid) { + final isHuawei = await _isHuaweiDevice(); + if (isHuawei) { + token = await _getHuaweiPushToken(); + } + } + + return token; } Future registerPushToken() async { @@ -32,4 +43,38 @@ class FirebaseApi { } } } + + Future _isHuaweiDevice() async { + if (!Platform.isAndroid) return false; + try { + final android = await DeviceInfoPlugin().androidInfo; + final brand = android.brand.toLowerCase(); + final manufacturer = android.manufacturer.toLowerCase(); + return brand.contains('huawei') || manufacturer.contains('huawei') || brand.contains('honor') || manufacturer.contains('honor'); + } catch (_) { + return false; + } + } + + Future _getHuaweiPushToken() async { + try { + await Push.turnOnPush(); + + String? resolvedToken; + final sub = Push.getTokenStream.listen((t) => resolvedToken ??= t, onError: (_) {}); + + Push.getToken(''); + + final started = DateTime.now(); + while (resolvedToken == null && DateTime.now().difference(started).inSeconds < 5) { + await Future.delayed(const Duration(milliseconds: 100)); + } + + await sub.cancel(); + return resolvedToken; + } catch (e, st) { + log('Huawei Push token error: $e', stackTrace: st); + return null; + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index 5fec1116..3f1e036e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,7 @@ dependencies: flutter_dotenv: ^5.2.1 uuid: ^4.5.1 appmetrica_plugin: ^3.3.0 + huawei_push: ^6.11.0+300 dev_dependencies: build_runner: ^2.7.0