Files
eatvio_app/lib/features/plans/presentation/pages/about_plan_screen.dart

557 lines
28 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:eatvio_app/core/app_config.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/widgets/bookmark_button.dart';
import 'package:eatvio_app/core/widgets/custom_popup.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/core/widgets/show_error_dialog.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/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_state.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.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_svg/svg.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/widgets/option_widget.dart';
class AboutPlanScreen extends StatefulWidget {
const AboutPlanScreen({super.key, required this.slug, required this.navigateToComments, required this.index});
final String slug;
final bool navigateToComments;
final int? index;
@override
State<AboutPlanScreen> createState() => _AboutPlanScreenState();
}
class _AboutPlanScreenState extends State<AboutPlanScreen> {
final CommentsCubit commentsCubit = sl<CommentsCubit>();
int? replyId;
final ScrollController scrollController = ScrollController();
final textEditingController = TextEditingController();
bool isMoreOptionsVisible = false;
bool _jumped = false;
@override
void initState() {
super.initState();
context.read<FeedCubit>().viewPlan(widget.slug);
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 PopScope(
child: MultiBlocProvider(
providers: [
BlocProvider<CommentsCubit>(
create: (context) => commentsCubit
..getComments(
widget.slug,
CommentSourceType.plan,
true,
),
),
BlocProvider<AboutPlanCubit>(
create: (context) => sl<AboutPlanCubit>()..getPlanWithLoading(widget.slug),
),
],
child: BlocConsumer<CommentsCubit, CommentsState>(
listener: (BuildContext context, CommentsState commentsState) {
if (commentsState is CommentsStateLoaded) {
setState(() {
replyId = commentsState.replyId;
});
}
},
builder: (BuildContext context, CommentsState commentsState) {
return BlocBuilder<AboutPlanCubit, AboutPlanState>(
builder: (context, state) {
if (state.isLoading) {
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
centerTitle: true,
title: Text(
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
body: const Loader(),
);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.navigateToComments && !_jumped) {
_scrollToBottom();
}
});
if (state.error != null) {
return Scaffold(
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
centerTitle: true,
title: Text(
state.planModel?.name ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: state.planModel?.author.authorIsMe ?? false,
path: 'plan/${widget.slug}',
complainedContentType: ComplainedContent.plan,
userSlug: state.planModel?.author.authorSlug ?? '',
),
const SizedBox(width: 16),
],
),
body: DataErrorWidget(onReload: () => context.read<AboutPlanCubit>().getPlan(widget.slug)),
);
}
return Scaffold(
backgroundColor: appTheme.gray100,
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),
),
),
),
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: () async {
final (chosenPlan, error) = await context.read<AboutPlanCubit>().chosePlan();
if (error == 'hasPlan') {
showErrorDialog(context, title: s.end_previous_plan, description: s.you_can_not_start_plan_until_end_of_prev_one);
return;
}
if (chosenPlan == null) {
if (!context.mounted) return;
showErrorDialog(context, title: 'Не удалось заказать план');
return;
}
if (!context.mounted) return;
GoRouter.of(context).pushReplacementNamed(
'chatScreen',
extra: {
'uuid': chosenPlan.chatId,
'sendInitialMessage': true,
},
);
},
label: Text(
s.to_order,
style: TextStyles.textMSemibold.copyWith(color: appTheme.whiteA700),
),
),
),
),
const SizedBox(width: 8),
BookmarkButton(
isFavorite: state.planModel?.isFavorite ?? false,
onTap: () {
final planModel = state.planModel;
if (planModel == null) return;
if (planModel.isFavorite) {
context.read<AboutPlanCubit>().removeFromFavorite(slug: planModel.slug, index: widget.index);
} else {
context.read<AboutPlanCubit>().addToFavorite(slug: planModel.slug, index: widget.index);
}
},
width: 52,
height: 52,
size: 24,
shape: BoxShape.circle,
color: appTheme.gray50,
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (replyId != null)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().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: const Icon(
Icons.close_rounded,
size: 18,
color: Colors.white,
),
),
),
if (replyId != null) const SizedBox(width: 8),
Expanded(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 52),
child: TextFormField(
textCapitalization: TextCapitalization.sentences,
onChanged: (_) {
setState(() {});
},
controller: textEditingController,
maxLines: 5,
minLines: 1,
keyboardType: TextInputType.multiline,
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),
),
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: Color(0xFFD1D1D6), width: 1),
),
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: () {
if (textEditingController.text.length > 2) {
commentsCubit.sendComment(
id: widget.slug,
commentText: textEditingController.text,
parentCommentId: replyId,
commentSourceType: CommentSourceType.plan,
);
context.read<CommentsCubit>().setReplyId(null);
setState(() {
textEditingController.clear();
replyId = null;
});
Future.delayed(const Duration(milliseconds: 500), () {
if (context.mounted) {
context.read<AboutPlanCubit>().getPlan(widget.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,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
state.planModel?.name ?? '',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
actions: [
CustomPopup(
isAuthor: state.planModel?.author.authorIsMe ?? false,
path: 'plan/${widget.slug}',
complainedContentType: ComplainedContent.plan,
userSlug: state.planModel?.author.authorSlug ?? '',
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
controller: scrollController,
padding: const EdgeInsets.only(bottom: 160),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
MainBackgroundBouble(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
CachedNetworkImage(
imageUrl: '${sl<AppConfig>().baseUrl}/storage/${state.planModel?.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(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/eye.svg', width: 18, height: 12),
const SizedBox(width: 8),
Text(
state.planModel?.totalViews.toString() ?? '',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
context.read<AboutPlanCubit>().likePlan(slug: widget.slug, index: widget.index);
},
child: InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset(
'assets/icons/heart.svg',
width: 14,
height: 12,
colorFilter:
state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
),
const SizedBox(width: 8),
Text(
state.planModel?.totalLikes.toString() ?? '0',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
),
const SizedBox(width: 12),
InfoBouble(
backgroundColor: appTheme.whiteA700,
children: [
SvgPicture.asset('assets/icons/comment2.svg', width: 13, height: 12),
const SizedBox(width: 8),
Text(
state.planModel?.totalComments.toString() ?? '',
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
),
],
),
],
),
),
],
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
s.about_plan,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
const SizedBox(height: 12),
InfoRow(plan: state.planModel, type: ProductType.plan),
ExpandableText(
text: state.planModel?.details ?? '',
),
if (state.planModel != null) ...[
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AuthorCard(
author: (state.planModel?.author)!,
title: s.plan_author,
),
),
],
const SizedBox(height: 16),
],
),
),
const 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,
],
),
const SizedBox(height: 12),
],
switch (state.chosenOptionIndex) {
1 => OptionWidget(
option: state.planModel?.packet2Options,
),
2 => OptionWidget(
option: state.planModel?.packet3Options,
),
_ => OptionWidget(
option: state.planModel?.packet1Options,
),
},
const SizedBox(
height: 16,
),
MainBackgroundBouble(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
children: [
Container(
padding: const 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,
),
),
),
const SizedBox(height: 16),
if (commentsState is CommentsStateLoading) const Loader(),
if (commentsState is CommentsStateLoaded)
CommentsWidget(
id: state.planModel?.id.toString() ?? '',
commentSourceType: CommentSourceType.plan,
comment: commentsState.comment,
replyId: replyId,
),
],
),
),
],
),
),
);
},
);
},
),
),
);
}
}