Merge branch 'main' into personalDiets

This commit is contained in:
Username1548
2025-07-14 12:17:37 +07:00
26 changed files with 500 additions and 112 deletions

View File

@@ -7,8 +7,8 @@ import 'package:eatvio_app/core/widgets/custom_popup.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_cubit.dart';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.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/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/core/locator_service.dart';

View File

@@ -72,6 +72,12 @@ class ComplainingCubit extends Cubit<ComplainingState> {
emit(state.copyWith(wasSentSuccessfully: true));
return;
}
case ComplainedContent.comment:
if (id != null) {
await repository.sendCommentComplain(id: id, category: category.getValue(), body: state.complainInfo);
emit(state.copyWith(wasSentSuccessfully: true));
return;
}
}
}
} catch (e, st) {

View File

@@ -64,4 +64,16 @@ class ComplainingRepository {
rethrow;
}
}
Future<void> sendCommentComplain({
required int id,
required String category,
required ComplainModel body,
}) async {
try {
await service.sendCommentComplain(id: id, category: category, body: body);
} catch (e) {
rethrow;
}
}
}

View File

@@ -42,5 +42,12 @@ abstract class ComplainingService {
@Path() required String category,
@Body() required ComplainModel body,
});
@POST('/api/comments/{id}/complain/{category}')
Future<void> sendCommentComplain({
@Path() required int id,
@Path() required String category,
@Body() required ComplainModel body,
});
}

View File

@@ -21,8 +21,8 @@ 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/start_diet_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/diets_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/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/generated/l10n.dart';

View File

