import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/string_extentions.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/info_row.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/author_card.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/expandable_text.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart'; import 'package:eatvio_app/core/widgets/main_background_bouble.dart'; import 'package:eatvio_app/features/diets/presentation/widgets/more_options.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_state.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_cubit.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart'; import 'package:eatvio_app/features/plans/presentation/widgets/options_tab.dart'; import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/svg.dart'; import '../widgets/option_widget.dart'; class AboutPlanScreen extends StatefulWidget { AboutPlanScreen({super.key, required this.plan, required this.navigateToComments, required this.index}); final PlanModel plan; final bool navigateToComments; final int? index; @override State createState() => _AboutPlanScreenState(); } class _AboutPlanScreenState extends State { final CommentsCubit commentsCubit = sl(); int? replyId; final ScrollController scrollController = ScrollController(); final textEditingController = TextEditingController(); bool isMoreOptionsVisible = false; bool _jumped = false; @override void initState() { super.initState(); scrollController.addListener(() { if (isMoreOptionsVisible) { setState(() { isMoreOptionsVisible = false; }); } }); } void _scrollToBottom() { _jumped = true; scrollController.jumpTo( scrollController.position.maxScrollExtent, ); } @override Widget build(BuildContext context) { final s = S.of(context); return MultiBlocProvider( providers: [ BlocProvider( create: (context) => commentsCubit ..getComments( widget.plan.slug, CommentSourceType.plan, true, ), ), BlocProvider( create: (context) => sl()..getPlanWithLoading(widget.plan.slug), ), ], child: BlocConsumer( listener: (BuildContext context, CommentsState commentsState) { if (commentsState is CommentsStateLoaded) { setState(() { replyId = commentsState.replyId; }); } }, builder: (BuildContext context, CommentsState commentsState) { return BlocBuilder( builder: (context, state) { if (state.isLoading) return Scaffold(body: Loader()); WidgetsBinding.instance.addPostFrameCallback((_) { if (widget.navigateToComments && !_jumped) { _scrollToBottom(); } }); return Scaffold( backgroundColor: appTheme.gray100, resizeToAvoidBottomInset: true, bottomSheet: Container( padding: 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), ), ), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: SizedBox( child: FloatingActionButton.extended( heroTag: 'nextButtonRegistration', highlightElevation: 0, elevation: 0, focusElevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), backgroundColor: appTheme.greenA700, onPressed: () { /// TODO: добавить обработку покупки плана // showModalBottomSheet( // context: context, // builder: (context) => StartDietBottomSheet( // aboutDietCubit: context.read(), // ), // ); }, label: Text( s.startDiet, style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700), ), ), ), ), SizedBox(width: 8), BookmarkButton( isFavorite: state.planModel?.isFavorite ?? false, onTap: () { final planModel = state.planModel; if (planModel == null) return; if (planModel.isFavorite) { context.read().removeFromFavorite(slug: planModel.slug, index: widget.index); } else { context.read().addToFavorite(slug: planModel.slug, index: widget.index); } }, width: 52, height: 52, size: 24, shape: BoxShape.circle, color: appTheme.gray50, ), ], ), SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (replyId != null) GestureDetector( onTap: () { context.read().setReplyId(null); setState(() { replyId = null; textEditingController.clear(); }); FocusManager.instance.primaryFocus?.unfocus(); }, child: Container( decoration: BoxDecoration( color: appTheme.blueA400, shape: BoxShape.circle, ), height: 32, width: 32, child: Icon( Icons.close_rounded, size: 18, color: Colors.white, ), ), ), if (replyId != null) SizedBox(width: 8), Expanded( child: SizedBox( child: TextFormField( onChanged: (_) { setState(() {}); }, controller: textEditingController, maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, style: TextStyles.textMMedium.copyWith(color: appTheme.black), decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), hintText: s.comment.capitalize(), hintStyle: TextStyles.textMMedium.copyWith(color: appTheme.lightGray600), filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: Color(0xFFD1D1D6), width: 1, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: Color(0xFFD1D1D6), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide( color: Color(0xFFD1D1D6), width: 1, ), ), ), ), ), ), SizedBox(width: 8), GestureDetector( onTap: () { if (textEditingController.text.length > 2) { commentsCubit.sendComment( id: widget.plan.slug, commentText: textEditingController.text, parentCommentId: replyId, commentSourceType: CommentSourceType.plan, ); context.read().setReplyId(null); setState(() { textEditingController.clear(); replyId = null; }); Future.delayed(Duration(milliseconds: 500), () { context.read().getPlan(widget.plan.slug); }); FocusManager.instance.primaryFocus?.unfocus(); } }, child: Container( decoration: BoxDecoration( color: textEditingController.text.length > 2 ? appTheme.blueA400 : appTheme.gray50, shape: BoxShape.circle, ), height: 32, width: 32, child: Icon( Icons.send_rounded, size: 18, color: textEditingController.text.length > 2 ? appTheme.whiteA700 : appTheme.black, ), ), ), ], ), ], ), ), ), appBar: AppBar( centerTitle: true, title: Text( widget.plan.name, maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), actions: [ PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( path: 'plan/${widget.plan.id}', ), anchor: Aligned( shiftToWithinBound: AxisFlag(x: true, y: true), follower: Alignment.topRight, target: Alignment.bottomRight, ), child: IconButton( onPressed: () { setState(() { isMoreOptionsVisible = !isMoreOptionsVisible; }); }, icon: SvgPicture.asset( 'assets/icons/more_button.svg', width: 20, height: 20, ), ), ), ], ), body: SingleChildScrollView( controller: scrollController, padding: EdgeInsets.only(bottom: 160), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 8), MainBackgroundBouble( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ CachedNetworkImage( imageUrl: baseUrl + '/storage/' + widget.plan.image, height: 300, width: MediaQuery.sizeOf(context).width, fit: BoxFit.fill, ), Positioned( bottom: 16, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ InfoBouble( children: [ SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12), SizedBox(width: 8), Text( widget.plan.totalViews.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), ), ], backgroundColor: appTheme.whiteA700, ), SizedBox(width: 12), GestureDetector( onTap: () { context.read().likePlan(slug: widget.plan.slug, index: widget.index); }, child: InfoBouble( children: [ SvgPicture.asset( 'assets/icons/heart.svg', width: 14, height: 12, /// TODO задача для бека на добавление wasLokedByMe? colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn), ), SizedBox(width: 8), Text( widget.plan.totalLikes.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), ), ], backgroundColor: appTheme.whiteA700, ), ), SizedBox(width: 12), InfoBouble( children: [ SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12), SizedBox(width: 8), Text( widget.plan.totalComments.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), ), ], backgroundColor: appTheme.whiteA700, ), ], ), ), ], ), SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( s.about_plan, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), ), SizedBox(height: 12), /// TODO глянуть как заменить InfoRow(plan: state.planModel), ExpandableText( text: widget.plan.details, ), SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AuthorCard( author: widget.plan.author, title: s.plan_author, ), ), SizedBox(height: 16), ], ), ), SizedBox(height: 16), if (state.planModel?.packetsCount == 3) ...[ OptionsTab( cost: [ state.planModel?.packet1Options?.price ?? 0, state.planModel?.packet2Options?.price ?? 0, state.planModel?.packet3Options?.price ?? 0, ], ), SizedBox(height: 12), ], switch (state.chosenOptionIndex) { 1 => OptionWidget( option: state.planModel?.packet2Options, ), 2 => OptionWidget( option: state.planModel?.packet3Options, ), _ => OptionWidget( option: state.planModel?.packet1Options, ), }, SizedBox( height: 16, ), MainBackgroundBouble( padding: EdgeInsets.symmetric(vertical: 16), child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.centerLeft, child: Text( '${state.planModel?.totalComments ?? 0} ${getCommentForm(state.planModel?.totalComments ?? 0, s)}', style: TextStyles.textMMedium.copyWith( color: appTheme.gray500, ), ), ), SizedBox(height: 16), if (commentsState is CommentsStateLoading) const Loader(), if (commentsState is CommentsStateLoaded) CommentsWidget( id: widget.plan.id.toString(), commentSourceType: CommentSourceType.plan, comment: commentsState.comment, replyId: replyId, ), ], ), ), ], ), ), ); }, ); }, ), ); } }