import 'dart:developer'; import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/enums/complained_content.dart'; import 'package:eatvio_app/core/enums/product_type.dart'; import 'package:eatvio_app/core/utils/localization_utils.dart'; import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:eatvio_app/core/widgets/bookmark_button.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.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/recipes_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/recipe/presentation/cubit/about_recipe_cubit.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/ingredients_widget.dart'; import 'package:eatvio_app/features/recipe/presentation/widgets/steps_widget.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 'package:url_launcher/url_launcher.dart'; class AboutRecipeScreen extends StatefulWidget { final Recipe recipe; final int? index; final bool navigateToComments; AboutRecipeScreen({super.key, required this.recipe, required this.index, required this.navigateToComments}); @override State createState() => _AboutRecipeScreenState(); } class _AboutRecipeScreenState extends State { final CommentsCubit commentsCubit = sl(); final AboutRecipeCubit recipeCubit = 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 BlocProvider( create: (context) => recipeCubit..getRecipe(widget.recipe.slug), child: BlocProvider( create: (BuildContext context) => commentsCubit..getComments(widget.recipe.slug.toString(), CommentSourceType.recipe, true), child: BlocConsumer( listener: (BuildContext context, CommentsState commentsState) { if (commentsState is CommentsStateLoaded) { setState(() { replyId = commentsState.replyId; }); } }, builder: (BuildContext context, CommentsState state) { return BlocBuilder( builder: (context, recipeState) { if (recipeState is AboutRecipeStateLoaded) { Future.delayed(Duration(milliseconds: 500), () { 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( 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: ConstrainedBox( constraints: BoxConstraints(minHeight: 52), child: TextFormField( textCapitalization: TextCapitalization.sentences, onChanged: (_) { setState(() {}); }, controller: textEditingController, maxLines: 5, minLines: 1, keyboardType: TextInputType.multiline, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1.2), decoration: InputDecoration( isDense: false, // убираем true, чтобы padding учитывался корректно contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), // вертикальный паддинг увеличен hintText: 'Комментарий', hintStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w400, height: 1.2, color: Color(0xFFAEAEB2)), 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), ), ), ), ), ), const SizedBox(width: 8), GestureDetector( onTap: () { if (textEditingController.text.length > 2) { commentsCubit.sendComment( id: widget.recipe.slug, commentText: textEditingController.text, parentCommentId: replyId, commentSourceType: CommentSourceType.recipe, ); context.read().setReplyId(null); setState(() { textEditingController.clear(); replyId = null; }); Future.delayed(Duration(milliseconds: 500), () { recipeCubit.getRecipe(widget.recipe.slug, index: widget.index); }); FocusManager.instance.primaryFocus?.unfocus(); } }, child: Container( decoration: BoxDecoration( color: textEditingController.text.length > 2 ? appTheme.blueA400 : Colors.grey, shape: BoxShape.circle, ), height: 32, width: 32, child: Icon(Icons.send_rounded, size: 18, color: Colors.white), ), ), ], ), ], ), ), ), appBar: AppBar( centerTitle: true, title: Text( widget.recipe.title ?? '', maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), actions: [ PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( complainedContentType: ComplainedContent.recipe, path: 'recipe/${widget.recipe.slug}', slug: widget.recipe.slug, ), 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.recipe.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.recipe.viewsCount.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black)), ], backgroundColor: appTheme.whiteA700, ), SizedBox(width: 12), GestureDetector( onTap: () { recipeCubit.likeRecipe(widget.recipe.slug, widget.index); }, child: InfoBouble( children: [ SvgPicture.asset( 'assets/icons/heart.svg', width: 14, height: 12, colorFilter: recipeState.recipe.wasLikedByMe ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null, ), SizedBox(width: 8), Text( recipeState.recipe.likesCount.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( recipeState.recipe.commentsCount.toString(), style: TextStyles.textXSSemibold.copyWith(color: appTheme.black), ), ], backgroundColor: appTheme.whiteA700, ), ], ), ), Positioned( right: 14, child: BookmarkButton( onTap: () async { if (recipeState.recipe.isFavorite) { await context.read().removeFromFavorite(widget.recipe.slug, widget.index); } else { await context.read().addToFavorite(widget.recipe.slug, widget.index); } }, color: appTheme.whiteA700, isFavorite: recipeState.recipe.isFavorite, borderRadius: BorderRadius.vertical(bottom: Radius.circular(12)), width: 40, height: 44, size: 19, ), ), ], ), SizedBox(height: 16), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text(s.about_recipe, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black)), ), SizedBox(height: 12), InfoRow(recipe: widget.recipe, type: ProductType.recipe), if (widget.recipe.videoUrl != null && widget.recipe.videoUrl!.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 16, bottom: 12, top: 12), child: GestureDetector( onTap: () async { try { final uri = Uri.parse(widget.recipe.videoUrl!); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } } catch (e) { log(e.toString()); showErrorToast(s.could_not_launch_video); } }, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(s.check_out_video_recipe, style: TextStyles.textMSemibold.copyWith(color: appTheme.black)), SizedBox(width: 12), SvgPicture.asset('assets/icons/play_icon.svg', width: 16, height: 16), ], ), ), ), ExpandableText(text: widget.recipe.description), SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: AuthorCard(author: widget.recipe.author, title: s.recipe_author), ), SizedBox(height: 16), ], ), ), SizedBox(height: 12), if (widget.recipe.ingredients?.isNotEmpty == true) IngredientsWidget(recipe: widget.recipe), if (widget.recipe.steps?.isNotEmpty == true) ...[SizedBox(height: 12), StepsWidget(recipe: widget.recipe)], SizedBox(height: 8), MainBackgroundBouble( padding: EdgeInsets.symmetric(vertical: 16), child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 16), alignment: Alignment.centerLeft, child: Text( recipeState.recipe.commentsCount.toString() + ' ' + getCommentForm(widget.recipe.commentsCount, s), style: TextStyles.textMMedium.copyWith(color: appTheme.gray500), ), ), SizedBox(height: 16), if (state is CommentsStateLoading) const Loader(), if (state is CommentsStateLoaded) CommentsWidget(id: widget.recipe.slug, commentSourceType: CommentSourceType.recipe, comment: state.comment, replyId: replyId), ], ), ), ], ), ), ); } if (recipeState is RecipeStateError) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text( widget.recipe.title ?? '', maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), actions: [ PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( complainedContentType: ComplainedContent.recipe, path: 'recipe/${widget.recipe.slug}', slug: widget.recipe.slug, ), 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: DataErrorWidget(onReload: () => context.read().getRecipe(widget.recipe.slug, index: widget.index)), ); } return Scaffold( appBar: AppBar( centerTitle: true, title: Text( widget.recipe.title ?? '', maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, softWrap: true, style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black), ), actions: [ PortalTarget( visible: isMoreOptionsVisible, portalFollower: MoreOptions( complainedContentType: ComplainedContent.recipe, path: 'recipe/${widget.recipe.slug}', slug: widget.recipe.slug, ), 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: const Loader(), ); }, ); }, ), ), ); } }