@@ -1,81 +0,0 @@
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/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CommentsCubit extends Cubit<CommentsState> {
final FeedRepository feedRepository;
CommentsCubit({
required this.feedRepository,
}) : super(const CommentsState());
void setReplyId(int? replyId) {
emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
}
Future<void> getComments(String id, CommentSourceType commentSourceType, [bool isFirstFetch = false]) async {
if (isFirstFetch) emit(const CommentsState.loading());
try {
switch (commentSourceType) {
case CommentSourceType.blogPost:
emit(CommentsState.loaded(comment: await feedRepository.getBlogPostComments(id: id)));
break;
case CommentSourceType.diet:
emit(CommentsState.loaded(comment: await feedRepository.getDietsComments(id: id)));
break;
case CommentSourceType.recipe:
emit(CommentsState.loaded(comment: await feedRepository.getRecipesComments(id: id)));
break;
case CommentSourceType.plan:
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
break;
}
} catch (e) {
emit(CommentsState.error(errorMessage: e.toString()));
}
}
Future<void> sendComment({
required String id,
required String commentText,
required CommentSourceType commentSourceType,
int? parentCommentId,
}) async {
try {
switch (commentSourceType) {
case CommentSourceType.blogPost:
await feedRepository.sendBlogPostComment(
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break;
case CommentSourceType.diet:
await feedRepository.sendDietsComment(
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break;
case CommentSourceType.recipe:
await feedRepository.sendRecipesComment(
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break;
case CommentSourceType.plan:
await feedRepository.sendPlansComment(
slug: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break;
}
getComments(id, commentSourceType);
} catch (e) {
emit(CommentsState.error(errorMessage: e.toString()));
}
}
}

View File

@@ -1,20 +0,0 @@
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'comments_state.freezed.dart';
@freezed
abstract class CommentsState with _$CommentsState {
const factory CommentsState() = _CommentsState;
const factory CommentsState.loading() = CommentsStateLoading;
const factory CommentsState.loaded({
required CommentModel comment,
int? replyId,
}) = CommentsStateLoaded;
const factory CommentsState.error({
required String errorMessage,
}) = CommentsStateError;
}

View File

@@ -1,14 +1,19 @@
import 'package:eatvio_app/core/comments/presentation/comments_actions_bottom_sheet.dart';
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/enums/complained_content.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
import 'package:eatvio_app/features/feed/models/comment_model.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/core/comments/comments_cubit/comments_cubit.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -53,98 +58,133 @@ class _CommentsWidgetState extends State<CommentsWidget> {
);
}
Container _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: widget.replyId == comment.id ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
GoRouter.of(context).pushNamed(
'profileScreen',
extra: Author(
authorSlug: comment.authorSlug,
authorName: comment.authorName,
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
authorIsMe: (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug ? true : false,
),
);
},
child: CircleAvatar(
foregroundImage: NetworkImage(
comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: radius,
Widget _comment(S s, Comment comment, [double radius = 18, bool isChild = false]) {
final isMe = (sl<ProfileAvatarCubit>().state as ProfileAvatarStateLoaded).profile.slug == comment.authorSlug;
return GestureDetector(
onTap: () {
showCommentsActionsBottomSheet(
context,
commentText: comment.body,
reply: !isChild
? () {
context.read<CommentsCubit>().setReplyId(comment.id);
}
: null,
copy: () {
Clipboard.setData(ClipboardData(text: comment.body));
showInfoToast(s.link_was_copied);
},
edit: (String text) {
context.read<CommentsCubit>().updateComment(id: comment.id, body: text);
},
delete: () {
context.read<CommentsCubit>().deleteComment(id: comment.id);
},
complain: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => ComplainingBottomSheet(
complainedContentType: ComplainedContent.comment,
id: comment.id,
),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
comment.authorName,
style: TextStyle(
height: 1,
color: appTheme.gray,
fontSize: 14,
fontWeight: FontWeight.w500,
);
},
isMyComment: isMe,
);
},
child: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: widget.replyId == comment.id ? appTheme.blueA400.withValues(alpha: 0.1) : Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
GoRouter.of(context).pushNamed(
'profileScreen',
extra: Author(
authorSlug: comment.authorSlug,
authorName: comment.authorName,
authorProfileIcon: comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
authorIsMe: isMe,
),
);
},
child: CircleAvatar(
foregroundImage: NetworkImage(
comment.authorProfileIcon.contains('https') ? comment.authorProfileIcon : baseUrl + comment.authorProfileIcon,
),
backgroundImage: const AssetImage('assets/images/no_avatar.png'),
radius: radius,
),
const SizedBox(height: 4),
SizedBox(
width: isChild ? MediaQuery.of(context).size.width * 0.72 : MediaQuery.of(context).size.width * 0.8,
child: Text(
comment.body,
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
comment.authorName,
style: TextStyle(
height: 1,
color: appTheme.black,
fontSize: 16,
color: appTheme.gray,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
comment.createdAt.format('dd MMMM', 'ru'),
const SizedBox(height: 4),
SizedBox(
width: isChild ? MediaQuery.of(context).size.width * 0.72 : MediaQuery.of(context).size.width * 0.8,
child: Text(
comment.body,
style: TextStyle(
height: 1,
color: appTheme.gray,
fontSize: 14,
color: appTheme.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 16),
if (!isChild)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(comment.id);
},
child: Text(
s.answer,
style: TextStyle(
height: 1,
color: appTheme.gray,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
comment.createdAt.format('dd MMMM', 'ru'),
style: TextStyle(
height: 1,
color: appTheme.gray,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
],
const SizedBox(width: 16),
if (!isChild)
GestureDetector(
onTap: () {
context.read<CommentsCubit>().setReplyId(comment.id);
},
child: Text(
s.answer,
style: TextStyle(
height: 1,
color: appTheme.gray,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
),
],
),
),
),
);

View File

@@ -66,7 +66,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
Future<void> getNotifications() async {
try {
final notifications = await notificationsRepository.getNotifications(page: 2);
final notifications = await notificationsRepository.getNotifications(page: 1);
_notifications.clear();
_notifications.addAll(notifications ?? []);
emit(

View File

@@ -67,7 +67,7 @@ class _PlanSubscriptionStartedToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.fillYasterday,
notificationsText: s.userSubscribedToPlan(notification.customerName, notification.planName),
);
}
}
@@ -81,7 +81,7 @@ class _PlanSubscriptionCanceledToAuthorWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.fillYasterday,
notificationsText: s.userCanceldThePlanSubscription(notification.customerName, notification.planName),
);
}
}
@@ -95,7 +95,7 @@ class _ChosenPlanStartedToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.fillYasterday,
notificationsText: s.planStarted(notification.planName),
);
}
}
@@ -109,7 +109,7 @@ class _ChosenPlanTomorrowReminderToClientWidget extends StatelessWidget {
return _BaseNotificationWidget(
isRead: notification.isRead,
notificationsText: s.fillYasterday,
notificationsText: s.tomorrowYourPlanWillStart(notification.planName),
);
}
}

View File

@@ -15,8 +15,8 @@ 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/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/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';

View File

@@ -16,8 +16,8 @@ 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/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/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/recipe/presentation/cubit/about_recipe_cubit.dart';