import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dismissible_page/dismissible_page.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.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'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart'; import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/plan_widget.dart'; import 'package:eatvio_app/features/profile/models/circumference_model.dart'; import 'package:eatvio_app/features/profile/models/weight_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_state.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart'; import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/circumference_chart_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/imt_chart_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/profile_blog_post_widget.dart'; import 'package:eatvio_app/features/profile/presentation/widgets/weight_chart_widget.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; class ProfileScreen extends StatefulWidget { final Author? author; const ProfileScreen({ super.key, this.author, }); @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { late final ProfileCubit profileCubit = sl(); final PlansCubit plansCubit = sl(); final List chipsLabelList = ['Вес', 'Замеры', 'ИМТ']; final List chipsIntervalLabelList = ['1М', '3М', '6М', '1Г', 'ВСЁ ВРЕМЯ']; final List chipsIntervalList = ['1', '3', '6', '12', 'all']; List weightPoints = []; List circumferencePoints = []; int _selectedChipIndex = 0; int _selectedIntervalChipIndex = 0; late bool canSub; bool isFirstFetch = true; int _currentBullet = 0; @override void initState() { super.initState(); widget.author == null ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug); } @override Widget build(BuildContext context) { FlutterNativeSplash.remove(); return BlocProvider( create: (BuildContext context) => plansCubit, child: BlocBuilder( bloc: profileCubit, builder: (BuildContext context, ProfileState state) { if (state is ProfileStateLoaded) { if (state.circumferencePoints != null) { circumferencePoints = state.circumferencePoints!; weightPoints = state.weightPoints!; } if (isFirstFetch) { canSub = !state.profile.isEntitySubscribedByMe; if (state.profile.roles.contains('consultant')) { plansCubit..getPlans(state.profile.slug); } isFirstFetch = false; } return widget.author == null ? _myProfileScreen(state) : _profileScreen(state); } else if (state is ProfileStateError) { return DataErrorWidget( onReload: () async { widget.author == null ? await profileCubit.getMyProfile() : await profileCubit.getProfileBySlug(widget.author!.authorSlug); }, ); } else { return Scaffold( appBar: AppBar(), body: const Loader(), ); } }, ), ); } _profileScreen(ProfileStateLoaded state) { return Scaffold( backgroundColor: Colors.grey.shade200, appBar: AppBar(), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( bottom: Radius.circular(16), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ _header(state), if (state.profile.roles.contains('consultant')) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const SizedBox(width: 16), Text( 'Консультант', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Color( 0xFF323F4B, ), ), ), const SizedBox(width: 12), if (state.profile.ratingPlace != null) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(12), ), child: Text( '${state.profile.ratingPlace!}-й в рейтинге', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: appTheme.blueA400, ), ), ), ], ), if (state.profile.educationResource?.hasMedicalEducation == 1) Row( children: [ const SizedBox(width: 16), Icon( Icons.done_rounded, color: Colors.green, ), Text( 'Медицинское образование', style: TextStyle( color: Color(0xFF323F4B), fontSize: 14, fontWeight: FontWeight.w600, ), ), ], ), ], ), if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state), const SizedBox(height: 8), _messageAndSubscribe(state), const SizedBox(height: 16), ], ), ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)), if (state.profile.roles.contains('consultant')) _socialLinks(state), if (state.profile.roles.contains('consultant')) _education(state), if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state), if (state.profile.roles.contains('consultant')) _activePlans(state), _blogPosts(state), ], ), ), ); } _myProfileScreen(ProfileStateLoaded state) { return Scaffold( backgroundColor: Colors.grey.shade200, appBar: AppBar(toolbarHeight: 0), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( bottom: Radius.circular(16), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ _header(state), if (state.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state), ], ), ), _subscriptionsAndSubscribers(state), if (state.profile.badges.isNotEmpty) Padding(padding: EdgeInsets.only(bottom: 16), child: _badges(state)), if (state.profile.roles.contains('consultant')) _socialLinks(state), if (state.profile.roles.contains('consultant')) _education(state), if (state.profile.roles.contains('consultant')) _diplomasAndCertificates(state), Padding(padding: EdgeInsets.only(bottom: 16), child: _charts()), if (state.profile.roles.contains('consultant')) _activePlans(state), _newBlogPostButton(), _blogPosts(state), ], ), ), ); } _activePlans(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); } return BlocBuilder( bloc: plansCubit, builder: (BuildContext context, PlansState state) { if (state is PlansStateLoaded) { if (state.plansList.isEmpty) { return const SizedBox.shrink(); } return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Активные планы', style: TextStyle( color: Color(0xFF323F4B), fontSize: 24, height: 1, fontWeight: FontWeight.w600, ), ), ], ), ), const SizedBox(height: 8), SizedBox( height: 378, child: PageView.builder( itemBuilder: (BuildContext context, int index) { return PlanWidget( plan: state.plansList[index], index: index, ); }, onPageChanged: (bullet) { setState(() { _currentBullet = bullet; }); }, itemCount: state.plansList.length, ), ), const SizedBox(height: 8), _bullets(_currentBullet, state.plansList.length), ], ); } return Loader(); }, ); } _bullets(int currentBullet, int bulletCount) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ for (int i = 0; i < bulletCount; i++) ...[ _bullet(i == currentBullet), i < bulletCount - 1 ? const SizedBox(width: 4) : const SizedBox.shrink(), ], ], ), ); } _bullet(bool isActive) { return AnimatedContainer( width: isActive ? 10 : 8, height: isActive ? 10 : 8, duration: Duration(milliseconds: 150), decoration: BoxDecoration( color: isActive ? appTheme.blueA400 : Color(0xFFD7D9DC), shape: BoxShape.circle, ), ); } _newBlogPostButton() { return Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: InkWell( onTap: () { GoRouter.of(context).pushNamed('blogPostCreateScreen'); }, borderRadius: BorderRadius.circular(8), child: Ink( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.add_circle_outline_rounded, color: Color(0xFF2F80ED), ), const SizedBox(width: 12), Text( 'Новая запись', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF2F80ED), ), ), ], ), ), ), ), ); } _diplomasAndCertificates(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant') || state.profile.diplomasAndCertificates == null || state.profile.diplomasAndCertificates!.isEmpty) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Дипломы\nи сертификаты', style: TextStyle( color: Color(0xFF323F4B), fontSize: 24, height: 1, fontWeight: FontWeight.w600, ), ), Text( widget.author == null ? 'Редактировать' : '', style: TextStyle( color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(height: 8), Container( width: MediaQuery.of(context).size.width - 2, padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ if (state.profile.diplomasAndCertificates != null) ...state.profile.diplomasAndCertificates!.map((d) => _diplomaImage(baseUrl + '/storage/' + d.path)).toList(), ], ), ), ), ), ], ), ); } _diplomaImage(String url) { return GestureDetector( onTap: () { context.pushTransparentRoute( rootNavigator: true, PhotoViewScreen( imageUrls: [url], index: 0, ), ); }, child: Hero( tag: url, child: ClipRRect( borderRadius: BorderRadius.circular(8), child: CachedNetworkImage( imageUrl: url, fit: BoxFit.fitHeight, width: MediaQuery.of(context).size.width / 3 - (60 / 3), height: 160, ), ), ), ); } _education(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant') || state.profile.educationResource?.completedCourses == null || state.profile.educationResource?.completedCourses?.length == 0) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.only(bottom: 16), child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Образование', style: TextStyle( color: Color(0xFF323F4B), fontSize: 24, fontWeight: FontWeight.w600, ), ), Text( widget.author == null ? 'Редактировать' : '', style: TextStyle( color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Column( children: [ if (state.profile.educationResource?.completedCourses != null) ...state.profile.educationResource!.completedCourses!.map((c) => _educationBlock(c.values.first.title, c.values.first.date)).toList(), ], ), ), ), ), ], ), ); } _educationBlock(String title, String date) { return SizedBox( width: double.infinity, child: Material( color: Color(0xFFF6FAFF), borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( height: 1, color: Color(0xFF323F4B), fontWeight: FontWeight.w500, fontSize: 16, ), ), const SizedBox(height: 4), Text( date, style: TextStyle( height: 1, color: Color(0xFF6D6F71), fontWeight: FontWeight.w500, fontSize: 14, ), ), ], ), ), ), ); } _socialLinks(ProfileStateLoaded state) { if (!state.profile.roles.contains('consultant')) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 4) + const EdgeInsets.only(bottom: 16), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ _socialLinkButton( () { launchUrl( Uri.parse( state.profile.educationResource?.vkLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/vk.svg', state.profile.educationResource?.vkLink == null ? true : false, ), const SizedBox(width: 12), _socialLinkButton( () { launchUrl( Uri.parse( state.profile.educationResource?.vkLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/telegram.svg', state.profile.educationResource?.vkLink == null ? true : false, ), const SizedBox(width: 12), _socialLinkButton( () { launchUrl( Uri.parse( state.profile.educationResource?.personalSiteLink ?? '', ), mode: LaunchMode.externalApplication, ); }, 'assets/icons/web.svg', state.profile.educationResource?.personalSiteLink == null ? true : false, ), ], ), ), ), ); } _socialLinkButton(Function() onTap, String icon, bool disabled) { return InkWell( onTap: disabled ? null : onTap, borderRadius: BorderRadius.circular(20), child: Ink( width: MediaQuery.of(context).size.width / 3 - (56 / 3), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: Color(0xFFF6F6F6), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), child: SvgPicture.asset( icon, width: 24, colorFilter: ColorFilter.mode(Colors.black12, BlendMode.srcIn), ), ), ), ); } _badges(ProfileStateLoaded state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(12), child: Ink( padding: const EdgeInsets.all(8), width: MediaQuery.of(context).size.width, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Достижения', style: TextStyle( color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ for (int i = 0; i < state.profile.badges.length; i++) ...[ state.profile.badges[i].image.contains('svg') ? SvgPicture.network( baseUrl + '/storage/' + state.profile.badges[i].image, width: 40, height: 40, ) : CachedNetworkImage( imageUrl: baseUrl + '/storage/' + state.profile.badges[i].image, width: 40, height: 40, ), i < state.profile.badges.length - 1 ? const SizedBox(width: 8) : const SizedBox.shrink(), ], ], ), ), ], ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.white, ), ), ), ); } _subscriptionsAndSubscribers(ProfileStateLoaded state) { return Padding( padding: const EdgeInsets.all(4), child: Row( children: [ InkWell( onTap: () => GoRouter.of(context).pushNamed( 'subsScreen', extra: SubType.Subscriptions, pathParameters: { 'slug': state.profile.slug, }, ), borderRadius: BorderRadius.circular(12), child: Ink( padding: const EdgeInsets.all(8), width: MediaQuery.of(context).size.width / 2 - 6, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Подписки', style: TextStyle( color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600, ), ), Text( '${state.profile.subscriptions.length}', style: TextStyle( color: Color(0xFF323F4B), fontSize: 22, fontWeight: FontWeight.w700, ), ), ], ), Padding( padding: EdgeInsets.only(right: 8), child: _miniAvatars(state.profile.subscriptions.map((e) => e.profilePhotoUrl).toList()), ), ], ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.white, ), ), ), SizedBox(width: 4), InkWell( onTap: () => GoRouter.of(context).pushNamed( 'subsScreen', extra: SubType.Subscribers, pathParameters: { 'slug': state.profile.slug, }, ), borderRadius: BorderRadius.circular(12), child: Ink( padding: const EdgeInsets.all(8), width: MediaQuery.of(context).size.width / 2 - 6, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Подписчики', style: TextStyle( color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w600, ), ), Text( '${state.profile.subscribers.length}', style: TextStyle( color: Color(0xFF323F4B), fontSize: 22, fontWeight: FontWeight.w700, ), ), ], ), Padding( padding: EdgeInsets.only(right: 8), child: _miniAvatars(state.profile.subscribers.map((e) => e.profilePhotoUrl).toList()), ), ], ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: Colors.white, ), ), ), ], ), ); } _miniAvatars(List urls) { int maxLength = urls.length > 2 ? 3 : urls.length; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ for (int i = 0; i < maxLength; i++) Align( widthFactor: 0.8, child: CircleAvatar( radius: 14, backgroundImage: NetworkImage( urls[i], ), ), ), ], ); } _blogPosts(ProfileStateLoaded state) { return ListView.separated( padding: EdgeInsets.symmetric(vertical: 16), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => ProfileBlogPostWidget( index: index, profileCubit: profileCubit, ), separatorBuilder: (context, index) => SizedBox(height: 8), itemCount: state.blogPosts.length, ); } _additionalInfo(ProfileStateLoaded state) { String text = state.profileDetails.additionalInfo; if (state.profileDetails.additionalInfo.length > 250) { text = state.profileDetails.additionalInfo.substring(0, 250); } return Container( alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16), child: Text( text, style: TextStyle( color: Colors.black.withValues(alpha: 0.4), height: 1, ), ), ); } _header(ProfileStateLoaded state) { return Padding( padding: const EdgeInsets.all(16), child: Row( children: [ PopupMenuButton( onSelected: (value) async { if (value == 'view') { context.pushTransparentRoute( rootNavigator: true, PhotoViewScreen( imageUrls: [state.profile.profilePhotoUrl], index: 0, ), ); } if (value == 'replace') { showCupertinoModalPopup( context: context, builder: (context) => CupertinoActionSheet( actions: [ CupertinoActionSheetAction( onPressed: () async { Navigator.of(context).pop(); final picker = ImagePicker(); XFile? image = await picker.pickImage(source: ImageSource.camera); if (image != null) { await profileCubit.uploadPhoto(File(image.path)); await sl().getMyProfile(); } }, child: const Row( children: const [ Icon(Icons.photo_camera), SizedBox(width: 8), Text('Фото'), ], ), ), CupertinoActionSheetAction( onPressed: () async { Navigator.of(context).pop(); final picker = ImagePicker(); XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { await profileCubit.uploadPhoto(File(image.path)); await sl().getMyProfile(); } }, child: const Row( children: const [ Icon(Icons.photo), SizedBox(width: 8), Text('Галерея'), ], ), ), ], cancelButton: CupertinoActionSheetAction( onPressed: () => Navigator.of(context).pop(), isDestructiveAction: true, child: const Text('Отмена'), ), ), ); } if (value == 'delete') { await profileCubit.deletePhoto(); await sl().getMyProfile(); } }, itemBuilder: (context) => [ PopupMenuItem( value: 'view', child: Text('Посмотреть'), ), if (widget.author == null) PopupMenuItem( value: 'replace', child: Text('Заменить'), ), if (widget.author == null) PopupMenuItem( value: 'delete', child: Text('Удалить'), ), ], child: Hero( tag: state.profile.profilePhotoUrl, child: CircleAvatar( radius: 32, backgroundImage: NetworkImage(state.profile.profilePhotoUrl), ), ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( state.profileDetails.firstName + ' ' + state.profileDetails.lastName, style: TextStyle( color: Colors.black, fontWeight: FontWeight.w600, fontSize: 16, height: 1, ), ), const SizedBox(height: 8), GestureDetector( onTap: () { showModalBottomSheet( useRootNavigator: true, showDragHandle: true, useSafeArea: true, context: context, builder: (context) => Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Подробнее', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Color(0xFF323F4B), ), ), const SizedBox(height: 16), _additional('Дата рожденя: ', '${state.profileDetails.birthdate}'), const SizedBox(height: 12), _additional('Страна: ', '${state.profileDetails.country}'), const SizedBox(height: 12), _additional('Город: ', '${state.profileDetails.city}'), const SizedBox(height: 12), _additional('Язык общения: ', '${state.profileDetails.lang}'), const SizedBox(height: 12), _additional('В Eatvio с: ', '${state.profile.createdAt?.format('d.MM.yyyy', 'ru')}'), const SizedBox(height: 12), ], ), ), ); }, child: Row( children: [ Icon( Icons.info_outline, size: 20, color: Color(0xFF6D6F71), ), const SizedBox(width: 4), Text( 'Дополнительно', style: TextStyle( color: Color(0xFF6D6F71), fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), ), ], ), const Spacer(), PopupMenuButton( onSelected: (value) { if (value == 'share') { final box = context.findRenderObject() as RenderBox?; Share.shareUri( Uri.parse( baseUrl + '/ru/profile/' + state.profile.slug, ), sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, ); } else { showModalBottomSheet( context: context, builder: (context) => ComplainingBottomSheet( complainedContentType: ComplainedContent.profile, slug: widget.author?.authorSlug, ), ); } }, itemBuilder: (context) => [ PopupMenuItem( value: 'share', child: Text('Поделиться'), ), if (widget.author != null) PopupMenuItem( value: 'complain', child: Text('Пожаловаться'), ), ], child: Icon(Icons.more_horiz_rounded), ), ], ), ); } _additional(String title, String value) { return Row( children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF6D6F71), ), ), Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Color(0xFF323F4B), ), ), ], ); } _messageAndSubscribe(ProfileStateLoaded state) { return Row( children: [ SizedBox(width: 16), GestureDetector( onTap: () async { GoRouter.of(context).pushNamed( 'chatScreen', extra: await profileCubit.getChatRoomIdBySlug(slug: widget.author!.authorSlug), ); }, child: Container( alignment: Alignment.center, padding: EdgeInsets.all(8), width: (MediaQuery.of(context).size.width - 40) / 2, decoration: BoxDecoration( color: appTheme.blueA400.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Text( 'Сообщение', style: TextStyle( color: appTheme.blueA400, ), ), ), ), SizedBox(width: 8), GestureDetector( onTap: () { setState(() { canSub = !canSub; }); canSub ? sl().unsubscribe(state.profile.slug) : sl().subscribe(state.profile.slug); }, child: Container( alignment: Alignment.center, padding: EdgeInsets.all(8), width: (MediaQuery.of(context).size.width - 40) / 2, decoration: BoxDecoration( color: !canSub ? Colors.grey.shade200 : appTheme.blueA400, borderRadius: BorderRadius.circular(16), ), child: Text( !canSub ? 'Отписаться' : 'Подписаться', style: TextStyle( color: !canSub ? Colors.black : Colors.white, ), ), ), ), SizedBox(width: 16), ], ); } _charts() { return Material( color: Colors.white, borderRadius: BorderRadius.circular(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, child: Row( children: [ _choiceChipWidget(0), SizedBox(width: 8), _choiceChipWidget(1), SizedBox(width: 8), _choiceChipWidget(2), ], ), ), Builder( builder: (context) { switch (_selectedChipIndex) { case 0: return WeightChartWidget( weightPoints: weightPoints, profileCubit: profileCubit, ); case 1: return CircumferenceChartWidget( circumferencePoints: circumferencePoints, profileCubit: profileCubit, ); case 2: return ImtChartWidget( weightPoints: weightPoints, ); default: return SizedBox.shrink(); } }, ), SizedBox(height: 8), Row( children: [ SizedBox(width: 16), _choiceIntervalChipWidget(0), SizedBox(width: 8), _choiceIntervalChipWidget(1), SizedBox(width: 8), _choiceIntervalChipWidget(2), SizedBox(width: 8), _choiceIntervalChipWidget(3), SizedBox(width: 8), _choiceIntervalChipWidget(4), ], ), SizedBox(height: 8), ], ), ); } _choiceIntervalChipWidget(int index) { return ChoiceChip( selectedColor: Colors.grey.withValues(alpha: 0.1), pressElevation: 0, showCheckmark: false, side: BorderSide.none, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), label: Text(chipsIntervalLabelList[index]), labelStyle: TextStyle( color: appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12, ), selected: _selectedIntervalChipIndex == index, onSelected: (bool selected) { profileCubit.getMyProfile(period: chipsIntervalList[index]); setState(() { _selectedIntervalChipIndex = index; }); }, ); } _choiceChipWidget(int index) { return ChoiceChip( selectedColor: appTheme.blueA400, pressElevation: 0, showCheckmark: false, side: BorderSide.none, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), label: Text(chipsLabelList[index]), labelStyle: TextStyle( color: index == _selectedChipIndex ? Colors.white : appTheme.gray600, fontWeight: FontWeight.w500, fontSize: 12, ), selected: _selectedChipIndex == index, backgroundColor: appTheme.gray100, onSelected: (bool selected) { setState(() { _selectedChipIndex = index; }); }, ); } }