merge upstream

This commit is contained in:
2026-03-12 14:13:03 +00:00
10 changed files with 72 additions and 35 deletions

View File

@@ -15,7 +15,7 @@ class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Домены для deep links (в т.ч. ссылки из пушей dev2.eatvio.com)
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru', 'dev2.eatvio.com', 'www.dev2.eatvio.com'];
static const List<String> _deepLinkHosts = ['eatvio.ru', 'www.eatvio.ru'];
static GoRouter appRouter = GoRouter(
initialLocation: RouteConstants.updateScreen,

View File

@@ -10,17 +10,14 @@ import 'package:eatvio_app/features/registration/presentation/pages/verify_email
import 'package:eatvio_app/features/registration_v2/presentation/pages/registration_v2_page.dart';
import 'package:go_router/go_router.dart';
/// Маршруты для аутентификации
class AuthRoutes {
/// Создание маршрутов аутентификации
static List<RouteBase> createRoutes() {
return [
GoRoute(
path: RouteConstants.authScreen,
name: 'authScreen',
builder: (context, state) => const AuthScreen(),
// registerAnonymousPushToken вызывается в AuthCubit.initializeUser()
// и не должен блокировать навигацию
routes: [
GoRoute(path: RouteConstants.registrationScreen, name: 'registrationScreen', builder: (context, state) => const RegistrationScreen()),
GoRoute(path: RouteConstants.registrationScreenV2, name: 'registrationScreenV2', builder: (context, state) => const RegistrationV2Page()),

View File

@@ -47,6 +47,7 @@ class DiaryCubit extends Cubit<DiaryState> {
}
String comment = '';
bool commentNeedsToBeFilled = false;
void setInitialDate(DateTime? date) {
if (date == null) return;
@@ -68,7 +69,7 @@ class DiaryCubit extends Cubit<DiaryState> {
return canSelect;
}
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId, DateTime? date}) async {
Future<bool> fetchDiaryV2({bool needLoading = false, int? diaryId}) async {
final currentState = state;
try {
if (needLoading) {
@@ -283,9 +284,15 @@ class DiaryCubit extends Cubit<DiaryState> {
}
}
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
await updateCurrentDiary();
Future<bool> finishDiaryDay({required int diaryId, required String wakeUp, required String retire}) async {
try {
await diaryRepository.finishDiaryDay(diaryId: diaryId, wakeUp: wakeUp, retire: retire, comment: comment.isEmpty ? null : comment);
await updateCurrentDiary();
return true;
} catch (e, st) {
log(e.toString(), stackTrace: st);
return false;
}
}
Future<NutritionDataModel> getNutrition(int index) async {
@@ -422,6 +429,7 @@ class DiaryCubit extends Cubit<DiaryState> {
void fillComment(String comment) {
this.comment = comment;
commentNeedsToBeFilled = comment.isNotEmpty && comment.length < 3;
}
}

View File

@@ -7,6 +7,7 @@ import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/widgets/main_background_bouble.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
@@ -39,12 +40,12 @@ class UserDiaryScreen extends StatelessWidget {
final int diaryId;
@override
Widget build(BuildContext context) {
return DiaryScreenContent(diaryId: diaryId,);
return DiaryScreenContent(diaryId: diaryId);
}
}
class DiaryScreenContent extends StatefulWidget {
const DiaryScreenContent({super.key, this.diaryId,this.date});
const DiaryScreenContent({super.key, this.diaryId, this.date});
final int? diaryId;
final DateTime? date;
@override
@@ -134,11 +135,14 @@ class _DiaryScreenContentState extends State<DiaryScreenContent> {
setState(() {
_isDiaryFinishing = true;
});
await context.read<DiaryCubit>().finishDiaryDay(
final isFinished = await context.read<DiaryCubit>().finishDiaryDay(
diaryId: state.diary.id,
wakeUp: state.diary.wakeup.toString(),
retire: state.diary.retire.toString(),
);
if (!isFinished && context.mounted) {
showErrorDialog(context);
}
setState(() {
_isDiaryFinishing = false;
});

View File

@@ -2,6 +2,7 @@ import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_text_form_field.dart';
import 'package:eatvio_app/features/create_diet/presentation/widgets/error_widget.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -15,10 +16,14 @@ class CommentForConsultantWidget extends StatefulWidget {
class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget> {
final controller = TextEditingController();
bool _commentNeedsToBeFilled = false;
@override
void initState() {
super.initState();
if (widget.comment != null) controller.text = widget.comment ?? '';
if (widget.comment != null) {
controller.text = widget.comment ?? '';
}
}
@override
@@ -41,13 +46,27 @@ class _CommentForConsultantWidgetState extends State<CommentForConsultantWidget>
CustomTextFormField(
controller: controller,
onChanged: (value) {
EasyDebounce.debounce('client-typing', const Duration(milliseconds: 500), () => context.read<DiaryCubit>().fillComment(value));
EasyDebounce.debounce(
'client-typing',
const Duration(milliseconds: 500),
() => context.read<DiaryCubit>().fillComment(value),
);
if (widget.comment == null) {
setState(() {
_commentNeedsToBeFilled = value.isNotEmpty && value.length < 3;
});
}
},
hasError: _commentNeedsToBeFilled,
hintText: 'Укажите дополнительную информацию',
minLines: 4,
maxLines: 4,
readOnly: widget.comment != null,
),
if (_commentNeedsToBeFilled) ...[
const SizedBox(height: 4),
const ValidationErrorWidget(error: 'Минимум 3 символа'),
],
],
),
),

View File

@@ -4,7 +4,9 @@ import 'package:eatvio_app/core/widgets/bottom_sheet_handler_bar.dart';
import 'package:eatvio_app/core/widgets/bottom_sheet_with_blur.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FinishDayButtonWidget extends StatefulWidget {
final Future<void> Function() onPressed;
@@ -18,6 +20,14 @@ class FinishDayButtonWidget extends StatefulWidget {
class _FinishDayButtonWidgetState extends State<FinishDayButtonWidget> {
void _onTap() {
if (context.read<DiaryCubit>().commentNeedsToBeFilled) {
showErrorDialog(
context,
title: 'Комментарий слишком короткий',
description: 'Минимум 3 символа в комментарии для консультанта.',
);
return;
}
if (!widget.hasProducts) {
showErrorDialog(
context,

View File

@@ -22,6 +22,11 @@ class PortionCommentWidget extends StatefulWidget {
class _PortionCommentWidgetState extends State<PortionCommentWidget> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(PortionCommentWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.portionId != oldWidget.portionId) {
@@ -57,17 +62,19 @@ class _PortionCommentWidgetState extends State<PortionCommentWidget> {
hintText: s.typePortionComment,
hasError: hasError,
onChanged: (value) {
final cubit = context.read<ConsultantCommentsCubit>();
EasyDebounce.debounce(
'client-typing',
'portion-comment-${widget.portionId}',
const Duration(milliseconds: 500),
() {
final hasPortionComment = state.commentsModel.portions.any((portion) => portion.id == widget.portionId);
final currentState = cubit.state;
final hasPortionComment = currentState.commentsModel.portions.any((portion) => portion.id == widget.portionId);
if (hasPortionComment) {
final portion = state.commentsModel.portions.firstWhere((portion) => portion.id == widget.portionId);
context.read<ConsultantCommentsCubit>().updatePortionComment(portion.copyWith(comment: value));
final portion = currentState.commentsModel.portions.firstWhere((portion) => portion.id == widget.portionId);
cubit.updatePortionComment(portion.copyWith(comment: value));
return;
}
context.read<ConsultantCommentsCubit>().addPortionComment(PortionCommentModel(id: widget.portionId, comment: value));
cubit.addPortionComment(PortionCommentModel(id: widget.portionId, comment: value));
},
);
},

View File

@@ -133,7 +133,7 @@ class NotificationWidget extends StatelessWidget {
CommentComplaintNotification() => _CommentComplaintWidget(notification: notification as CommentComplaintNotification),
CommentDeletedNotification() => _CommentDeletedWidget(notification: notification as CommentDeletedNotification),
UserSubscribedNotification() => _UserSubscribedWidget(notification: notification as UserSubscribedNotification),
UnknownNotification() => _UnknownNotificationWidget(notification: notification as UnknownNotification),
UnknownNotification() => const SizedBox.shrink(),
};
}
}
@@ -594,14 +594,6 @@ class _EntityCommentedWidget extends StatelessWidget {
}
}
class _UnknownNotificationWidget extends StatelessWidget {
const _UnknownNotificationWidget({required this.notification});
final UnknownNotification notification;
@override
Widget build(BuildContext context) {
return _BaseNotificationWidget(isRead: false, notificationsText: 'Неизвестное уведомление', createdAt: DateTime.now(), onTap: null);
}
}
// Новые виджеты для дополнительных типов уведомлений
class _PlanSubscriptionExpiredWidget extends StatelessWidget {

View File

@@ -1,6 +1,6 @@
name: eatvio_app
description: A new Flutter project.
version: 1.3.17+97
version: 1.3.18+98
publish_to: 'none'
environment:

View File

@@ -88,7 +88,7 @@ void main() {
when(diaryCubit.state).thenReturn(const DiaryState.loading());
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -109,7 +109,7 @@ void main() {
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
@@ -132,7 +132,7 @@ void main() {
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -161,7 +161,7 @@ void main() {
when(diaryCubit.state).thenReturn(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
@@ -193,7 +193,7 @@ void main() {
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
@@ -217,7 +217,7 @@ void main() {
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'), date: anyNamed('date'))).thenAnswer((_) async => true);
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));