import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/blog_post_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/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/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/weight_chart_widget.dart'; import 'package:eatvio_app/locator_service.dart'; import 'package:eatvio_app/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:rive/rive.dart'; import 'package:flutter_svg/flutter_svg.dart'; class ProfileScreen extends StatefulWidget { final Author? author; const ProfileScreen({ super.key, this.author, }); @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { final ProfileCubit profileCubit = 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; @override void initState() { super.initState(); widget.author == null ? profileCubit.getMyProfile() : profileCubit.getProfileBySlug(widget.author!.authorSlug); } @override Widget build(BuildContext context) { return BlocBuilder( bloc: profileCubit, builder: (BuildContext context, ProfileState state) { if (state is ProfileStateLoaded) { if (state.circumferencePoints != null) { circumferencePoints = state.circumferencePoints!; weightPoints = state.weightPoints!; } return _profileScreen(state); } else if (state is ProfileStateError) { return Scaffold( appBar: AppBar(), body: Center( child: Padding( padding: const EdgeInsets.all(16), child: Text(state.errorMessage), ), ), ); } else { return Scaffold( appBar: AppBar(), body: Center( child: SizedBox( height: 32, width: 32, child: RiveAnimation.asset( 'assets/rive_animations/loading.riv', ), ), ), ); } }, ); } _profileScreen(ProfileStateLoaded state) { return Scaffold( backgroundColor: Colors.grey.shade200, appBar: widget.author == null ? AppBar(toolbarHeight: 0) : 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.profileDetails.additionalInfo.isNotEmpty) _additionalInfo(state), if (widget.author != null) const SizedBox(height: 8), if (widget.author != null) _messageAndSubscribe(), if (widget.author != null) const SizedBox(height: 16), ], ), ), _subscriptionsAndSubscribers(state), if(state.profile.badges.isNotEmpty) _badges(state), if (widget.author == null) const SizedBox(height: 16), _socialLinks(state), if (widget.author == null) const SizedBox(height: 16), _education(state), if (widget.author == null) const SizedBox(height: 16), _diplomasAndCertificates(state), if (widget.author == null) const SizedBox(height: 16), if (widget.author == null) _charts(), if (widget.author == null) const SizedBox(height: 16), _activePlans(state), if (widget.author == null) const SizedBox(height: 16), if (widget.author == null) _newBlogPostButton(), _blogPosts(state), ], ), ), ); } _activePlans(ProfileStateLoaded state) { if (state.profile.role != 'consultant') { 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, ), ), Text( widget.author == null ? 'Редактировать' : 'Показать все', style: TextStyle( color: Color(0xFF2F80ED), fontSize: 16, fontWeight: FontWeight.w500, ), ), ], ), ), const SizedBox(height: 8), _bullets(0, 4), ], ); } _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.role != 'consultant') { return const SizedBox.shrink(); } return 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: [ _diplomaImage( state.profile.profilePhotoUrl, ), const SizedBox(width: 8), _diplomaImage( state.profile.profilePhotoUrl, ), const SizedBox(width: 8), _diplomaImage( state.profile.profilePhotoUrl, ), const SizedBox(width: 8), _diplomaImage( state.profile.profilePhotoUrl, ), ], ), ), ), ), ], ); } _diplomaImage(String url) { return 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.role != 'consultant') { 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, 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: [ _educationBlock( 'Наименование диплома или сертификата в 2 строки', '30.01.2020', ), ], ), ), ), ), ], ); } _educationBlock(String title, String date) { return 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.role != 'consultant') { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Material( color: Colors.white, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ _socialLinkButton(() {}, 'assets/icons/vk.svg'), const SizedBox(width: 12), _socialLinkButton(() {}, 'assets/icons/telegram.svg'), const SizedBox(width: 12), _socialLinkButton(() {}, 'assets/icons/web.svg'), ], ), ), ), ); } _socialLinkButton(Function() onTap, String icon) { return InkWell( onTap: 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, ), ), ), ); } _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, ), 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: () {}, 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: () {}, 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) => BlogPostWidget( feedBlogPostModel: state.blogPosts[index], ), 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.withOpacity(0.4), height: 1, ), ), ); } _header(ProfileStateLoaded state) { return Padding( padding: const EdgeInsets.all(16), child: Row( children: [ CircleAvatar( radius: 32, backgroundImage: NetworkImage(state.profile.profilePhotoUrl), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( state.profile.name, style: TextStyle( color: Colors.black, fontWeight: FontWeight.w600, fontSize: 16, height: 1, ), ), const SizedBox(height: 8), 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(), IconButton( onPressed: () {}, icon: Icon(Icons.more_horiz_rounded), ), ], ), ); } _messageAndSubscribe() { 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.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: Text( 'Сообщение', style: TextStyle( color: appTheme.blueA400, ), ), ), ), SizedBox(width: 8), Container( alignment: Alignment.center, padding: EdgeInsets.all(8), width: (MediaQuery.of(context).size.width - 40) / 2, decoration: BoxDecoration( color: appTheme.blueA400, borderRadius: BorderRadius.circular(16), ), child: Text( 'Подписаться', style: TextStyle( color: 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, ); } 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.withOpacity(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; }); }, ); } }