merge upstream

This commit is contained in:
2026-04-16 17:22:23 +00:00
161 changed files with 3900 additions and 3280 deletions

View File

@@ -1,37 +1,39 @@
name: Eatvio App Android Test name: Eatvio App Android Test
on: [ pull_request ] on: [pull_request]
env: env:
flutter_version: "3.41.2" flutter_version: '3.41.2'
java_version: "21.x" java_version: '21.x'
flutter_channel: "stable" flutter_channel: 'stable'
my_local_cache_dir: "/opt/hostedtoolcache/" my_local_cache_dir: '/opt/hostedtoolcache/'
ANDROID_SDK_ROOT: "/opt/hostedtoolcache/Android/Sdk" ANDROID_SDK_ROOT: '/opt/hostedtoolcache/Android/Sdk'
jobs: jobs:
build: build:
runs-on: [ ubuntu-latest ] runs-on: [ubuntu-latest]
steps: steps:
- uses: https://github.com/actions/checkout@v3 - uses: https://github.com/actions/checkout@v3
- name: Set proxy server # Пока что не нужно проксирование
env: # - name: Set proxy server
PROXY_USER: ${{ secrets.PROXY_USER }} # env:
PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }} # PROXY_USER: ${{ secrets.PROXY_USER }}
run: | # PROXY_PASSWORD: ${{ secrets.PROXY_PASSWORD }}
echo "Setting up proxy..." # run: |
cat <<EOF >/etc/apt/apt.conf.d/proxy.conf # echo "Setting up proxy..."
Acquire::http::Proxy "socks5h://${PROXY_USER}:${PROXY_PASSWORD}@10.55.5.202:19843/"; # cat <<EOF >/etc/apt/apt.conf.d/proxy.conf
EOF # Acquire::http::Proxy "socks5h://${PROXY_USER}:${PROXY_PASSWORD}@10.55.5.202:19843/";
# EOF
- name: Checkout env repository - name: Checkout env repository
uses: https://github.com/actions/checkout@v3 uses: https://github.com/actions/checkout@v3
with: with:
repository: mso7nueh/eatvio_app_env repository: eatvio/eatvio_app_env
path: env_repo path: env_repo
token: ${{ vars.MSO7NUEH_TOKEN }} # Здесь может быть токен любого юзера с доступом в gitea
token: ${{ vars.READ_USER_GITEA_TOKEN }}
ref: main ref: main
- name: Copy .env file - name: Copy .env file
@@ -66,7 +68,7 @@ jobs:
- name: Cache Android SDK - name: Cache Android SDK
id: cache-sdk id: cache-sdk
uses: "https://github.com/actions/cache@v4" uses: 'https://github.com/actions/cache@v4'
with: with:
path: ${{ env.my_local_cache_dir }} path: ${{ env.my_local_cache_dir }}
key: ${{ runner.os }}-cache-sdk key: ${{ runner.os }}-cache-sdk
@@ -74,14 +76,14 @@ jobs:
- name: Set up JDK - name: Set up JDK
uses: https://github.com/actions/setup-java@v3 uses: https://github.com/actions/setup-java@v3
with: with:
distribution: "zulu" distribution: 'zulu'
java-version: ${{ env.java_version }} java-version: ${{ env.java_version }}
cache: gradle cache: gradle
- name: Install utils for Android SDK - name: Install utils for Android SDK
run: | run: |
apt-get update apt-get update
apt-get install -y openjdk-17-jdk wget jq clang cmake ninja-build libgtk-3-dev apt-get install -y openjdk-17-jdk wget jq clang cmake ninja-build libgtk-3-dev
- name: Install Android SDK - name: Install Android SDK
if: steps.cache-sdk.outputs.cache-hit != 'true' if: steps.cache-sdk.outputs.cache-hit != 'true'
@@ -100,15 +102,14 @@ jobs:
sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31" sdkmanager "platform-tools" "platforms;android-35" "platforms;android-34" "platforms;android-33" "platforms;android-31"
sdkmanager "build-tools;34.0.0" sdkmanager "build-tools;34.0.0"
sdkmanager "ndk;25.1.8937393" sdkmanager "ndk;25.1.8937393"
sdkmanager --install "cmake;3.22.1" sdkmanager --install "cmake;3.22.1"
- name: Set up Android environment variables - name: Set up Android environment variables
id: "android-env-setup" id: 'android-env-setup'
run: | run: |
echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV echo "ANDROID_SDK_ROOT=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV echo "PATH=$PATH:${{ env.my_local_cache_dir }}Android/Sdk/cmdline-tools/latest/bin:${{ env.my_local_cache_dir }}Android/Sdk/platform-tools:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/30.0.3:${{ env.my_local_cache_dir }}Android/Sdk/build-tools/34.0.0" >> $GITHUB_ENV
echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV echo "ANDROID_HOME=${{ env.my_local_cache_dir }}Android/Sdk" >> $GITHUB_ENV
- name: Set up Flutter - name: Set up Flutter
run: | run: |
@@ -147,7 +148,7 @@ jobs:
if [ "$ARCH" = "x86_64" ]; then ARCH="x64"; fi if [ "$ARCH" = "x86_64" ]; then ARCH="x64"; fi
git config --global --add safe.directory "${{ env.my_local_cache_dir }}flutter" git config --global --add safe.directory "${{ env.my_local_cache_dir }}flutter"
flutter config --android-sdk="$ANDROID_HOME" flutter config --android-sdk="$ANDROID_HOME"
flutter doctor -v flutter doctor -v
- name: Get dependencies - name: Get dependencies
run: | run: |
@@ -212,12 +213,11 @@ jobs:
percentage = (covered / total) * 100 percentage = (covered / total) * 100
printf "✅ Coverage: %.2f%%\n", percentage printf "✅ Coverage: %.2f%%\n", percentage
print "" print ""
if (percentage < 60) { if (percentage < 70) {
printf "⚠️ Warning: Coverage is below 60%%\n" printf "❌ FAIL: Coverage is below 70%%\n"
} else if (percentage < 80) { exit 1
printf "💛 Good: Coverage is above 60%%\n"
} else { } else {
printf "💚 Excellent: Coverage is above 80%%\n" printf "💚 Excellent: Coverage is above 70%%\n"
} }
} }
} }
@@ -236,7 +236,7 @@ jobs:
APK_NAME="eatvio_app_${TIMESTAMP}.apk" APK_NAME="eatvio_app_${TIMESTAMP}.apk"
mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME mv build/app/outputs/flutter-apk/app-release.apk $APK_NAME
echo "APK build finished $APK_NAME" echo "APK build finished $APK_NAME"
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
- name: Upload APK artifact - name: Upload APK artifact
uses: https://github.com/actions/upload-artifact@v3 uses: https://github.com/actions/upload-artifact@v3

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'dart:io' as io; import 'dart:io' as io;
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
import 'package:eatvio_app/core/locator_service.dart' as di; import 'package:eatvio_app/core/locator_service.dart' as di;
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/firebase_options.dart'; import 'package:eatvio_app/firebase_options.dart';
@@ -16,6 +17,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
/// Класс для инициализации приложения /// Класс для инициализации приложения
class AppInitializer { class AppInitializer {
/// Инициализация всех сервисов приложения /// Инициализация всех сервисов приложения
static Future<void> initialize({required AppBuildType buildType}) async { static Future<void> initialize({required AppBuildType buildType}) async {
await _initializeEnvironment(); await _initializeEnvironment();
@@ -79,11 +81,12 @@ class AppInitializer {
options.dsn = EnvConfig.sentryDsn; options.dsn = EnvConfig.sentryDsn;
options.tracesSampleRate = 1.0; options.tracesSampleRate = 1.0;
options.debug = false; options.debug = false;
options.beforeSend = (event, hint) {
if (kDebugMode) return null;
if (!ErrorReportPolicy.allowSentryEvent(event, hint)) return null;
return event;
};
}, appRunner: appRunner); }, appRunner: appRunner);
FlutterError.onError = (details) async {
if (kDebugMode) return;
await Sentry.captureException(details.exception, stackTrace: details.stack);
};
} }
} }

View File

@@ -1,4 +1,5 @@
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/routing/auth_routes.dart'; import 'package:eatvio_app/core/routing/auth_routes.dart';
import 'package:eatvio_app/core/routing/content_routes.dart'; import 'package:eatvio_app/core/routing/content_routes.dart';
import 'package:eatvio_app/core/routing/main_navigation_routes.dart'; import 'package:eatvio_app/core/routing/main_navigation_routes.dart';
@@ -6,6 +7,7 @@ import 'package:eatvio_app/core/routing/other_routes.dart';
import 'package:eatvio_app/core/routing/route_constants.dart'; import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/routing/settings_routes.dart'; import 'package:eatvio_app/core/routing/settings_routes.dart';
import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart'; import 'package:eatvio_app/features/auth/entities/auth_result_entity.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -14,8 +16,8 @@ import 'package:go_router/go_router.dart';
class AppRouter { class AppRouter {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Домены для deep links (в т.ч. ссылки из пушей dev2.eatvio.com) static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com', 'dev2.eatvio.com'];
static const List<String> deepLinkHosts = ['eatvio.ru', 'eatvio.com']; static const List<String> metaSegments = ['ru', 'en', 'api', 'v2'];
static GoRouter appRouter = GoRouter( static GoRouter appRouter = GoRouter(
initialLocation: RouteConstants.updateScreen, initialLocation: RouteConstants.updateScreen,
@@ -30,11 +32,101 @@ class AppRouter {
], ],
); );
static String? _resolveRecipeDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 2) return null;
final contentIdentifier = effectiveSegments[1];
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter');
return '/about-recipes-screen?slug=$contentIdentifier';
}
static String? _resolveBlogPostDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 2) return null;
final contentIdentifier = effectiveSegments[1];
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter');
return '/blog-post-screen?id=$contentIdentifier';
}
static String? _resolvePlanDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 2) return null;
final contentIdentifier = effectiveSegments[1];
final planId = int.tryParse(contentIdentifier);
if (planId != null) {
developer.log('Deep link: diary', name: 'AppRouter');
return RouteConstants.diaryScreen;
}
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter');
return '/about-plan-screen?slug=$contentIdentifier';
}
static String? _resolveDietDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length == 2) {
final contentIdentifier = effectiveSegments[1];
final dietId = int.tryParse(contentIdentifier);
if (dietId != null) {
developer.log('Deep link: diet/id=$dietId', name: 'AppRouter');
return '/about-diet-screen?id=$dietId';
}
developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter');
return '/about-diet-screen?slug=$contentIdentifier';
}
const dietTabIndexWithHiddenPlansTab = 3;
const dietTabIndexWithoutHiddenPlansTab = 4;
final initialTabIndex = hidePlans ? dietTabIndexWithHiddenPlansTab : dietTabIndexWithoutHiddenPlansTab;
return '${RouteConstants.feedScreen}?initialTabIndex=$initialTabIndex';
}
static String? _resolveChatDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length == 2) {
final contentIdentifier = effectiveSegments[1];
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
} else if (effectiveSegments.length == 3 && effectiveSegments[1] == 'show') {
final contentIdentifier = effectiveSegments[2];
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter');
return '${RouteConstants.chatScreen}?uuid=$contentIdentifier';
}
developer.log('Deep link: chat', name: 'AppRouter');
return RouteConstants.chatSelectScreen;
}
static String? _resolveProfileDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length == 2) {
final contentIdentifier = effectiveSegments[1];
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter');
return '/profile-screen?slug=$contentIdentifier';
}
return RouteConstants.myProfileScreen;
}
static String? _resolveBadgeDeepLink(List<String> effectiveSegments) {
return RouteConstants.myProfileScreen;
}
static String? _resolveDiaryDeepLink(List<String> effectiveSegments) {
developer.log('Deep link: diary', name: 'AppRouter');
return RouteConstants.diaryScreen;
}
static String? _resolveConsultantDiaryCommentDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 2) return null;
final contentIdentifier = effectiveSegments[1];
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
return RouteConstants.diaryScreen;
}
static String? _resolveConsultationsDeepLink(List<String> effectiveSegments) {
if (effectiveSegments.length != 3 || effectiveSegments[1] != 'status') return RouteConstants.consultationsScreen;
final consultationId = effectiveSegments[2];
developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter');
return '${InfoByOrderScreen.path}?planId=$consultationId';
}
static String? _resolveDeepLinkUri(Uri uri) { static String? _resolveDeepLinkUri(Uri uri) {
final host = uri.host.toLowerCase(); final host = uri.host.toLowerCase();
final segments = uri.pathSegments; final segments = uri.pathSegments;
// Проверяем, что хост заканчивается на один из разрешенных доменов
final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost')); final isDeepLink = deepLinkHosts.any((allowedHost) => host == allowedHost || host.endsWith('.$allowedHost'));
if (!isDeepLink) { if (!isDeepLink) {
@@ -45,112 +137,61 @@ class AppRouter {
if (segments.isEmpty) { if (segments.isEmpty) {
developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter'); developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen; return RouteConstants.feedScreen;
} }
// Обработка api ссылок (например, из пушей чата) final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList();
if (segments.length >= 4 &&
segments[0] == 'api' &&
segments[1] == 'v2' &&
segments[2] == 'chat' &&
segments[3] == 'show') {
developer.log('Deep link: api/v2/chat/show → chat-select-screen', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
}
// Определяем, есть ли в начале сегмент языка (ru/en). Если есть — пропускаем его.
int startIndex = 0;
if (segments[0] == 'ru' || segments[0] == 'en') {
startIndex = 1;
} else {
developer.log('Deep link without language segment: ${segments[0]}', name: 'AppRouter');
}
final effectiveSegments = segments.sublist(startIndex);
if (effectiveSegments.isEmpty) { if (effectiveSegments.isEmpty) {
developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter'); developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen; return RouteConstants.feedScreen;
} }
final type = effectiveSegments[0]; final type = effectiveSegments[0];
if (effectiveSegments.length == 3 && type == 'consultations' && effectiveSegments[1] == 'status') { switch (type) {
final consultationId = effectiveSegments[2]; case 'recipe':
developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter'); final target = _resolveRecipeDeepLink(effectiveSegments);
FlutterNativeSplash.remove(); if (target != null) return target;
return '${RouteConstants.consultationsScreen}?id=$consultationId'; break;
} case 'blog_post':
final target = _resolveBlogPostDeepLink(effectiveSegments);
if (effectiveSegments.length == 2) { if (target != null) return target;
final contentIdentifier = effectiveSegments[1]; break;
case 'plan':
switch (type) { final target = _resolvePlanDeepLink(effectiveSegments);
case 'recipe': if (target != null) return target;
developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter'); break;
FlutterNativeSplash.remove(); case 'diet':
return '/about-recipes-screen?slug=$contentIdentifier'; final target = _resolveDietDeepLink(effectiveSegments);
case 'blog_post': if (target != null) return target;
developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter'); break;
FlutterNativeSplash.remove(); case 'chat':
return '/blog-post-screen?id=$contentIdentifier'; final target = _resolveChatDeepLink(effectiveSegments);
case 'plan': if (target != null) return target;
developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter'); break;
FlutterNativeSplash.remove(); case 'profile':
return '/about-plan-screen?slug=$contentIdentifier'; final target = _resolveProfileDeepLink(effectiveSegments);
case 'diet': if (target != null) return target;
{ break;
final dietId = int.tryParse(contentIdentifier); case 'badge':
if (dietId != null) { final target = _resolveBadgeDeepLink(effectiveSegments);
developer.log('Deep link: diet/id=$dietId', name: 'AppRouter'); if (target != null) return target;
FlutterNativeSplash.remove(); break;
return '/about-diet-screen?id=$dietId'; case 'diary':
} final target = _resolveDiaryDeepLink(effectiveSegments);
developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter'); if (target != null) return target;
FlutterNativeSplash.remove(); break;
return '/about-diet-screen?slug=$contentIdentifier'; case 'consultant_diary_comment':
} final target = _resolveConsultantDiaryCommentDeepLink(effectiveSegments);
case 'chat': if (target != null) return target;
developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter'); break;
FlutterNativeSplash.remove(); case 'consultations':
return RouteConstants.chatSelectScreen; final target = _resolveConsultationsDeepLink(effectiveSegments);
case 'profile': if (target != null) return target;
developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter'); break;
FlutterNativeSplash.remove();
return '/profile-screen?slug=$contentIdentifier';
case 'diary':
developer.log('Deep link: diary/$contentIdentifier', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
case 'consultant_diary_comment':
developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
}
}
if (effectiveSegments.length == 1) {
if (type == 'chat') {
developer.log('Deep link: chat', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.chatSelectScreen;
}
if (type == 'diary') {
developer.log('Deep link: diary', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.diaryScreen;
}
if (type == 'badge') {
developer.log('Deep link: badge → my-profile tab', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.myProfileScreen;
}
} }
developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter'); developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter');
FlutterNativeSplash.remove();
return RouteConstants.feedScreen; return RouteConstants.feedScreen;
} }
@@ -190,13 +231,13 @@ class AppRouter {
} }
final target = _resolveDeepLinkUri(uri); final target = _resolveDeepLinkUri(uri);
FlutterNativeSplash.remove();
if (target != null) { if (target != null) {
appRouter.go(target); appRouter.go(target);
} else { return;
developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter');
appRouter.go(RouteConstants.feedScreen);
} }
developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter');
appRouter.go(RouteConstants.feedScreen);
} }
static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) { static void navigateToRegisterViaSocial({required AuthResultEntity authResult, required String provider}) {

View File

@@ -5,15 +5,13 @@ import 'package:eatvio_app/core/comments/repositories/comments_repository.dart';
import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart'; import 'package:eatvio_app/features/feed/presentation/widgets/comments_widget.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class CommentsCubit extends Cubit<CommentsState> { class CommentsCubit extends Cubit<CommentsState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
final CommentsRepository commentsRepository; final CommentsRepository commentsRepository;
CommentsCubit({ CommentsCubit({required this.feedRepository, required this.commentsRepository}) : super(const CommentsState());
required this.feedRepository,
required this.commentsRepository,
}) : super(const CommentsState());
void setReplyId(int? replyId) { void setReplyId(int? replyId) {
emit((state as CommentsStateLoaded).copyWith(replyId: replyId)); emit((state as CommentsStateLoaded).copyWith(replyId: replyId));
@@ -36,51 +34,34 @@ class CommentsCubit extends Cubit<CommentsState> {
emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id))); emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id)));
break; break;
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(CommentsState.error(errorMessage: e.toString())); emit(CommentsState.error(errorMessage: e.toString()));
} }
} }
Future<void> sendComment({ Future<bool> sendComment({required String id, required String commentText, required CommentSourceType commentSourceType, int? parentCommentId}) async {
required String id,
required String commentText,
required CommentSourceType commentSourceType,
int? parentCommentId,
}) async {
try { try {
switch (commentSourceType) { switch (commentSourceType) {
case CommentSourceType.blogPost: case CommentSourceType.blogPost:
await feedRepository.sendBlogPostComment( await feedRepository.sendBlogPostComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break; break;
case CommentSourceType.diet: case CommentSourceType.diet:
await feedRepository.sendDietsComment( await feedRepository.sendDietsComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break; break;
case CommentSourceType.recipe: case CommentSourceType.recipe:
await feedRepository.sendRecipesComment( await feedRepository.sendRecipesComment(id: id, commentText: commentText, parentCommentId: parentCommentId);
id: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break; break;
case CommentSourceType.plan: case CommentSourceType.plan:
await feedRepository.sendPlansComment( await feedRepository.sendPlansComment(slug: id, commentText: commentText, parentCommentId: parentCommentId);
slug: id,
commentText: commentText,
parentCommentId: parentCommentId,
);
break; break;
} }
getComments(id, commentSourceType); getComments(id, commentSourceType);
} catch (e) { return true;
} catch (e, st) {
await reportFrom(e, st, this);
emit(CommentsState.error(errorMessage: e.toString())); emit(CommentsState.error(errorMessage: e.toString()));
return false;
} }
} }
@@ -93,6 +74,7 @@ class CommentsCubit extends Cubit<CommentsState> {
emit(CommentsState.loaded(comment: updatedCommentModel)); emit(CommentsState.loaded(comment: updatedCommentModel));
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(CommentsState.loaded(comment: commentModel)); emit(CommentsState.loaded(comment: commentModel));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
@@ -101,13 +83,15 @@ class CommentsCubit extends Cubit<CommentsState> {
Future<bool> updateComment({required int id, required String body}) async { Future<bool> updateComment({required int id, required String body}) async {
final commentModel = (state as CommentsStateLoaded).comment; final commentModel = (state as CommentsStateLoaded).comment;
final updatedCommentModel = final updatedCommentModel = commentModel.copyWith(
commentModel.copyWith(data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList()); data: commentModel.data.map((comment) => comment.id == id ? comment.copyWith(body: body) : comment).toList(),
);
try { try {
await commentsRepository.updateComment(id: id, body: body); await commentsRepository.updateComment(id: id, body: body);
emit(CommentsState.loaded(comment: updatedCommentModel)); emit(CommentsState.loaded(comment: updatedCommentModel));
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(CommentsState.loaded(comment: commentModel)); emit(CommentsState.loaded(comment: commentModel));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/comments/models/comment_update_model.dart'; import 'package:eatvio_app/core/comments/models/comment_update_model.dart';
import 'package:eatvio_app/core/comments/services/comments_service.dart'; import 'package:eatvio_app/core/comments/services/comments_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CommentsRepository { class CommentsRepository {
final CommentsService service; final CommentsService service;
@@ -8,20 +7,12 @@ class CommentsRepository {
CommentsRepository({required this.service}); CommentsRepository({required this.service});
Future<void> deleteComment({required int id}) async { Future<void> deleteComment({required int id}) async {
try { return service.deleteComment(id);
return service.deleteComment(id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> updateComment({required int id, required String body}) async { Future<void> updateComment({required int id, required String body}) async {
try { return service.updateComment(id, CommentUpdateModel(body: body));
return service.updateComment(id, CommentUpdateModel(body: body));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -2,6 +2,7 @@ import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
import 'package:eatvio_app/core/prefs.dart'; import 'package:eatvio_app/core/prefs.dart';
import 'package:eatvio_app/core/prefs_keys.dart'; import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class MarketingCubit extends Cubit<MarketingCubitState> { class MarketingCubit extends Cubit<MarketingCubitState> {
final Prefs prefs; final Prefs prefs;
@@ -22,8 +23,9 @@ class MarketingCubit extends Cubit<MarketingCubitState> {
await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true); await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true);
} }
return needToShowMarketingBottomSheet; return needToShowMarketingBottomSheet;
} catch (e) { } catch (e, st) {
rethrow; await reportFrom(e, st, this);
return false;
} }
} }
} }

View File

@@ -1,10 +1,13 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/constants/app_constants.dart'; import 'package:eatvio_app/core/constants/app_constants.dart';
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/core/error_reporting/sentry_error_reporter.dart';
import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart'; import 'package:eatvio_app/core/interceptors/phone_verify_error_interceptor.dart';
import 'package:eatvio_app/core/interceptors/token_interceptor.dart'; import 'package:eatvio_app/core/interceptors/token_interceptor.dart';
import 'package:eatvio_app/core/prefs.dart'; import 'package:eatvio_app/core/prefs.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:sentry_dio/sentry_dio.dart'; import 'package:sentry_dio/sentry_dio.dart';
@@ -14,6 +17,7 @@ class ExternalRegistrations {
/// Регистрация всех внешних зависимостей /// Регистрация всех внешних зависимостей
static void registerAll() { static void registerAll() {
sl.registerLazySingleton<ErrorReporter>(SentryErrorReporter.new);
_registerDio(); _registerDio();
_registerPrefs(); _registerPrefs();
} }
@@ -30,7 +34,9 @@ class ExternalRegistrations {
sendTimeout: AppConstants.pendingDelay, sendTimeout: AppConstants.pendingDelay,
), ),
)..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]) )..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()])
..addSentry(), ..addSentry(
failedRequestStatusCodes: const [SentryStatusCode.defaultRange()],
),
); );
} }

View File

@@ -72,7 +72,7 @@ class ServiceRegistrations {
sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl())); sl.registerLazySingleton<CancelPlanService>(() => CancelPlanService(sl()));
sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl())); sl.registerLazySingleton<CreatePlanService>(() => CreatePlanService(sl()));
sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl())); sl.registerLazySingleton<NotificationsService>(() => NotificationsService(sl()));
sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi()); sl.registerLazySingleton<FirebaseApi>(() => FirebaseApi(errorReporter: sl()));
sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl())); sl.registerLazySingleton<ResetPasswordService>(() => ResetPasswordService(sl()));
sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl())); sl.registerLazySingleton<BlockedUserService>(() => BlockedUserService(sl()));
sl.registerLazySingleton<CommentsService>(() => CommentsService(sl())); sl.registerLazySingleton<CommentsService>(() => CommentsService(sl()));

View File

@@ -0,0 +1,80 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
/// Rules for which errors are sent to crash reporting.
///
/// [shouldReport] is used by [ErrorReporter]. [allowSentryEvent] is used in
/// [SentryFlutter.init] as a last-line filter (Dio failed-request capture, etc.).
class ErrorReportPolicy {
ErrorReportPolicy._();
/// When `false`, the error is not sent via [ErrorReporter].
static bool shouldReport(Object? error) {
if (error == null) return false;
if (error is String) return false;
if (error is SocketException) return false;
if (error is DioException) {
return _shouldReportDio(error);
}
return true;
}
/// Used in SDK [SentryOptions.beforeSend]. Return `false` to drop the event.
///
/// For [DioException], rules differ from [shouldReport]: events from `sentry_dio`
/// (e.g. 5xx) must pass through here, while [shouldReport] returns false for 5xx
/// so [SentryErrorReporter] does not duplicate them after `catch` in cubits / manual [report] calls.
static bool allowSentryEvent(SentryEvent event, Hint hint) {
final t = event.throwable;
if (t == null) return true;
if (t is DioException) {
return _allowSentrySdkDio(t);
}
return shouldReport(t);
}
/// Cancel, timeouts, socket / connection errors — do not send (noisy, often user environment).
static bool _isTransientDioFailure(DioException e) {
switch (e.type) {
case DioExceptionType.cancel:
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
case DioExceptionType.connectionError:
return true;
case DioExceptionType.unknown:
case DioExceptionType.badResponse:
case DioExceptionType.badCertificate:
break;
}
return e.error is SocketException;
}
/// Noise to drop for any Dio-originated SDK event; do not use for manual [report].
static bool _allowSentrySdkDio(DioException e) {
if (_isTransientDioFailure(e)) return false;
final code = e.response?.statusCode;
if (code != null && code >= 400 && code < 500) return false;
return true;
}
static bool _shouldReportDio(DioException e) {
if (_isTransientDioFailure(e)) return false;
final code = e.response?.statusCode;
if (code != null) {
if (code >= 500 && code <= 599) {
return false;
}
if (code >= 400 && code < 500) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,9 @@
/// Application-wide error reporting (e.g. Sentry).
abstract class ErrorReporter {
Future<void> report(
Object error,
StackTrace? stackTrace, {
String? origin,
Map<String, Object?>? extras,
});
}

View File

@@ -0,0 +1,14 @@
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
/// Reporting sink that does nothing (dev tooling, tests, diagnostics).
class NoOpErrorReporter implements ErrorReporter {
const NoOpErrorReporter();
@override
Future<void> report(
Object error,
StackTrace? stackTrace, {
String? origin,
Map<String, Object?>? extras,
}) async {}
}

View File

@@ -0,0 +1,17 @@
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/core/error_reporting/no_op_error_reporter.dart';
import 'package:eatvio_app/core/locator_service.dart';
/// Отправка в [ErrorReporter] с тегом origin = [context.runtimeType] (имя кубита/виджета и т.д.).
///
/// Если [ErrorReporter] не зарегистрирован (например, после [GetIt.reset] в тестах), используется [NoOpErrorReporter].
Future<void> reportFrom(Object error, StackTrace? stackTrace, Object context) {
final reporter = sl.isRegistered<ErrorReporter>()
? sl<ErrorReporter>()
: const NoOpErrorReporter();
return reporter.report(
error,
stackTrace,
origin: context.runtimeType.toString(),
);
}

View File

@@ -0,0 +1,33 @@
import 'package:eatvio_app/core/error_reporting/error_report_policy.dart';
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class SentryErrorReporter implements ErrorReporter {
SentryErrorReporter();
@override
Future<void> report(
Object error,
StackTrace? stackTrace, {
String? origin,
Map<String, Object?>? extras,
}) async {
if (!ErrorReportPolicy.shouldReport(error)) return;
await Sentry.captureException(
error,
stackTrace: stackTrace,
withScope: (scope) {
if (origin != null && origin.isNotEmpty) {
scope.setTag('origin', origin);
}
if (extras != null && extras.isNotEmpty) {
scope.setContexts(
'report_extras',
extras.map((k, v) => MapEntry(k, v?.toString())),
);
}
},
);
}
}

View File

@@ -11,11 +11,14 @@ import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart' show MethodChannel, PlatformException; import 'package:flutter/services.dart' show MethodChannel, PlatformException;
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
class FirebaseApi { class FirebaseApi {
final ErrorReporter _errorReporter;
final _firebaseMessaging = FirebaseMessaging.instance; final _firebaseMessaging = FirebaseMessaging.instance;
FirebaseApi({required ErrorReporter errorReporter}) : _errorReporter = errorReporter;
Future<String?> getToken() async { Future<String?> getToken() async {
String? token; String? token;
@@ -61,7 +64,7 @@ class FirebaseApi {
); );
} catch (e, st) { } catch (e, st) {
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'FirebaseApi');
// Не пробрасываем ошибку дальше, так как это не критичная операция // Не пробрасываем ошибку дальше, так как это не критичная операция
} }
} }
@@ -86,7 +89,7 @@ class FirebaseApi {
); );
} catch (e, st) { } catch (e, st) {
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'FirebaseApi');
// Не пробрасываем ошибку дальше, так как это не критичная операция // Не пробрасываем ошибку дальше, так как это не критичная операция
} }
} }
@@ -112,12 +115,12 @@ class FirebaseApi {
} on PlatformException catch (e, st) { } on PlatformException catch (e, st) {
showErrorToast('$e $st'); showErrorToast('$e $st');
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'FirebaseApi');
return null; return null;
} catch (e, st) { } catch (e, st) {
showErrorToast('$e $st'); showErrorToast('$e $st');
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'FirebaseApi');
return null; return null;
} }
} }

View File

@@ -3,7 +3,6 @@ import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
abstract class ImageCompressUtility { abstract class ImageCompressUtility {
@@ -30,44 +29,21 @@ abstract class ImageCompressUtility {
} }
static Future<File> compressFile(File file) async { static Future<File> compressFile(File file) async {
// Skip compression in test environment to avoid hangs
try { try {
final appConfig = sl<AppConfig>(); final appConfig = sl<AppConfig>();
if (appConfig.buildType == AppBuildType.tests) { if (appConfig.buildType == AppBuildType.tests) {
// In tests, return the original file without compression
return file; return file;
} }
} catch (_) { } catch (_) {}
// If AppConfig is not available, proceed with compression
final compressedPath = '${p.withoutExtension(file.path)}_compressed.jpg';
final result = await FlutterImageCompress.compressAndGetFile(file.absolute.path, compressedPath, quality: 90, minHeight: 1080, minWidth: 1080);
if (result == null) {
return file;
} }
final image = img.decodeImage(file.readAsBytesSync())!; return File(result.path);
int width = image.width;
int height = image.height;
if (width > height) {
if (width > 1080) {
height = (height * 1080) ~/ width;
width = 1080;
}
} else {
if (height > 1080) {
width = (width * 1080) ~/ height;
height = 1080;
}
}
String filePathWithoutExtension = p.withoutExtension(file.path);
String compressedFilePath = '${filePathWithoutExtension}_compressed.jpg';
final result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
compressedFilePath,
quality: 90,
minHeight: height,
minWidth: width,
);
return File(result!.path);
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/widgets/show_error_dialog.dart'; import 'package:eatvio_app/core/widgets/show_error_dialog.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -12,18 +13,21 @@ class PhoneVerifyErrorInterceptor extends Interceptor {
final data = err.response?.data; final data = err.response?.data;
if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') { if (data is Map<String, dynamic> && data['type'] == 'unverificated_phone') {
final String phone = await sl<ProfileRepository>().getMyPhone(); final String phone = await sl<ProfileRepository>().getMyPhone();
final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone}); final String passKey = await sl<ProfileRepository>().getPassKey(routeName: 'user_detailz.change-phone-verify', routeParams: {"phone": phone});
await showErrorDialog(AppRouter.navigatorKey.currentContext!, await showErrorDialog(
title: 'Привяжите номер телефона', description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона'); AppRouter.navigatorKey.currentContext!,
title: 'Привяжите номер телефона',
description: 'Для того, что бы выполнить это действие необходимо привязать номер телефона',
);
final currentContext = AppRouter.navigatorKey.currentContext;
if (currentContext != null && currentContext.mounted && GoRouter.of(currentContext).state.path == RouteConstants.stepperBecomeConsultantScreen) {
GoRouter.of(currentContext).pop();
}
AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey); AppRouter.navigatorKey.currentContext?.pushNamed('verifyScreen', extra: passKey);
return handler.reject( return handler.reject(
DioException( DioException(requestOptions: err.requestOptions, response: err.response, error: 'Phone number not verified', type: DioExceptionType.badResponse),
requestOptions: err.requestOptions,
response: err.response,
error: 'Phone number not verified',
type: DioExceptionType.badResponse,
),
); );
} }
} }

View File

@@ -8,6 +8,7 @@ import 'package:eatvio_app/core/utils/toasts.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
class InternetConnectionCubit extends Cubit<InternetConnectionState> { class InternetConnectionCubit extends Cubit<InternetConnectionState> {
@@ -33,7 +34,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
try { try {
result = await _connectivity.checkConnectivity(); result = await _connectivity.checkConnectivity();
} on PlatformException catch (e) { } on PlatformException catch (e, st) {
await reportFrom(e, st, this);
log('Couldn\'t check connectivity status', error: e); log('Couldn\'t check connectivity status', error: e);
return; return;
} }
@@ -42,9 +44,13 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
} }
Future<void> _updateAccessStatus(List<ConnectivityResult> result) async { Future<void> _updateAccessStatus(List<ConnectivityResult> result) async {
final hasInternet = await _checkInternetAccess(result); try {
if (!hasInternet) showInternetConnectionToast(); final hasInternet = await _checkInternetAccess(result);
emit(state.copyWith(isInternetAvailable: hasInternet)); if (!hasInternet) showInternetConnectionToast();
emit(state.copyWith(isInternetAvailable: hasInternet));
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async { Future<bool> _checkInternetAccess(List<ConnectivityResult> result) async {
@@ -57,7 +63,8 @@ class InternetConnectionCubit extends Cubit<InternetConnectionState> {
try { try {
final response = await http.get(uri).timeout(const Duration(seconds: 3)); final response = await http.get(uri).timeout(const Duration(seconds: 3));
return response.statusCode >= 100 && response.statusCode < 600; return response.statusCode >= 100 && response.statusCode < 600;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
continue; continue;
} }
} }

View File

@@ -32,5 +32,5 @@ Future<void> init({required AppBuildType buildType}) async {
CubitRegistrations.registerAll(); CubitRegistrations.registerAll();
// Регистрация WebSocket менеджера // Регистрация WebSocket менеджера
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl())); sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl(), errorReporter: sl()));
} }

View File

@@ -1,23 +1,18 @@
import 'dart:developer' as developer; import 'dart:developer' as developer;
import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
/// Отвечает за навигацию по диплинкам, пришедшим в пуш-уведомлениях.
class PushNavigationHandler { class PushNavigationHandler {
static bool _initialized = false; static bool _initialized = false;
static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push'); static const MethodChannel _hmsChannel = MethodChannel('com.eatvioapp/hms_push');
/// Инициализация обработчиков Firebase Messaging.
/// Должна вызываться один раз после старта приложения.
static Future<void> initialize() async { static Future<void> initialize() async {
if (_initialized) return; if (_initialized) return;
_initialized = true; _initialized = true;
// Сообщение, по которому запустили приложение из "убитого" состояния.
try { try {
final initialMessage = await FirebaseMessaging.instance.getInitialMessage(); final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) { if (initialMessage != null) {
@@ -26,18 +21,11 @@ class PushNavigationHandler {
}); });
} }
} catch (e) { } catch (e) {
developer.log( developer.log('PushNavigationHandler: error while getting initialMessage: $e', name: 'PushNavigationHandler');
'PushNavigationHandler: error while getting initialMessage: $e',
name: 'PushNavigationHandler',
);
} }
// Тап по уведомлению, когда приложение было в фоне. FirebaseMessaging.onMessageOpenedApp.listen((message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'));
FirebaseMessaging.onMessageOpenedApp.listen(
(message) => _handleMessageInternal(message, source: 'onMessageOpenedApp'),
);
// Подписка на события от native-кода (HMS через MethodChannel).
_hmsChannel.setMethodCallHandler((call) async { _hmsChannel.setMethodCallHandler((call) async {
if (call.method == 'openActionUrl') { if (call.method == 'openActionUrl') {
final url = call.arguments as String? ?? ''; final url = call.arguments as String? ?? '';
@@ -46,118 +34,31 @@ class PushNavigationHandler {
}); });
} }
/// Входная точка для навигации из native-кода (например, HMS через MethodChannel).
static void handleActionUrlFromNative(String url) { static void handleActionUrlFromNative(String url) {
if (url.isEmpty) { if (url.isEmpty) {
developer.log( developer.log('PushNavigationHandler.handleActionUrlFromNative: empty url', name: 'PushNavigationHandler');
'PushNavigationHandler.handleActionUrlFromNative: empty url',
name: 'PushNavigationHandler',
);
return; return;
} }
developer.log( developer.log('PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url', name: 'PushNavigationHandler');
'PushNavigationHandler.handleActionUrlFromNative: navigating by url=$url',
name: 'PushNavigationHandler',
);
AppRouter.navigateByDeepLinkUrl(url); AppRouter.navigateByDeepLinkUrl(url);
} }
/// Публичный метод для единичной обработки сообщения (если понадобится где-то ещё).
static void handleMessage(RemoteMessage message) { static void handleMessage(RemoteMessage message) {
_handleMessageInternal(message, source: 'manualHandle'); _handleMessageInternal(message, source: 'manualHandle');
} }
static void _handleMessageInternal(RemoteMessage message, {required String source}) { static void _handleMessageInternal(RemoteMessage message, {required String source}) {
developer.log(
'PushNavigationHandler: received message. data=${message.data}, notification=${message.notification?.title}, source=$source',
name: 'PushNavigationHandler',
);
final data = message.data; final data = message.data;
final actionUrl = data['action_url'] as String?;
// Пытаемся взять URL из action_url или action (бэкенд шлёт и то и другое) if (actionUrl == null || actionUrl.isEmpty) {
String? rawUrl = data['action_url'] as String?; developer.log('PushNavigationHandler: no action_url in message data, source=$source, data=$data', name: 'PushNavigationHandler');
if (rawUrl == null || rawUrl.isEmpty) {
rawUrl = data['action'] as String?;
}
if (rawUrl != null && rawUrl.isNotEmpty) {
developer.log(
'PushNavigationHandler: manual navigation by url=$rawUrl, source=$source',
name: 'PushNavigationHandler',
);
// Если это URL, но без схемы, добавляем https
String finalUrl = rawUrl;
if (!finalUrl.startsWith('http') && finalUrl.contains('/')) {
finalUrl = 'https://${AppRouter.deepLinkHosts.first}/$finalUrl'.replaceAll('//', '/').replaceFirst('https:/', 'https://');
}
AppRouter.navigateByDeepLinkUrl(finalUrl);
return; return;
} }
// Fallback: навигация по типу уведомления, если URL отсутствует. developer.log('PushNavigationHandler: navigating by action_url=$actionUrl, source=$source', name: 'PushNavigationHandler');
final target = _resolveTargetByType(data);
if (target != null) {
developer.log(
'PushNavigationHandler: fallback navigation by type, target=$target, source=$source, data=$data',
name: 'PushNavigationHandler',
);
AppRouter.appRouter.go(target);
return;
}
developer.log( AppRouter.navigateByDeepLinkUrl(actionUrl);
'PushNavigationHandler: no action_url/action and no known type, source=$source, data=$data',
name: 'PushNavigationHandler',
);
}
/// Определяет маршрут по полям `type` / `action` из data, если URL отсутствует.
static String? _resolveTargetByType(Map<String, dynamic> data) {
final type = data['type'] as String? ?? '';
final action = data['action'] as String? ?? '';
final databaseType = data['database_type'] as String? ?? '';
// Чат
if (type == 'chat_message' || databaseType == 'chat-message') {
return RouteConstants.chatSelectScreen;
}
// Дневник
if (type == 'diary_reminder' || action == 'open_diary' || databaseType == 'diary-reminder') {
return RouteConstants.diaryScreen;
}
// Комментарий консультанта к дневнику
if (type == 'consultant_diary_comment' || databaseType == 'consultant-left-diary-comment') {
return RouteConstants.diaryScreen;
}
// Консультации (plan-related)
if (type.startsWith('plan_') || type.startsWith('chosen_plan_') || databaseType.contains('plan')) {
return RouteConstants.consultationsScreen;
}
// Диета
if (type == 'diet_started' || type == 'diet_cancelled' || databaseType.startsWith('diet-')) {
return RouteConstants.feedScreen;
}
// Бейдж
if (type == 'badge_awarded' || databaseType == 'badge-awarded') {
return RouteConstants.myProfileScreen;
}
// Мотивация
if (type == 'motivation') {
return RouteConstants.feedScreen;
}
return null;
} }
} }

View File

@@ -33,10 +33,7 @@ class MainNavigationRoutes {
}); });
} }
return _MainShellWithNavScope( return _MainShellWithNavScope(currentLocation: state.matchedLocation, child: child);
currentLocation: state.matchedLocation,
child: child,
);
}, },
routes: [ routes: [
GoRoute( GoRoute(
@@ -49,7 +46,14 @@ class MainNavigationRoutes {
GoRoute( GoRoute(
path: RouteConstants.feedScreen, path: RouteConstants.feedScreen,
name: 'feedScreen', name: 'feedScreen',
pageBuilder: (context, state) => const NoTransitionPage(child: FeedScreen()), pageBuilder: (context, state) {
final initialTabIndexFromQuery = int.tryParse(state.uri.queryParameters['initialTabIndex'] ?? '');
final extra = state.extra;
if (extra is int) {
return NoTransitionPage(child: FeedScreen(initialTabIndex: extra));
}
return NoTransitionPage(child: FeedScreen(initialTabIndex: initialTabIndexFromQuery));
},
redirect: (context, state) async { redirect: (context, state) async {
try { try {
await sl<WsManager>().initialize(); await sl<WsManager>().initialize();
@@ -85,16 +89,7 @@ class MainNavigationRoutes {
GoRoute( GoRoute(
path: RouteConstants.menuScreen, path: RouteConstants.menuScreen,
name: 'menuScreen', name: 'menuScreen',
pageBuilder: (context, state) => NoTransitionPage( pageBuilder: (context, state) => const NoTransitionPage(child: MenuScreen()),
child: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return MenuScreen(profileModel: state.profile);
}
return const SizedBox.shrink();
},
),
),
), ),
], ],
), ),
@@ -105,10 +100,7 @@ class MainNavigationRoutes {
/// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications) /// Wraps the shell with [BottomNavScope] so descendants (e.g. notifications)
/// can switch tabs via [BottomNavController.switchToRoute] instead of pushing. /// can switch tabs via [BottomNavController.switchToRoute] instead of pushing.
class _MainShellWithNavScope extends StatefulWidget { class _MainShellWithNavScope extends StatefulWidget {
const _MainShellWithNavScope({ const _MainShellWithNavScope({required this.child, required this.currentLocation});
required this.child,
required this.currentLocation,
});
final Widget child; final Widget child;
final String currentLocation; final String currentLocation;
@@ -124,10 +116,7 @@ class _MainShellWithNavScopeState extends State<_MainShellWithNavScope> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BottomNavScope( return BottomNavScope(
controller: _navController, controller: _navController,
child: BottomNavigationBarWidget( child: BottomNavigationBarWidget(currentLocation: widget.currentLocation, child: widget.child),
currentLocation: widget.currentLocation,
child: widget.child,
),
); );
} }
} }

View File

@@ -156,10 +156,7 @@ class OtherRoutes {
builder: (context, state) { builder: (context, state) {
if (state.extra is Map<String, dynamic>) { if (state.extra is Map<String, dynamic>) {
final data = state.extra as Map<String, dynamic>; final data = state.extra as Map<String, dynamic>;
return RecipeCreateScreen( return RecipeCreateScreen(recipe: data['recipe'] as Recipe?, autoValidate: data['autoValidate'] as bool? ?? false);
recipe: data['recipe'] as Recipe?,
autoValidate: data['autoValidate'] as bool? ?? false,
);
} }
return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null); return RecipeCreateScreen(recipe: state.extra != null ? state.extra as Recipe : null);
}, },
@@ -212,6 +209,10 @@ class OtherRoutes {
path: RouteConstants.chatScreen, path: RouteConstants.chatScreen,
name: 'chatScreen', name: 'chatScreen',
builder: (context, state) { builder: (context, state) {
final uuidFromQuery = state.uri.queryParameters['uuid'];
if (uuidFromQuery != null) {
return ChatScreen(uuid: uuidFromQuery, sendInitialMessage: false);
}
final data = state.extra as Map<String, dynamic>; final data = state.extra as Map<String, dynamic>;
final uuid = data['uuid'] as String; final uuid = data['uuid'] as String;
final sendInitialMessage = data['sendInitialMessage'] as bool?; final sendInitialMessage = data['sendInitialMessage'] as bool?;
@@ -233,7 +234,7 @@ class OtherRoutes {
final data = state.extra as Map<String, dynamic>; final data = state.extra as Map<String, dynamic>;
final slug = data['slug'] as String?; final slug = data['slug'] as String?;
final isCanceled = data['isCanceled'] as bool?; final isCanceled = data['isCanceled'] as bool?;
return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled,); return StepperBecomeConsultantScreen(slug: slug, isCanceled: isCanceled);
}, },
), ),
GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()), GoRoute(path: RouteConstants.calendarScreen, name: 'calendarScreen', builder: (context, state) => const CalendarScreen()),
@@ -267,6 +268,11 @@ class OtherRoutes {
path: InfoByOrderScreen.path, path: InfoByOrderScreen.path,
name: InfoByOrderScreen.name, name: InfoByOrderScreen.name,
builder: (context, state) { builder: (context, state) {
final queryParams = state.uri.queryParameters;
final planIdFromQuery = int.tryParse(queryParams['planId'] ?? '');
if (planIdFromQuery != null) {
return InfoByOrderScreen(planId: planIdFromQuery, successfulPayment: null, customerSlug: null, isConsultantView: true);
}
final data = state.extra as Map<String, dynamic>; final data = state.extra as Map<String, dynamic>;
final planId = data['planId'] as int; final planId = data['planId'] as int;
final successfulPayment = data['successfulPayment'] as bool?; final successfulPayment = data['successfulPayment'] as bool?;

View File

@@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:eatvio_app/core/views/cubit/views_state.dart'; import 'package:eatvio_app/core/views/cubit/views_state.dart';
import 'package:eatvio_app/core/views/repository/views_repository.dart'; import 'package:eatvio_app/core/views/repository/views_repository.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart'; import 'package:eatvio_app/features/feed/models/view_model.dart';
@@ -86,9 +88,13 @@ class ViewsCubit extends Cubit<ViewsCubitState> {
} }
Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async { Future<void> _viewContent({required ViewableContentType type, required List<int> ids}) async {
await viewsRepository.viewContent( try {
viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids), await viewsRepository.viewContent(
); viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids),
);
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
void _scheduleDebouncedSend() { void _scheduleDebouncedSend() {

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/core/views/service/views_service.dart'; import 'package:eatvio_app/core/views/service/views_service.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart'; import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ViewsRepository { class ViewsRepository {
final ViewsService viewsService; final ViewsService viewsService;
@@ -8,11 +7,7 @@ class ViewsRepository {
ViewsRepository({required this.viewsService}); ViewsRepository({required this.viewsService});
Future<void> viewContent({required ViewModel viewModel}) async { Future<void> viewContent({required ViewModel viewModel}) async {
try { await viewsService.viewContent(viewModel: viewModel);
await viewsService.viewContent(viewModel: viewModel);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -9,14 +9,16 @@ import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/websockets/ws_service.dart'; import 'package:eatvio_app/core/websockets/ws_service.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart'; import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
class WsManager { class WsManager {
final WsService wsService; final WsService wsService;
final ErrorReporter _errorReporter;
WsManager({required this.wsService}); WsManager({required this.wsService, required ErrorReporter errorReporter})
: _errorReporter = errorReporter;
PusherChannelsClient? client; PusherChannelsClient? client;
int? userId; int? userId;
@@ -114,7 +116,7 @@ class WsManager {
_subscribe(); _subscribe();
await _connectCubitsToStreams(); await _connectCubitsToStreams();
} catch (e, st) { } catch (e, st) {
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'WsManager');
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }
@@ -130,7 +132,7 @@ class WsManager {
await sl<NotificationsCubit>().connectToWebSocket(); await sl<NotificationsCubit>().connectToWebSocket();
} catch (e, st) { } catch (e, st) {
log('WsManager._connectCubitsToStreams: $e', stackTrace: st); log('WsManager._connectCubitsToStreams: $e', stackTrace: st);
await Sentry.captureException(e, stackTrace: st); await _errorReporter.report(e, st, origin: 'WsManager');
} }
} }

View File

@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit/auth_state
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart';
class AuthCubit extends Cubit<AuthState> { class AuthCubit extends Cubit<AuthState> {
@@ -26,7 +27,8 @@ class AuthCubit extends Cubit<AuthState> {
emit(AuthState.loaded(token: token)); emit(AuthState.loaded(token: token));
await TokenStorage.saveTokenInStorage(token); await TokenStorage.saveTokenInStorage(token);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Неверный адрес электронной почты или пароль'); showErrorToast('Неверный адрес электронной почты или пароль');
emit(AuthState.error(errorMessage: '$e')); emit(AuthState.error(errorMessage: '$e'));
@@ -37,22 +39,30 @@ class AuthCubit extends Cubit<AuthState> {
Future<void> initializeUser() async { Future<void> initializeUser() async {
try { try {
await registerAnonymousPushToken(); await registerAnonymousPushToken();
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
} catch (e) { } catch (e, st) {
log('Error in initializeUser: $e'); log('Error in initializeUser: $e', stackTrace: st);
FlutterNativeSplash.remove(); FlutterNativeSplash.remove();
} }
} }
Future<void> registerAnonymousPushToken() async { Future<void> registerAnonymousPushToken() async {
final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false; try {
if (!hasAnonymousTokenBeenSent) { final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false;
await firebaseApi.registerAnonymousPushToken(); if (!hasAnonymousTokenBeenSent) {
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true); await firebaseApi.registerAnonymousPushToken();
prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true);
}
} catch (e, st) {
await reportFrom(e, st, this);
} }
} }
Future<void> registerPushToken() async { Future<void> registerPushToken() async {
await firebaseApi.registerPushToken(); try {
await firebaseApi.registerPushToken();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/auth/models/oauth_verification_request_respo
import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/oauth_cubit/oauth_state.dart';
import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; import 'package:eatvio_app/features/auth/repositories/auth_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin { class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
final AuthRepository authRepository; final AuthRepository authRepository;
@@ -29,7 +30,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
log('OAuthCubit: Got token model: ${tokenModel.token}'); log('OAuthCubit: Got token model: ${tokenModel.token}');
TokenStorage.saveTokenInStorage(tokenModel.token); TokenStorage.saveTokenInStorage(tokenModel.token);
emit(OAuthState.loaded(token: tokenModel.token)); emit(OAuthState.loaded(token: tokenModel.token));
} on DioException catch (e) { } on DioException catch (e, st) {
await reportFrom(e, st, this);
log('OAuthCubit: Error occurred: $e'); log('OAuthCubit: Error occurred: $e');
// Проверяем, является ли ошибка ошибкой валидации email/phone // Проверяем, является ли ошибка ошибкой валидации email/phone
final errors = e.response?.data['errors'] as Map<String, dynamic>?; final errors = e.response?.data['errors'] as Map<String, dynamic>?;
@@ -62,7 +64,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
} else { } else {
emit(OAuthState.error(errorMessage: e.toString())); emit(OAuthState.error(errorMessage: e.toString()));
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log('OAuthCubit: Error occurred: $e'); log('OAuthCubit: Error occurred: $e');
emit(OAuthState.error(errorMessage: e.toString())); emit(OAuthState.error(errorMessage: e.toString()));
} }
@@ -103,7 +106,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
TokenStorage.saveTokenInStorage(token); TokenStorage.saveTokenInStorage(token);
cacheRegistrationDate(DateTime.now()); cacheRegistrationDate(DateTime.now());
emit(OAuthState.loaded(token: token)); emit(OAuthState.loaded(token: token));
} on DioException catch (e) { } on DioException catch (e, st) {
await reportFrom(e, st, this);
emit(OAuthState.error(errorMessage: e.toString())); emit(OAuthState.error(errorMessage: e.toString()));
final errors = e.response?.data['errors'] as Map<String, dynamic>?; final errors = e.response?.data['errors'] as Map<String, dynamic>?;
final emailErrors = errors?['email'] as List<dynamic>?; final emailErrors = errors?['email'] as List<dynamic>?;
@@ -153,7 +157,8 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
// Сбрасываем состояние после успешного запроса кода // Сбрасываем состояние после успешного запроса кода
emit(const OAuthState()); emit(const OAuthState());
return response; return response;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log('OAuthCubit: Error requesting verification code: $e'); log('OAuthCubit: Error requesting verification code: $e');
emit(OAuthState.error(errorMessage: e.toString())); emit(OAuthState.error(errorMessage: e.toString()));
return null; return null;
@@ -200,10 +205,10 @@ class OAuthCubit extends Cubit<OAuthState> with RegistrationDateCacheMixin {
log('OAuthCubit: OAuth verification confirmed successfully, token received'); log('OAuthCubit: OAuth verification confirmed successfully, token received');
TokenStorage.saveTokenInStorage(response.token); TokenStorage.saveTokenInStorage(response.token);
emit(OAuthState.loaded(token: response.token)); emit(OAuthState.loaded(token: response.token));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log('OAuthCubit: Error confirming OAuth verification: $e'); log('OAuthCubit: Error confirming OAuth verification: $e');
emit(OAuthState.error(errorMessage: e.toString())); emit(OAuthState.error(errorMessage: e.toString()));
rethrow; // Пробрасываем ошибку дальше, чтобы UI мог её обработать
} }
} }
} }

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart'; import 'package:eatvio_app/features/auth/presentation/bloc/reset_password_cubit/reset_password_state.dart';
import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart'; import 'package:eatvio_app/features/auth/repositories/reset_password_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ResetPasswordCubit extends Cubit<ResetPasswordState> { class ResetPasswordCubit extends Cubit<ResetPasswordState> {
final ResetPasswordRepository resetPasswordRepository; final ResetPasswordRepository resetPasswordRepository;
@@ -15,7 +16,8 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
} else { } else {
emit(const ResetPasswordState.codeSendError()); emit(const ResetPasswordState.codeSendError());
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(const ResetPasswordState.codeSendError()); emit(const ResetPasswordState.codeSendError());
} }
} }
@@ -28,7 +30,8 @@ class ResetPasswordCubit extends Cubit<ResetPasswordState> {
} else { } else {
emit(const ResetPasswordState.resetPasswordError()); emit(const ResetPasswordState.resetPasswordError());
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(const ResetPasswordState.resetPasswordError()); emit(const ResetPasswordState.resetPasswordError());
} }
} }

View File

@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/auth/models/oauth_verification_confirm_respo
import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart'; import 'package:eatvio_app/features/auth/models/oauth_verification_request_response.dart';
import 'package:eatvio_app/features/auth/models/token_model.dart'; import 'package:eatvio_app/features/auth/models/token_model.dart';
import 'package:eatvio_app/features/auth/services/auth_service.dart'; import 'package:eatvio_app/features/auth/services/auth_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class AuthRepository { class AuthRepository {
final AuthService authService; final AuthService authService;
@@ -15,14 +14,10 @@ class AuthRepository {
AuthRepository({required this.authService}); AuthRepository({required this.authService});
Future<TokenModel> getTokenByCredentials(String email, String password) async { Future<TokenModel> getTokenByCredentials(String email, String password) async {
try { return await authService.getTokenByCredentials(
return await authService.getTokenByCredentials( credentials: CredentialsModel(email: email, password: password),
credentials: CredentialsModel(email: email, password: password), );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<TokenModel> signInViaSocialMedia({ Future<TokenModel> signInViaSocialMedia({
@@ -31,14 +26,10 @@ class AuthRepository {
required String refreshToken, required String refreshToken,
required String clientId, required String clientId,
}) async { }) async {
try { return await authService.signInViaSocialMedia(
return await authService.signInViaSocialMedia( body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId),
body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId), );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<String> signUpViaSocialMedia({ Future<String> signUpViaSocialMedia({
@@ -56,47 +47,39 @@ class AuthRepository {
required int height, required int height,
required String clientId, required String clientId,
}) async { }) async {
try { return (await authService.signUpViaSocialMedia(
return (await authService.signUpViaSocialMedia( body: OAuthModel(
body: OAuthModel( provider: provider,
provider: provider, accessToken: accessToken,
accessToken: accessToken, id: id,
id: id, refreshToken: refreshToken,
refreshToken: refreshToken, firstName: firstName,
firstName: firstName, lastName: lastName,
lastName: lastName, email: email,
email: email, phone: phone,
phone: phone, dateOfBirth: dateOfBirth,
dateOfBirth: dateOfBirth, gender: gender == 'М' ? 'male' : 'female',
gender: gender == 'М' ? 'male' : 'female', weight: weight,
weight: weight, height: height,
height: height, clientId: clientId,
clientId: clientId, ),
), ))['token']!
))['token']! as String;
as String;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async { Future<OAuthVerificationRequestResponse?> requestOAuthVerification({String? email, String? phone}) async {
try { Map<String, dynamic>? body;
Map<String, dynamic>? body; if (email != null) {
if (email != null) { body = {'email': email};
body = {'email': email}; } else if (phone != null) {
} else if (phone != null) { body = {'phone': phone};
body = {'phone': phone};
}
if (body != null) {
return await authService.requestOAuthVerification(body: body);
}
return null;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
if (body != null) {
return await authService.requestOAuthVerification(body: body);
}
return null;
} }
Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({ Future<OAuthVerificationConfirmResponse> confirmOAuthVerification({
@@ -116,33 +99,28 @@ class AuthRepository {
required int weight, required int weight,
required int height, required int height,
}) async { }) async {
try { final request = OAuthVerificationConfirmRequest(
final request = OAuthVerificationConfirmRequest( provider: provider,
provider: provider, accessToken: accessToken,
accessToken: accessToken, refreshToken: refreshToken,
refreshToken: refreshToken, clientId: clientId,
clientId: clientId, id: id,
id: id, email: email,
email: email, phone: phone,
phone: phone, code: code,
code: code, verificationType: verificationType,
verificationType: verificationType, firstName: firstName,
firstName: firstName, lastName: lastName,
lastName: lastName, dateOfBirth: dateOfBirth,
dateOfBirth: dateOfBirth, gender: gender,
gender: gender, weight: weight,
weight: weight, height: height,
height: height, );
); // Логируем запрос перед отправкой
// Логируем запрос перед отправкой log(
log( 'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone',
'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone', );
); return await authService.confirmOAuthVerification(body: request);
return await authService.confirmOAuthVerification(body: request);
} catch (e, s) {
log('AuthRepository: Error in confirmOAuthVerification: $e');
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -1,5 +1,4 @@
import 'package:eatvio_app/features/auth/services/reset_password_service.dart'; import 'package:eatvio_app/features/auth/services/reset_password_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ResetPasswordRepository { class ResetPasswordRepository {
final ResetPasswordService resetPasswordService; final ResetPasswordService resetPasswordService;
@@ -9,22 +8,18 @@ class ResetPasswordRepository {
}); });
Future<bool> sendEmailCodeForPasswordChange(String email) async { Future<bool> sendEmailCodeForPasswordChange(String email) async {
try { final response = await resetPasswordService.sendEmailCodeForPasswordChange(
final response = await resetPasswordService.sendEmailCodeForPasswordChange( data: {
data: { 'email': email,
'email': email, },
}, );
); int statusCode = response.response.statusCode ?? 0;
int statusCode = response.response.statusCode ?? 0; if (statusCode >= 200 && statusCode < 300) {
if (statusCode >= 200 && statusCode < 300) { return true;
return true; } else {
} else { return false;
return false;
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<bool> confirmPasswordChange( Future<bool> confirmPasswordChange(
@@ -33,24 +28,20 @@ class ResetPasswordRepository {
String passwordConfirmation, String passwordConfirmation,
String email, String email,
) async { ) async {
try { final response = await resetPasswordService.confirmPasswordChange(
final response = await resetPasswordService.confirmPasswordChange( data: {
data: { 'code': code,
'code': code, 'password': password,
'password': password, 'password_confirmation': passwordConfirmation,
'password_confirmation': passwordConfirmation, 'email': email,
'email': email, },
}, );
); int statusCode = response.response.statusCode ?? 0;
int statusCode = response.response.statusCode ?? 0; if (statusCode >= 200 && statusCode < 300) {
if (statusCode >= 200 && statusCode < 300) { return true;
return true; } else {
} else { return false;
return false;
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
} }

View File

@@ -8,10 +8,11 @@ import 'package:eatvio_app/features/auth/models/yandex_profile_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/env_config.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/generated/l10n.dart'; import 'package:eatvio_app/generated/l10n.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:vk_id/vk_id.dart'; import 'package:vk_id/vk_id.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
@@ -43,7 +44,7 @@ class SocialAuthFacade {
} }
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr); return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
} catch (e, s) { } catch (e, s) {
await Sentry.captureException(e, stackTrace: s); await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
return null; return null;
} }
} }
@@ -161,7 +162,7 @@ class SocialAuthFacade {
); );
} catch (e, s) { } catch (e, s) {
log(e.toString()); log(e.toString());
await Sentry.captureException(e, stackTrace: s); await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
} }
return null; return null;
} }
@@ -209,7 +210,7 @@ class SocialAuthFacade {
return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr); return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr);
} catch (e, s) { } catch (e, s) {
await Sentry.captureException(e, stackTrace: s); await sl<ErrorReporter>().report(e, s, origin: 'SocialAuthFacade');
return null; return null;
} }
} }

View File

@@ -1,7 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart'; import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart';
import 'package:eatvio_app/features/barcode/services/barcode_service.dart'; import 'package:eatvio_app/features/barcode/services/barcode_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class BarcodeRepository { class BarcodeRepository {
final BarcodeService barcodeService; final BarcodeService barcodeService;
@@ -15,49 +14,46 @@ class BarcodeRepository {
/// Получить информацию о продукте по штрихкоду /// Получить информацию о продукте по штрихкоду
/// Возвращает либо данные продукта, либо статус задачи обработки /// Возвращает либо данные продукта, либо статус задачи обработки
Future<BarcodeResult> getProductByBarcode(String barcode) async { Future<BarcodeResult> getProductByBarcode(String barcode) async {
try { final response = await dio.get<Map<String, dynamic>>(
final response = await dio.get('/api/gsone/barcode/$barcode'); '/api/gsone/barcode/$barcode',
options: Options(validateStatus: (_) => true),
if (response.statusCode == 200) { );
// Данные найдены в БД
final product = BarcodeProductModel.fromJson(response.data as Map<String, dynamic>); final code = response.statusCode ?? 0;
return BarcodeResult.success(product); final data = response.data;
} else if (response.statusCode == 202) { if (data is! Map<String, dynamic>) {
// Запущена задача обработки throw DioException(
final taskStatus = BarcodeTaskStatusModel.fromJson(response.data as Map<String, dynamic>); requestOptions: response.requestOptions,
return BarcodeResult.processing(taskStatus.taskId ?? ''); response: response,
} message: 'Invalid barcode response',
);
throw Exception('Unexpected status code: ${response.statusCode}');
} on DioException catch (e, s) {
if (e.response?.statusCode == 202) {
// Если получен статус 202, значит запущена задача обработки
final taskStatus = BarcodeTaskStatusModel.fromJson(e.response!.data as Map<String, dynamic>);
return BarcodeResult.processing(taskStatus.taskId ?? '');
}
await Sentry.captureException(e, stackTrace: s);
rethrow;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
if (code == 200) {
final product = BarcodeProductModel.fromJson(data);
return BarcodeResult.success(product);
}
if (code == 202) {
final taskStatus = BarcodeTaskStatusModel.fromJson(data);
return BarcodeResult.processing(taskStatus.taskId ?? '');
}
throw DioException(
requestOptions: response.requestOptions,
response: response,
message: 'Unexpected status code: $code',
);
} }
/// Получить статус задачи обработки /// Получить статус задачи обработки
Future<BarcodeTaskStatus> getTaskStatus(String taskId) async { Future<BarcodeTaskStatus> getTaskStatus(String taskId) async {
try { final statusModel = await barcodeService.getTaskStatus(taskId: taskId);
final statusModel = await barcodeService.getTaskStatus(taskId: taskId);
if (statusModel.status == 'completed' && statusModel.data != null) {
if (statusModel.status == 'completed' && statusModel.data != null) { return BarcodeTaskStatus.completed(statusModel.data!);
return BarcodeTaskStatus.completed(statusModel.data!); } else if (statusModel.status == 'failed') {
} else if (statusModel.status == 'failed') { return BarcodeTaskStatus.failed();
return BarcodeTaskStatus.failed(); } else {
} else { return BarcodeTaskStatus.processing();
return BarcodeTaskStatus.processing();
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
} }
@@ -109,4 +105,3 @@ enum BarcodeTaskStatusType {
processing, processing,
failed, failed,
} }

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart'; import 'package:eatvio_app/features/blog_post/presentation/cubit/blog_post_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class BlogPostCubit extends Cubit<BlogPostState> { class BlogPostCubit extends Cubit<BlogPostState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
@@ -16,6 +17,7 @@ class BlogPostCubit extends Cubit<BlogPostState> {
emit(state.copyWith(blogPostModel: postModel, isLoading: false)); emit(state.copyWith(blogPostModel: postModel, isLoading: false));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(isLoading: false)); emit(state.copyWith(isLoading: false));
} }
@@ -37,6 +39,7 @@ class BlogPostCubit extends Cubit<BlogPostState> {
await feedRepository.likeBlogPost(id: current.id); await feedRepository.likeBlogPost(id: current.id);
} }
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
// rollback on error // rollback on error
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(blogPostModel: current)); emit(state.copyWith(blogPostModel: current));

View File

@@ -78,9 +78,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
bottomSheet: Container( bottomSheet: Container(
padding: EdgeInsets.only( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
bottom: MediaQuery.of(context).padding.bottom + 8,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))), border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.1))),
@@ -147,14 +145,18 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
GestureDetector( GestureDetector(
onTap: () { onTap: () async {
if (textEditingController.text.length > 2) { if (textEditingController.text.length > 2) {
commentsCubit.sendComment( final isSent = await commentsCubit.sendComment(
id: widget.id.toString(), id: widget.id.toString(),
commentText: textEditingController.text, commentText: textEditingController.text,
parentCommentId: replyId, parentCommentId: replyId,
commentSourceType: CommentSourceType.blogPost, commentSourceType: CommentSourceType.blogPost,
); );
if (!context.mounted) return;
if (isSent) context.read<FeedCubit>().incrementCommentsState(widget.id);
context.read<CommentsCubit>().setReplyId(null); context.read<CommentsCubit>().setReplyId(null);
setState(() { setState(() {
textEditingController.clear(); textEditingController.clear();
@@ -237,9 +239,7 @@ class _BlogPostScreenState extends State<BlogPostScreen> {
if (blogPostState.blogPostModel?.description.isNotEmpty ?? false) if (blogPostState.blogPostModel?.description.isNotEmpty ?? false)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: MarkdownHtmlWidget( child: MarkdownHtmlWidget(markdownText: blogPostState.blogPostModel!.description),
markdownText: blogPostState.blogPostModel!.description,
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (blogPostState.blogPostModel?.image.isNotEmpty ?? false) if (blogPostState.blogPostModel?.image.isNotEmpty ?? false)

View File

@@ -1,9 +1,12 @@
import 'package:eatvio_app/core/cubit/base_cubit.dart'; import 'dart:developer';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart'; import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_state.dart';
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart'; import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> { class BlogPostCreateCubit extends Cubit<BlogPostCreateState> {
final BlogPostRepository blogPostRepository; final BlogPostRepository blogPostRepository;
BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState()); BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState());
@@ -15,7 +18,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
emit(const BlogPostCreateState.loaded()); emit(const BlogPostCreateState.loaded());
return true; return true;
} catch (error, stackTrace) { } catch (error, stackTrace) {
handleError(error, stackTrace); log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
await reportFrom(error, stackTrace, this);
emit(BlogPostCreateState.error(errorMessage: error.toString())); emit(BlogPostCreateState.error(errorMessage: error.toString()));
return false; return false;
} }
@@ -28,7 +32,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
emit(const BlogPostCreateState.loaded()); emit(const BlogPostCreateState.loaded());
return true; return true;
} catch (error, stackTrace) { } catch (error, stackTrace) {
handleError(error, stackTrace); log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
await reportFrom(error, stackTrace, this);
emit(BlogPostCreateState.error(errorMessage: error.toString())); emit(BlogPostCreateState.error(errorMessage: error.toString()));
return false; return false;
} }
@@ -39,7 +44,8 @@ class BlogPostCreateCubit extends BaseCubit<BlogPostCreateState> {
await blogPostRepository.deleteBlogPost(id); await blogPostRepository.deleteBlogPost(id);
return true; return true;
} catch (error, stackTrace) { } catch (error, stackTrace) {
handleError(error, stackTrace); log('BlogPostCreateCubit: $error', stackTrace: stackTrace);
await reportFrom(error, stackTrace, this);
return false; return false;
} }
} }

View File

@@ -1,5 +1,5 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
@@ -8,7 +8,6 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart'; import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class BlogPostRepository { class BlogPostRepository {
final BlogPostService blogPostService; final BlogPostService blogPostService;
@@ -23,48 +22,30 @@ class BlogPostRepository {
String? description, String? description,
XFile? image, XFile? image,
) async { ) async {
FormData formData = FormData.fromMap({ final formData = FormData.fromMap({
if (title != null && title.isNotEmpty) 'title': title, if (title != null && title.isNotEmpty) 'title': title,
if (description != null && description.isNotEmpty) 'description_md': description, if (description != null && description.isNotEmpty) 'description_md': description,
if (videoUrl != null && videoUrl.isNotEmpty) 'video_url': videoUrl, if (videoUrl != null && videoUrl.isNotEmpty) 'video_url': videoUrl,
if (image != null) 'image': await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name), if (image != null) 'image': await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name),
}); });
try { return blogPostService.createBlogPost(
return await blogPostService.createBlogPost( formData: formData,
formData: formData, );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<XFile?> downloadImage(String imageUrl) async { Future<XFile?> downloadImage(String imageUrl) async {
try { final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
// Загрузка изображения
final response = await http.get(Uri.parse('${sl<AppConfig>().baseUrl}/storage/$imageUrl'));
if (response.statusCode == 200) { if (response.statusCode == 200) {
// Получение временной директории final directory = await getTemporaryDirectory();
final directory = await getTemporaryDirectory(); final filePath = '${directory.path}/image.jpg';
// Создание файла в временной директории final file = File(filePath);
final filePath = '${directory.path}/image.jpg'; // Вы можете изменить название файла await file.writeAsBytes(response.bodyBytes);
final file = File(filePath);
// Запись данных в файл
await file.writeAsBytes(response.bodyBytes);
// Возвращаем объект XFile return XFile(file.path);
return XFile(file.path);
} else {
log('Ошибка загрузки изображения: ${response.statusCode}');
return null;
}
} catch (e, s) {
log('Произошла ошибка: $e');
await Sentry.captureException(e, stackTrace: s);
return null;
} }
return null;
} }
Future<void> updateBlogPost( Future<void> updateBlogPost(
@@ -75,9 +56,9 @@ class BlogPostRepository {
XFile? image, XFile? image,
String? oldImageUrl, String? oldImageUrl,
) async { ) async {
FormData formData = FormData.fromMap({ final formData = FormData.fromMap({
'title': title, 'title': title,
'description_md': description, 'description_md': description,
'video_url': videoUrl, 'video_url': videoUrl,
'image': image != null 'image': image != null
? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name) ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name)
@@ -86,23 +67,13 @@ class BlogPostRepository {
: null, : null,
}); });
try { return blogPostService.updateBlogPost(
return await blogPostService.updateBlogPost( id: id,
id: id, formData: formData,
formData: formData, );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deleteBlogPost(int id) async { Future<void> deleteBlogPost(int id) async {
try { await blogPostService.deleteBlogPost(id: id);
await blogPostService.deleteBlogPost(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -1,8 +1,11 @@
import 'dart:async';
import 'package:appmetrica_plugin/appmetrica_plugin.dart'; import 'package:appmetrica_plugin/appmetrica_plugin.dart';
import 'package:eatvio_app/core/app_router.dart'; import 'package:eatvio_app/core/app_router.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
@@ -28,7 +31,8 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
return age; return age;
} }
} catch (e) { } catch (e, st) {
unawaited(reportFrom(e, st, this));
return 0; return 0;
} }
return 0; return 0;
@@ -51,10 +55,12 @@ class ProfileAvatarCubit extends Cubit<ProfileAvatarState> {
]); ]);
AppMetrica.setUserProfileID(profile.slug); AppMetrica.setUserProfileID(profile.slug);
AppMetrica.reportUserProfile(userProfile); AppMetrica.reportUserProfile(userProfile);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
// Игнорируем ошибки плагинов, профиль уже загружен // Игнорируем ошибки плагинов, профиль уже загружен
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
AppRouter.navigatorKey.currentContext?.go('/auth-screen'); AppRouter.navigatorKey.currentContext?.go('/auth-screen');
emit(ProfileAvatarState.error(errorMessage: e.toString())); emit(ProfileAvatarState.error(errorMessage: e.toString()));
} }

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart'; import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart';
import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart'; import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class CalendarCubit extends Cubit<CalendarState> { class CalendarCubit extends Cubit<CalendarState> {
final CalendarRepository calendarRepository; final CalendarRepository calendarRepository;
@@ -12,7 +13,8 @@ class CalendarCubit extends Cubit<CalendarState> {
try { try {
final calendarList = await calendarRepository.getCalendar(); final calendarList = await calendarRepository.getCalendar();
emit(CalendarState.loaded(calendarList: calendarList)); emit(CalendarState.loaded(calendarList: calendarList));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(CalendarState.error(errorMessage: e.toString())); emit(CalendarState.error(errorMessage: e.toString()));
} }
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/calendar/models/calendar_model.dart'; import 'package:eatvio_app/features/calendar/models/calendar_model.dart';
import 'package:eatvio_app/features/calendar/services/calendar_service.dart'; import 'package:eatvio_app/features/calendar/services/calendar_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CalendarRepository { class CalendarRepository {
final CalendarService calendarService; final CalendarService calendarService;
@@ -8,11 +7,7 @@ class CalendarRepository {
CalendarRepository({required this.calendarService}); CalendarRepository({required this.calendarService});
Future<List<CalendarModel>> getCalendar() async { Future<List<CalendarModel>> getCalendar() async {
try { return await calendarService.getCalendar();
return await calendarService.getCalendar();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cubit/cancel_plan_s
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart'; import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart'; import 'package:eatvio_app/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class CancelPlanCubit extends Cubit<CancelPlanState> { class CancelPlanCubit extends Cubit<CancelPlanState> {
final CancelPlanRepository cancelPlanRepository; final CancelPlanRepository cancelPlanRepository;
@@ -18,6 +19,7 @@ class CancelPlanCubit extends Cubit<CancelPlanState> {
await cancelPlanRepository.cancelChosenPlan(id: id, body: body); await cancelPlanRepository.cancelChosenPlan(id: id, body: body);
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
return false; return false;

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart'; import 'package:eatvio_app/features/cancel_plan_bottom_sheet/models/cancelation_model.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart'; import 'package:eatvio_app/features/cancel_plan_bottom_sheet/service/cancel_plan_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CancelPlanRepository { class CancelPlanRepository {
final CancelPlanService cancelService; final CancelPlanService cancelService;
@@ -9,11 +8,7 @@ class CancelPlanRepository {
required int id, required int id,
CancelationModel? body CancelationModel? body
}) async { }) async {
try { await cancelService.cancelChosenPlan(id: id, body: body);
await cancelService.cancelChosenPlan(id: id, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -20,6 +20,7 @@ import 'package:eatvio_app/features/my_finance/models/card_model.dart';
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class ChatRoomCubit extends Cubit<ChatRoomState> { class ChatRoomCubit extends Cubit<ChatRoomState> {
@@ -64,7 +65,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
); );
currentPage++; currentPage++;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
} }
} }
@@ -75,6 +77,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
final cards = await cardsRepository.getCards(); final cards = await cardsRepository.getCards();
_setCard(cards: cards); _setCard(cards: cards);
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }
@@ -90,6 +93,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async { Future<void> initChat(String uuid, {bool sendInitialMessage = false}) async {
try { try {
emit(const ChatRoomState.loading());
final chatRoom = await chatRepository.getChatRoom(uuid); final chatRoom = await chatRepository.getChatRoom(uuid);
ChosenPlanModel? chosenPlan; ChosenPlanModel? chosenPlan;
if (chatRoom.chosenPlanId != null) { if (chatRoom.chosenPlanId != null) {
@@ -111,7 +115,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
if (chosenPlan?.consultantId == chatRoom.users.sender.id) { if (chosenPlan?.consultantId == chatRoom.users.sender.id) {
await checkCards(); await checkCards();
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
typingSubscription?.cancel(); typingSubscription?.cancel();
messagesSubscription?.cancel(); messagesSubscription?.cancel();
@@ -121,13 +126,17 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
} }
Future<void> updateChosenPlanState() async { Future<void> updateChosenPlanState() async {
final currentState = state; try {
if (currentState is ChatRoomStateLoaded) { final currentState = state;
final chosenPlanId = currentState.chatRoom.chosenPlanId; if (currentState is ChatRoomStateLoaded) {
if (chosenPlanId != null) { final chosenPlanId = currentState.chatRoom.chosenPlanId;
final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId); if (chosenPlanId != null) {
emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan)); final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId);
emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan));
}
} }
} catch (e, st) {
await reportFrom(e, st, this);
} }
} }
@@ -142,6 +151,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan)); emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan));
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -151,7 +161,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
try { try {
await chatRepository.cancelChosenPlan(id: planId); await chatRepository.cancelChosenPlan(id: planId);
emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null)); emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
} }
} }
@@ -165,12 +176,12 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
AudioPlayer().play((AssetSource('audio/recive_message.wav'))); AudioPlayer().play((AssetSource('audio/recive_message.wav')));
final message = ChatMessageModel.fromJson(jsonDecode(event.data)); final message = ChatMessageModel.fromJson(jsonDecode(event.data));
final currentState = state as ChatRoomStateLoaded; final currentState = state as ChatRoomStateLoaded;
// Сообщения от текущего пользователя не приходят через WebSocket, // Сообщения от текущего пользователя не приходят через WebSocket,
// они обрабатываются через ответ API в sendMessage // они обрабатываются через ответ API в sendMessage
// Здесь обрабатываем только входящие сообщения от других пользователей // Здесь обрабатываем только входящие сообщения от других пользователей
List<ChatMessageModel> messagesList = [message, ...currentState.chatMessages]; List<ChatMessageModel> messagesList = [message, ...currentState.chatMessages];
chatRepository.emitNewMessageToChats(message); chatRepository.emitNewMessageToChats(message);
emit(currentState.copyWith(chatMessages: messagesList, isLoading: isLoading)); emit(currentState.copyWith(chatMessages: messagesList, isLoading: isLoading));
EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms()); EasyDebounce.debounce('searchChatRoom', const Duration(milliseconds: 300), () => sl<ChatsCubit>().getChatRooms());
@@ -191,7 +202,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
final messageUuid = uuid.v4(); final messageUuid = uuid.v4();
final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null; final compressedFiles = files != null ? await ImageCompressUtility.compressFilesList(List<File>.from(files)) : null;
AudioPlayer().play((AssetSource('audio/send_message.wav'))); AudioPlayer().play((AssetSource('audio/send_message.wav')));
// Создаем временное сообщение с состоянием loading // Создаем временное сообщение с состоянием loading
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
@@ -216,7 +227,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
try { try {
// Отправляем сообщение и получаем ответ от сервера с правильными данными // Отправляем сообщение и получаем ответ от сервера с правильными данными
final serverMessage = await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug); final serverMessage = await chatRepository.sendMessage(data: data, files: compressedFiles, slug: userSlug);
// Заменяем локальное сообщение данными с сервера (с правильными URL файлов) // Заменяем локальное сообщение данными с сервера (с правильными URL файлов)
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
@@ -238,7 +249,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
}).toList(), }).toList(),
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) { chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
@@ -253,6 +265,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
sl<ChatsCubit>().getChatRooms(); sl<ChatsCubit>().getChatRooms();
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -274,6 +287,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
} }
return false; return false;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -286,6 +300,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
final paymentModel = await chatRepository.getPaymentLink(id: planId); final paymentModel = await chatRepository.getPaymentLink(id: planId);
return paymentModel.paymentUrl; return paymentModel.paymentUrl;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return null; return null;
} }
@@ -300,7 +315,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
body: PlanComplainModel(description: description), body: PlanComplainModel(description: description),
); );
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
return false; return false;
} }
@@ -312,7 +328,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
if (planId == null) return false; if (planId == null) return false;
await chatRepository.finishChosenPlan(id: planId); await chatRepository.finishChosenPlan(id: planId);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
return false; return false;
} }
@@ -341,7 +358,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
if (message != null) { if (message != null) {
try { try {
AudioPlayer().play((AssetSource('audio/send_message.wav'))); AudioPlayer().play((AssetSource('audio/send_message.wav')));
// Определяем, есть ли файлы для повторной отправки // Определяем, есть ли файлы для повторной отправки
List<File>? filesToResend; List<File>? filesToResend;
if (message.mediaData != null && message.mediaData!.isNotEmpty) { if (message.mediaData != null && message.mediaData!.isNotEmpty) {
@@ -352,7 +369,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
filesToResend = message.mediaData!.cast<File>(); filesToResend = message.mediaData!.cast<File>();
} }
} }
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
chatMessages: [ chatMessages: [
@@ -375,12 +392,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
try { try {
// Отправляем сообщение и получаем ответ от сервера // Отправляем сообщение и получаем ответ от сервера
final serverMessage = await chatRepository.sendMessage( final serverMessage = await chatRepository.sendMessage(data: message.data, files: filesToResend, slug: userSlug);
data: message.data,
files: filesToResend,
slug: userSlug,
);
// Заменяем локальное сообщение данными с сервера // Заменяем локальное сообщение данными с сервера
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
@@ -400,7 +413,8 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
}).toList(), }).toList(),
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
(state as ChatRoomStateLoaded).copyWith( (state as ChatRoomStateLoaded).copyWith(
chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) { chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) {
@@ -414,6 +428,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
} }
sl<ChatsCubit>().getChatRooms(); sl<ChatsCubit>().getChatRooms();
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }

View File

@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_sta
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart'; import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ChatsCubit extends Cubit<ChatsState> { class ChatsCubit extends Cubit<ChatsState> {
final ChatRepository chatRepository; final ChatRepository chatRepository;
@@ -31,10 +32,14 @@ class ChatsCubit extends Cubit<ChatsState> {
late int lastPage; late int lastPage;
Future<void> connectToWebSocket() async { Future<void> connectToWebSocket() async {
await newIncomingMessagesSubscription?.cancel(); try {
newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) { await newIncomingMessagesSubscription?.cancel();
_handleNewMessageViaSocket(event.data); newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) {
}); _handleNewMessageViaSocket(event.data);
});
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> getChatRoomsWithLoading({String? query}) async { Future<void> getChatRoomsWithLoading({String? query}) async {
@@ -53,7 +58,8 @@ class ChatsCubit extends Cubit<ChatsState> {
), ),
); );
page++; page++;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(errorMessage: e.toString(), state: BaseState.error), state.copyWith(errorMessage: e.toString(), state: BaseState.error),
); );
@@ -76,6 +82,7 @@ class ChatsCubit extends Cubit<ChatsState> {
); );
page++; page++;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit( emit(
state.copyWith(errorMessage: e.toString(), state: BaseState.error), state.copyWith(errorMessage: e.toString(), state: BaseState.error),
@@ -99,6 +106,7 @@ class ChatsCubit extends Cubit<ChatsState> {
); );
page++; page++;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit( emit(
state.copyWith( state.copyWith(
@@ -132,33 +140,37 @@ class ChatsCubit extends Cubit<ChatsState> {
} }
Future<void> _handleNewMessageViaSocket(dynamic data) async { Future<void> _handleNewMessageViaSocket(dynamic data) async {
final json = jsonDecode(data); try {
final message = ChatMessageModel.fromJson(json['message']); final json = jsonDecode(data);
final message = ChatMessageModel.fromJson(json['message']);
final currentChatRooms = [...state.chatRooms]; final currentChatRooms = [...state.chatRooms];
bool wasChanged = false; bool wasChanged = false;
bool willIncrementUnreadChats = false; bool willIncrementUnreadChats = false;
final updatedChatRooms = currentChatRooms.map((chatRoom) { final updatedChatRooms = currentChatRooms.map((chatRoom) {
if (chatRoom.id == message.roomId) { if (chatRoom.id == message.roomId) {
wasChanged = true; wasChanged = true;
if (chatRoom.unreadMessagesCount == 0) { if (chatRoom.unreadMessagesCount == 0) {
willIncrementUnreadChats = true; willIncrementUnreadChats = true;
}
return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1);
} }
return chatRoom.copyWith(lastMessage: message, unreadMessagesCount: chatRoom.unreadMessagesCount + 1); return chatRoom;
} }).toList();
return chatRoom;
}).toList();
if (!wasChanged && _query == null) { if (!wasChanged && _query == null) {
getChatRooms(); getChatRooms();
} else { } else {
emit( emit(
state.copyWith( state.copyWith(
chatRooms: updatedChatRooms, chatRooms: updatedChatRooms,
chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount, chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount,
), ),
); );
}
} catch (e, st) {
await reportFrom(e, st, this);
} }
} }

View File

@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
import 'package:easy_debounce/easy_debounce.dart'; import 'package:easy_debounce/easy_debounce.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/widgets/custom_outlined_button.dart'; 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/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/core/widgets/loader.dart';
@@ -62,6 +63,14 @@ class _ChatScreenState extends State<ChatScreen> {
chatRoomCubit.setTimer(); chatRoomCubit.setTimer();
} }
@override
void didUpdateWidget(ChatScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.uuid != widget.uuid) {
chatRoomCubit.initChat(widget.uuid, sendInitialMessage: widget.sendInitialMessage ?? false);
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -96,11 +105,34 @@ class _ChatScreenState extends State<ChatScreen> {
return _buildChat(context, state); return _buildChat(context, state);
} else if (state is ChatRoomStateError) { } else if (state is ChatRoomStateError) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
},
),
),
body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)), body: DataErrorWidget(onReload: () => chatRoomCubit.initChat(widget.uuid)),
); );
} else { } else {
return const Scaffold(body: Loader()); return Scaffold(
body: const Loader(),
appBar: AppBar(
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
},
),
),
);
} }
}, },
), ),
@@ -148,8 +180,18 @@ class _ChatScreenState extends State<ChatScreen> {
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: AppBar( appBar: AppBar(
titleSpacing: 0, titleSpacing: 0,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go(RouteConstants.chatSelectScreen);
},
),
backgroundColor: appTheme.gray100, backgroundColor: appTheme.gray100,
centerTitle: false, centerTitle: false,
title: GestureDetector( title: GestureDetector(
onTap: () { onTap: () {
GoRouter.of(context).pushNamed('profileScreen', extra: state.chatRoom.users.receiver.slug); GoRouter.of(context).pushNamed('profileScreen', extra: state.chatRoom.users.receiver.slug);
@@ -224,7 +266,7 @@ class _ChatScreenState extends State<ChatScreen> {
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId; final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
final message = state.chatMessages[index]; final message = state.chatMessages[index];
final isPlanOrderMessage = message.type == 'plan_order'; final isPlanOrderMessage = message.type == 'plan_order';
PacketOptions? messageOption; PacketOptions? messageOption;
if (isPlanOrderMessage && chosenPlan != null) { if (isPlanOrderMessage && chosenPlan != null) {
if (chosenPlan.optionType == 'options1') { if (chosenPlan.optionType == 'options1') {
@@ -235,7 +277,7 @@ class _ChatScreenState extends State<ChatScreen> {
messageOption = chosenPlan.plan.packet3Options; messageOption = chosenPlan.plan.packet3Options;
} }
} }
return Column( return Column(
children: [ children: [
_messageDate(message.updatedAt), _messageDate(message.updatedAt),
@@ -284,7 +326,7 @@ class _ChatScreenState extends State<ChatScreen> {
final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId; final isCurrentUserAuthor = state.chatRoom.users.receiver.id != state.chatMessages[index].authorId;
final message = state.chatMessages[index]; final message = state.chatMessages[index];
final isPlanOrderMessage = message.type == 'plan_order'; final isPlanOrderMessage = message.type == 'plan_order';
PacketOptions? messageOption; PacketOptions? messageOption;
if (isPlanOrderMessage && chosenPlan != null) { if (isPlanOrderMessage && chosenPlan != null) {
if (chosenPlan.optionType == 'options1') { if (chosenPlan.optionType == 'options1') {
@@ -295,7 +337,7 @@ class _ChatScreenState extends State<ChatScreen> {
messageOption = chosenPlan.plan.packet3Options; messageOption = chosenPlan.plan.packet3Options;
} }
} }
return Column( return Column(
children: [ children: [
if (isPlanOrderMessage && chosenPlan != null && messageOption != null) if (isPlanOrderMessage && chosenPlan != null && messageOption != null)

View File

@@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
@@ -12,7 +11,6 @@ import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart'; import 'package:eatvio_app/features/chat/services/chat_service.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ChatRepository { class ChatRepository {
final ChatService chatService; final ChatService chatService;
@@ -34,160 +32,104 @@ class ChatRepository {
String? data, String? data,
List<File>? files, List<File>? files,
}) async { }) async {
try { final multipartFiles = <MultipartFile>[];
final multipartFiles = <MultipartFile>[];
if (files != null) { if (files != null) {
for (final file in files) { for (final file in files) {
final fileBytes = await file.readAsBytes(); final fileBytes = await file.readAsBytes();
final multipartFile = MultipartFile.fromBytes( final multipartFile = MultipartFile.fromBytes(
fileBytes, fileBytes,
filename: file.path.split('/').last, filename: file.path.split('/').last,
contentType: MediaType('application', 'octet-stream'), contentType: MediaType('application', 'octet-stream'),
); );
multipartFiles.add(multipartFile); multipartFiles.add(multipartFile);
}
} }
// Если data пустая строка или null, не передаем её вообще
// Сервер требует либо непустую строку, либо отсутствие поля
final dataToSend = (data == null || data.trim().isEmpty) ? null : data;
return await chatService.sendMessage(
slug: slug,
data: dataToSend,
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
} }
// Если data пустая строка или null, не передаем её вообще
// Сервер требует либо непустую строку, либо отсутствие поля
final dataToSend = (data == null || data.trim().isEmpty) ? null : data;
return await chatService.sendMessage(
slug: slug,
data: dataToSend,
mediaData: multipartFiles.isEmpty ? null : multipartFiles,
socketId: (await SocketIdStorage.getSocketIdFromStorage())!,
);
} }
Future<ChatRoomModel> getChatRoom(String uuid) async { Future<ChatRoomModel> getChatRoom(String uuid) async {
try { return (await chatService.getChatRoom(uuid: uuid));
return (await chatService.getChatRoom(uuid: uuid));
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<ChatRoomPaginationModel> getChatRooms({String? query, int page = 1}) async { Future<ChatRoomPaginationModel> getChatRooms({String? query, int page = 1}) async {
try { return (await chatService.getChatRooms(query: query, page: page));
return (await chatService.getChatRooms(query: query, page: page));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<ChatMessagesModel> getChatMessages(String id, int page) async { Future<ChatMessagesModel> getChatMessages(String id, int page) async {
try { return (await chatService.getChatMessages(id: id, page: page));
return (await chatService.getChatMessages(id: id, page: page));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async { Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
try { return (await chatService.getChatRoomIdByUserSlug(user: slug));
return (await chatService.getChatRoomIdByUserSlug(user: slug));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> markMessagesAsRead(String id, List<int> messageIds) async { Future<void> markMessagesAsRead(String id, List<int> messageIds) async {
try { await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> cancelChosenPlan({ Future<void> cancelChosenPlan({
required int id, required int id,
}) async { }) async {
try { await chatService.cancelChosenPlan(id: id);
await chatService.cancelChosenPlan(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> choseStartPlanDate({required int id, required Map<String, String> date}) async { Future<void> choseStartPlanDate({required int id, required Map<String, String> date}) async {
try { await chatService.choseStartPlanDate(id: id, date: date);
await chatService.choseStartPlanDate(id: id, date: date);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<PaymentModel> getPaymentLink({ Future<PaymentModel> getPaymentLink({
required int id, required int id,
}) async { }) async {
try { return await chatService.getPaymentLink(id: id);
return await chatService.getPaymentLink(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<ChosenPlanModel> getChosenPlan({ Future<ChosenPlanModel> getChosenPlan({
required int id, required int id,
}) async { }) async {
try { return await chatService.getChosenPlan(id: id);
return await chatService.getChosenPlan(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> finishChosenPlan({ Future<void> finishChosenPlan({
required int id, required int id,
}) async { }) async {
try { await chatService.finishChosenPlan(id: id);
await chatService.finishChosenPlan(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> complainAboutConsultation({ Future<void> complainAboutConsultation({
required int id, required int id,
required PlanComplainModel body, required PlanComplainModel body,
}) async { }) async {
try { await chatService.complainAboutConsultation(id: id, body: body);
await chatService.complainAboutConsultation(id: id, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<String> getChatByUserSlug(String slug) async { Future<String> getChatByUserSlug(String slug) async {
try { final response = await chatService.getChatByUserSlug(slug: slug);
final response = await chatService.getChatByUserSlug(slug: slug); if (response.response.statusCode == 404) {
if (response.response.statusCode == 404) { return '404';
return '404';
} else {
return response.data['id'];
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return 'error';
} }
return response.data['id'];
} }
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart'; import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_state.dart';
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart'; import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ComplainingCubit extends Cubit<ComplainingState> { class ComplainingCubit extends Cubit<ComplainingState> {
final ComplainingRepository repository; final ComplainingRepository repository;
@@ -81,6 +82,7 @@ class ComplainingCubit extends Cubit<ComplainingState> {
} }
} }
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
emit(state.copyWith(wasSentSuccessfully: false)); emit(state.copyWith(wasSentSuccessfully: false));

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/complaining/models/complain_model.dart'; import 'package:eatvio_app/features/complaining/models/complain_model.dart';
import 'package:eatvio_app/features/complaining/services/complaining_service.dart'; import 'package:eatvio_app/features/complaining/services/complaining_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ComplainingRepository { class ComplainingRepository {
final ComplainingService service; final ComplainingService service;
@@ -11,12 +10,8 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendPlanComplain(slug: slug, category: category, body: body);
await service.sendPlanComplain(slug: slug, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendRecipeComplain({ Future<void> sendRecipeComplain({
@@ -24,12 +19,8 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendRecipeComplain(slug: slug, category: category, body: body);
await service.sendRecipeComplain(slug: slug, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendBlogPostsComplain({ Future<void> sendBlogPostsComplain({
@@ -37,12 +28,8 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendBlogPostsComplain(id: id, category: category, body: body);
await service.sendBlogPostsComplain(id: id, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendProfileComplain({ Future<void> sendProfileComplain({
@@ -50,12 +37,8 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendProfileComplain(slug: slug, category: category, body: body);
await service.sendProfileComplain(slug: slug, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendDietComplain({ Future<void> sendDietComplain({
@@ -63,12 +46,8 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendDietComplain(id: id, category: category, body: body);
await service.sendDietComplain(id: id, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendCommentComplain({ Future<void> sendCommentComplain({
@@ -76,11 +55,7 @@ class ComplainingRepository {
required String category, required String category,
required ComplainModel body, required ComplainModel body,
}) async { }) async {
try { await service.sendCommentComplain(id: id, category: category, body: body);
await service.sendCommentComplain(id: id, category: category, body: body);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -1,9 +1,11 @@
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart'; import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultant_profile_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart'; import 'package:eatvio_app/features/consultant/become_consultant/repositories/consultant_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class RegisterConsultantCubit extends Cubit<RegisterConsultantState> { class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
final ConsultantRepository consultantRepository; final ConsultantRepository consultantRepository;
@@ -20,70 +22,106 @@ class RegisterConsultantCubit extends Cubit<RegisterConsultantState> {
); );
Future<void> getMyRequestStatus(String slug) async { Future<void> getMyRequestStatus(String slug) async {
_update = true; try {
final myRequestStatus = await consultantRepository.getMyRequestStatus(slug); _update = true;
final certPhotos = await consultantRepository.getConsultantCertPhotos(); final myRequestStatus = await consultantRepository.getMyRequestStatus(slug);
final certPhotos = await consultantRepository.getConsultantCertPhotos();
List<CertificatesModel>? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) { List<CertificatesModel>? certificates = myRequestStatus.educationModel.completedCourses?.map((courseMap) {
final course = courseMap.values.first; final course = courseMap.values.first;
return CertificatesModel(title: course.title, date: course.date); return CertificatesModel(title: course.title, date: course.date);
}).toList(); }).toList();
String? sanitizeLink(String? link) { String? sanitizeLink(String? link) {
return (link != null && link.isNotEmpty) ? link : null; return (link != null && link.isNotEmpty) ? link : null;
} }
emit( emit(
RegisterConsultantState( RegisterConsultantState(
consultant: ConsultantProfileModel( consultant: ConsultantProfileModel(
certificates: certificates, certificates: certificates,
socialLinks: SocialLinksModel( socialLinks: SocialLinksModel(
vkLink: sanitizeLink(myRequestStatus.educationModel.vkLink), vkLink: sanitizeLink(myRequestStatus.educationModel.vkLink),
tgLink: sanitizeLink(myRequestStatus.educationModel.tgLink), tgLink: sanitizeLink(myRequestStatus.educationModel.tgLink),
fbLink: sanitizeLink(myRequestStatus.educationModel.fbLink), fbLink: sanitizeLink(myRequestStatus.educationModel.fbLink),
instaLink: sanitizeLink(myRequestStatus.educationModel.instaLink), instaLink: sanitizeLink(myRequestStatus.educationModel.instaLink),
personalSiteLink: sanitizeLink(myRequestStatus.educationModel.personalSiteLink), personalSiteLink: sanitizeLink(myRequestStatus.educationModel.personalSiteLink),
),
hasMedicalEducation: myRequestStatus.educationModel.hasMedicalEducation == 1,
), ),
hasMedicalEducation: myRequestStatus.educationModel.hasMedicalEducation == 1, acceptOffer: false,
loading: false,
requestModel: myRequestStatus,
certPhotos: certPhotos,
isUpdating: _update,
error: null,
), ),
acceptOffer: false, );
loading: false, } catch (e, st) {
requestModel: myRequestStatus, await reportFrom(e, st, this);
certPhotos: certPhotos, log(e.toString());
isUpdating: _update emit(state.copyWith(loading: false, error: e.toString()));
), }
);
} }
Future<void> disableLoading() async { Future<void> disableLoading() async {
final certPhotos = await consultantRepository.getConsultantCertPhotos(); try {
emit(state.copyWith(loading: false, certPhotos: certPhotos)); final certPhotos = await consultantRepository.getConsultantCertPhotos();
emit(state.copyWith(loading: false, certPhotos: certPhotos, error: null));
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(state.copyWith(loading: false, error: e.toString()));
}
}
/// Повторная загрузка после [DataErrorWidget], аналогично [AboutDietCubit.getDiet].
Future<void> reloadData(String? slug) async {
emit(state.copyWith(loading: true, error: null));
if (slug != null) {
await getMyRequestStatus(slug);
} else {
await disableLoading();
}
} }
void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) { void updateConsultantProfileModel(ConsultantProfileModel consultant, [bool acceptOffer = false]) {
emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false)); emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false, error: null));
} }
Future<void> registerConsultant() async { Future<void> registerConsultant() async {
if (state.newAddedPhotos.isNotEmpty) { emit(state.copyWith(error: null));
await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos); try {
} if (state.newAddedPhotos.isNotEmpty) {
if (state.deletedPhotoIds.isNotEmpty) { await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos);
for (final int id in state.deletedPhotoIds) {
await consultantRepository.deletePhoto(id);
} }
if (state.deletedPhotoIds.isNotEmpty) {
for (final int id in state.deletedPhotoIds) {
await consultantRepository.deletePhoto(id);
}
}
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(state.copyWith(error: e.toString()));
} }
await consultantRepository.registerConsultant(consultant: state.consultant, update: _update);
} }
Future<void> addCertPhotos(List<File> files) async { Future<void> addCertPhotos(List<File> files) async {
if (files.isEmpty) { if (files.isEmpty) {
return; return;
} }
final compressedFiles = await ImageCompressUtility.compressFilesList(files); try {
final compressedFiles = await ImageCompressUtility.compressFilesList(files);
emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles])); emit(state.copyWith(newAddedPhotos: [...state.newAddedPhotos, ...compressedFiles], error: null));
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(state.copyWith(error: e.toString()));
}
} }
Future<void> removeCertPhoto(int? id, String? path) async { Future<void> removeCertPhoto(int? id, String? path) async {

View File

@@ -18,5 +18,6 @@ abstract class RegisterConsultantState with _$RegisterConsultantState {
@Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos, @Default(<ConsultantCertPhotoModel>[]) List<ConsultantCertPhotoModel> certPhotos,
@Default(<File>[]) List<File> newAddedPhotos, @Default(<File>[]) List<File> newAddedPhotos,
@Default(<int>[]) List<int> deletedPhotoIds, @Default(<int>[]) List<int> deletedPhotoIds,
String? error,
}) = _RegisterConsultantState; }) = _RegisterConsultantState;
} }

View File

@@ -17,18 +17,13 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
title: const Text('Стать консультантом'), title: const Text('Стать консультантом'),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
centerTitle: false, centerTitle: false,
leading: IconButton( leading: IconButton(onPressed: () => GoRouter.of(context).pop(), icon: const Icon(Icons.close)),
onPressed: () => GoRouter.of(context).pop(),
icon: const Icon(Icons.close),
),
), ),
body: const Padding( body: const Padding(
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
child: Text( child: Text(
'Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\nВнутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.', 'Чтобы стать консультантом необходимо пройти короткую регистрацию. Подготовьте свои дипломы и/или сертификаты в формате jpg, они Вам пригодятся для проверки и подтверждения. Также необходимо будет указать ссылки на Ваши соц. сети и указать тип лица (физ. лицо или юр. лицо). На данный момент доступна регистрация в качестве физ. лица.\n\nВнутри Итвио разница между обычным пользователем и консультантом заключается в дополнительных возможностях последнего создавать публичные диеты, рецепты и планы питания; участвовать в ежемесячных рейтингах.',
style: TextStyle( style: TextStyle(fontSize: 16),
fontSize: 16,
),
), ),
), ),
bottomSheet: _startRegistrationButton(), bottomSheet: _startRegistrationButton(),
@@ -37,24 +32,16 @@ class _BecomeConsultantScreenState extends State<BecomeConsultantScreen> {
InkWell _startRegistrationButton() { InkWell _startRegistrationButton() {
return InkWell( return InkWell(
onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen'), onTap: () => GoRouter.of(context).pushReplacementNamed('stepperBecomeConsultantScreen', extra: {'slug': null}),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: Ink( child: Ink(
height: 52, height: 52,
width: MediaQuery.of(context).size.width - 32, width: MediaQuery.of(context).size.width - 32,
decoration: BoxDecoration( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), color: appTheme.blueA400),
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: const Center( child: const Center(
child: Text( child: Text(
'Начать регистрацию', 'Начать регистрацию',
style: TextStyle( style: TextStyle(color: Colors.white, height: 1, fontWeight: FontWeight.w600, fontSize: 18),
color: Colors.white,
height: 1,
fontWeight: FontWeight.w600,
fontSize: 18,
),
), ),
), ),
), ),

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/utils/image_constant.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/loader.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart';
@@ -9,6 +10,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/registration_as_screen.dart';
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart'; import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/social_networks_screen.dart';
import 'package:eatvio_app/core/locator_service.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:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -48,8 +50,37 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
create: (context) => registrationConsultantCubit, create: (context) => registrationConsultantCubit,
child: BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>( child: BlocBuilder<RegisterConsultantCubit, RegisterConsultantState>(
builder: (context, state) { builder: (context, state) {
if (state.loading == false) { if (state.loading) {
return const Scaffold(body: Loader());
}
if (state.error != null) {
return Scaffold( return Scaffold(
appBar: AppBar(
centerTitle: true,
leading: BackButton(
onPressed: () {
if (context.canPop()) {
GoRouter.of(context).pop();
return;
}
GoRouter.of(context).go('/feed-screen');
},
),
title: Text(
'',
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
softWrap: true,
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
),
body: DataErrorWidget(
onReload: () => context.read<RegisterConsultantCubit>().reloadData(widget.slug),
),
);
}
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row( floatingActionButton: Row(
children: [ children: [
@@ -136,6 +167,9 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
try { try {
await registrationConsultantCubit.registerConsultant(); await registrationConsultantCubit.registerConsultant();
if (!context.mounted) return; if (!context.mounted) return;
if (registrationConsultantCubit.state.error != null) {
return;
}
await showModalBottomSheet( await showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
showDragHandle: true, showDragHandle: true,
@@ -183,7 +217,7 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
); );
} catch (e) { } catch (e) {
if (!context.mounted) return; if (!context.mounted) return;
showDialog( showDialog<void>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
actionsPadding: const EdgeInsets.symmetric(horizontal: 4), actionsPadding: const EdgeInsets.symmetric(horizontal: 4),
@@ -191,9 +225,7 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
actions: [ actions: [
TextButton( TextButton(
child: const Text('Ок'), child: const Text('Ок'),
onPressed: () { onPressed: () => Navigator.of(context).pop(),
Navigator.of(context).pop();
},
), ),
], ],
), ),
@@ -282,9 +314,6 @@ class _StepperBecomeConsultantScreenState extends State<StepperBecomeConsultantS
], ],
), ),
); );
} else {
return const Scaffold(body: Loader());
}
}, },
), ),
); );

View File

@@ -5,7 +5,6 @@ import 'package:eatvio_app/features/consultant/become_consultant/models/consulta
import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart'; import 'package:eatvio_app/features/consultant/become_consultant/models/consultation_request_model.dart';
import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart'; import 'package:eatvio_app/features/consultant/become_consultant/services/consultant_service.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ConsultantRepository { class ConsultantRepository {
final ConsultantService consultantService; final ConsultantService consultantService;
@@ -13,90 +12,58 @@ class ConsultantRepository {
const ConsultantRepository({required this.consultantService}); const ConsultantRepository({required this.consultantService});
Future<void> registerConsultant({required ConsultantProfileModel consultant, bool update = false}) async { Future<void> registerConsultant({required ConsultantProfileModel consultant, bool update = false}) async {
try { if (update) {
if (update) { return await consultantService.consultantUpdate(consultantModel: consultant);
return await consultantService.consultantUpdate(consultantModel: consultant); } else {
} else { return await consultantService.consultantRegistration(consultantModel: consultant);
return await consultantService.consultantRegistration(consultantModel: consultant);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async { Future<ConsultationRequestModel> getConsultantRequestStatus(String slug) async {
try { return await consultantService.getConsultantRequestStatus(slug: slug);
return await consultantService.getConsultantRequestStatus(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<ConsultationRequestModel> getMyRequestStatus(String slug) async { Future<ConsultationRequestModel> getMyRequestStatus(String slug) async {
try { return await consultantService.getMyRequestStatus(slug: slug);
return await consultantService.getMyRequestStatus(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<ConsultantCertPhotoModel>> getConsultantCertPhotos() async { Future<List<ConsultantCertPhotoModel>> getConsultantCertPhotos() async {
try { return await consultantService.getConsultantCertPhotos();
return await consultantService.getConsultantCertPhotos();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<ConsultantCertPhotoModel>> storeConsultantCertPhotos({required List<File> files}) async { Future<List<ConsultantCertPhotoModel>> storeConsultantCertPhotos({required List<File> files}) async {
try { final multipartFiles = <MultipartFile>[];
final multipartFiles = <MultipartFile>[];
for (final file in files) { for (final file in files) {
final fileBytes = await file.readAsBytes(); final fileBytes = await file.readAsBytes();
final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream')); final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream'));
multipartFiles.add(multipartFile); multipartFiles.add(multipartFile);
}
if (multipartFiles.isEmpty) {
return [];
}
return await consultantService.storeConsultantCertPhotos(photos: multipartFiles);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
if (multipartFiles.isEmpty) {
return [];
}
return await consultantService.storeConsultantCertPhotos(photos: multipartFiles);
} }
Future<void> hideConsultantCertPhoto(int id) async { Future<void> hideConsultantCertPhoto(int id) async {
try { await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id});
await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> unHideConsultantCertPhoto(int id) async { Future<void> unHideConsultantCertPhoto(int id) async {
try { await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id});
await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deletePhoto(int id) async { Future<void> deletePhoto(int id) async {
try { await consultantService.deletePhoto(id: id);
await consultantService.deletePhoto(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultant_rating/repositories/consultant_ra
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultantRatingCubit extends Cubit<ConsultantRatingState> { class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
final ConsultantRatingRepository repository; final ConsultantRatingRepository repository;
@@ -20,6 +21,7 @@ class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name); final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name);
emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores)); emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(isStatisticsLoading: true, error: e.toString())); emit(state.copyWith(isStatisticsLoading: true, error: e.toString()));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
@@ -31,6 +33,7 @@ class ConsultantRatingCubit extends Cubit<ConsultantRatingState> {
final scoreLogs = await repository.getScoreLogs(date: date); final scoreLogs = await repository.getScoreLogs(date: date);
emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null)); emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(isRatingLoading: true, error: e.toString())); emit(state.copyWith(isRatingLoading: true, error: e.toString()));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }

View File

@@ -1,7 +1,6 @@
import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart'; import 'package:eatvio_app/features/consultant_rating/models/rating_model.dart';
import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart'; import 'package:eatvio_app/features/consultant_rating/models/statistics_model.dart';
import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart'; import 'package:eatvio_app/features/consultant_rating/services/consultant_rating_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ConsultantRatingRepository { class ConsultantRatingRepository {
final ConsultantRatingService service; final ConsultantRatingService service;
@@ -9,21 +8,13 @@ class ConsultantRatingRepository {
ConsultantRatingRepository(this.service); ConsultantRatingRepository(this.service);
Future<StatisticsModel> getConsultantStatistics({String? date}) async { Future<StatisticsModel> getConsultantStatistics({String? date}) async {
try { return await service.getConsultantStatistics(date: date);
return await service.getConsultantStatistics(date: date);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<RatingModel>> getScoreLogs({String? date}) async { Future<List<RatingModel>> getScoreLogs({String? date}) async {
try { final data = (await service.getScoreLogs(date: date)).data;
final data = (await service.getScoreLogs(date: date)).data; return data;
return data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/core/enums/base_state.dart';
import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart'; import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_state.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart'; import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultationsCubit extends Cubit<ConsultationsState> { class ConsultationsCubit extends Cubit<ConsultationsState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -14,6 +15,7 @@ class ConsultationsCubit extends Cubit<ConsultationsState> {
final consultations = await consultationsRepository.getConsultations(); final consultations = await consultationsRepository.getConsultations();
emit(state.copyWith(consultations: consultations, state: BaseState.success)); emit(state.copyWith(consultations: consultations, state: BaseState.success));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(state: BaseState.error)); emit(state.copyWith(state: BaseState.error));
} }

View File

@@ -1,7 +1,6 @@
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/consultations/services/consultation_service.dart'; import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
@@ -11,13 +10,8 @@ class ConsultationsRepository {
ConsultationsRepository({required this.consultationService}); ConsultationsRepository({required this.consultationService});
Future<List<ConsultationModel>> getConsultations() async { Future<List<ConsultationModel>> getConsultations() async {
try { final consultations = await consultationService.getConsultations();
final consultations = await consultationService.getConsultations(); return consultations.data;
return consultations.data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<ConsultationModel>> getFilteredConsultations({ Future<List<ConsultationModel>> getFilteredConsultations({
@@ -26,44 +20,24 @@ class ConsultationsRepository {
FiltersComparisonEnum? stateComparison, FiltersComparisonEnum? stateComparison,
FiltersComparisonEnum? dateComparison, FiltersComparisonEnum? dateComparison,
}) async { }) async {
try { final model = await consultationService.getFilteredConsultations(
final model = await consultationService.getFilteredConsultations( state: state,
state: state, date: date,
date: date, stateComparison: stateComparison,
stateComparison: stateComparison, dateComparison: dateComparison,
dateComparison: dateComparison, );
); return model.data;
return model.data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<StatisticModel> getStatistic() async { Future<StatisticModel> getStatistic() async {
try { return consultationService.getStatistics();
return await consultationService.getStatistics();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async { Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
try { return consultationService.getConsultantFinances(date, dateComparison);
return await consultationService.getConsultantFinances(date, dateComparison);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async { Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
try { return consultationService.getUserPurchases(date, dateComparison);
return await consultationService.getUserPurchases(date, dateComparison);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -13,9 +13,9 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart'; import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
class CreateDietCubit extends Cubit<CreateDietState> { class CreateDietCubit extends Cubit<CreateDietState> {
@@ -29,9 +29,10 @@ class CreateDietCubit extends Cubit<CreateDietState> {
Future<void> setInitialData(Diet? diet) async { Future<void> setInitialData(Diet? diet) async {
if (diet == null) return; if (diet == null) return;
emit(state.copyWith(isLoading: true, isDietBeingEdited: true)); try {
dietId = diet.id; emit(state.copyWith(isLoading: true, isDietBeingEdited: true));
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id); dietId = diet.id;
final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id);
final countOfDays = diet.duration; final countOfDays = diet.duration;
final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]); final List<List<List<DailySchedule>>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]);
@@ -66,7 +67,11 @@ class CreateDietCubit extends Cubit<CreateDietState> {
imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl, imageUrl: appConfig.buildType == AppBuildType.tests ? null : imageUrl,
), ),
); );
emit(state.copyWith(isLoading: false, isDietBeingEdited: false, dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration))); emit(state.copyWith(isLoading: false, isDietBeingEdited: false, dietState: diet.state, durationIndex: durationValues.indexOf(diet.duration)));
} catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(isLoading: false, isDietBeingEdited: false));
}
} }
void setDetails(String text) { void setDetails(String text) {
@@ -112,7 +117,12 @@ class CreateDietCubit extends Cubit<CreateDietState> {
} }
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
return await glossaryRepository.getGlossaryListByQuery(query); try {
return await glossaryRepository.getGlossaryListByQuery(query);
} catch (e, st) {
await reportFrom(e, st, this);
return [];
}
} }
void setImage(XFile? image, {String? imageUrl}) { void setImage(XFile? image, {String? imageUrl}) {
@@ -204,7 +214,8 @@ class CreateDietCubit extends Cubit<CreateDietState> {
Future<Diet?> getDietForPreview() async { Future<Diet?> getDietForPreview() async {
if (!_isFormValidForDraft()) return null; if (!_isFormValidForDraft()) return null;
final authorProfile = await profileRepository.getMyProfile(); try {
final authorProfile = await profileRepository.getMyProfile();
final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[]; final imageBytes = (await state.dietData.image?.readAsBytes()) ?? <int>[];
String image = ''; String image = '';
if (imageBytes.isNotEmpty && imageBytes.length <= _maxImageBytesForBase64) { if (imageBytes.isNotEmpty && imageBytes.length <= _maxImageBytesForBase64) {
@@ -297,19 +308,26 @@ class CreateDietCubit extends Cubit<CreateDietState> {
isFavorite: false, isFavorite: false,
dailyNutrition: dailyNutrition, dailyNutrition: dailyNutrition,
); );
return diet; return diet;
} catch (e, st) {
await reportFrom(e, st, this);
return null;
}
} }
Future<File> downloadImageAsFile(String imageUrl) async { Future<File?> downloadImageAsFile(String imageUrl) async {
final response = await http.get(Uri.parse(imageUrl)); try {
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode == 200) { if (response.statusCode != 200) {
throw Exception('Не удалось загрузить изображение');
}
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final filePath = '${tempDir.path}/${imageUrl.split('/').last}'; final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
final file = File(filePath); final file = File(filePath);
return await file.writeAsBytes(response.bodyBytes); return file.writeAsBytes(response.bodyBytes);
} else { } catch (e, st) {
throw Exception('Не удалось загрузить изображение'); await reportFrom(e, st, this);
return null;
} }
} }
@@ -353,7 +371,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft)); emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft));
return null; return null;
} catch (e, st) { } catch (e, st) {
await Sentry.captureException(e, stackTrace: st); await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(isDietPublishing: false)); emit(state.copyWith(isDietPublishing: false));
} }
@@ -391,7 +409,7 @@ class CreateDietCubit extends Cubit<CreateDietState> {
emit(state.copyWith(isDietBeingEdited: false)); emit(state.copyWith(isDietBeingEdited: false));
return null; return null;
} catch (e, st) { } catch (e, st) {
await Sentry.captureException(e, stackTrace: st); await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(isDietBeingEdited: false)); emit(state.copyWith(isDietBeingEdited: false));
return 'error'; return 'error';

View File

@@ -2,7 +2,6 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CreateDietRepository { class CreateDietRepository {
final CreateDietService createDietService; final CreateDietService createDietService;
@@ -62,48 +61,40 @@ class CreateDietRepository {
dietMap.addAll({'name': name, 'duration': duration}); dietMap.addAll({'name': name, 'duration': duration});
final formData = FormData.fromMap(dietMap); final formData = FormData.fromMap(dietMap);
try { switch (state) {
switch (state) { case DietState.active:
case DietState.active: return await createDietService.createPublishedDiet(formData: formData);
return await createDietService.createPublishedDiet(formData: formData); case DietState.draft:
case DietState.draft: return await createDietService.createDraftedDiet(formData: formData);
return await createDietService.createDraftedDiet(formData: formData); case DietState.personal:
case DietState.personal: return await createDietService.createPersonalDiet(formData: formData);
return await createDietService.createPersonalDiet(formData: formData);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async { Future<Map<int, Map<int, Map<int, DailySchedule>>>> getDietPortionsNutrition(int dietId) async {
try { final response = await createDietService.getDietPortionsNutrition(diet: dietId);
final response = await createDietService.getDietPortionsNutrition(diet: dietId); final data = response.data;
final data = response.data; final dailySchedule = (data as Map<String, dynamic>).map((key, value) {
final dailySchedule = (data as Map<String, dynamic>).map((key, value) { return MapEntry(
return MapEntry( int.parse(key),
int.parse(key), (value as Map<String, dynamic>).map((key, value) {
(value as Map<String, dynamic>).map((key, value) { return MapEntry(
return MapEntry( int.parse(key),
int.parse(key), (value as Map<String, dynamic>).map((key, value) {
(value as Map<String, dynamic>).map((key, value) { final portionMap = Map<String, dynamic>.from(value as Map<String, dynamic>);
final portionMap = Map<String, dynamic>.from(value as Map<String, dynamic>); final pnvid = portionMap['product_nutritional_value_id'];
final pnvid = portionMap['product_nutritional_value_id']; if (pnvid is String) {
if (pnvid is String) { portionMap['product_nutritional_value_id'] = int.tryParse(pnvid);
portionMap['product_nutritional_value_id'] = int.tryParse(pnvid); }
} return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap));
return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap)); }),
}), );
); }),
}), );
); });
}); return dailySchedule;
return dailySchedule;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<int> updateDiet({ Future<int> updateDiet({
@@ -161,23 +152,16 @@ class CreateDietRepository {
} }
final formData = FormData.fromMap(dietMap); final formData = FormData.fromMap(dietMap);
try { switch (state) {
switch (state) { case DietState.active:
case DietState.active: final response = await createDietService.updatePublished(diet: dietId, formData: formData);
final response = await createDietService.updatePublished(diet: dietId, formData: formData); return response.response.statusCode ?? 0;
return response.response.statusCode ?? 0; case DietState.draft:
case DietState.draft: final response = await createDietService.updateDrafted(diet: dietId, formData: formData);
final response = await createDietService.updateDrafted(diet: dietId, formData: formData); return response.response.statusCode ?? 0;
return response.response.statusCode ?? 0; case DietState.personal:
case DietState.personal: final response = await createDietService.updatePersonal(diet: dietId, formData: formData);
final response = await createDietService.updatePersonal(diet: dietId, formData: formData); return response.response.statusCode ?? 0;
return response.response.statusCode ?? 0;
}
} on DioException catch (e) {
return e.response?.statusCode ?? 0;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart'; import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_state.dart';
import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart'; import 'package:eatvio_app/features/create_plan/repositories/create_plan_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
class CreatePlanCubit extends Cubit<CreatePlanState> { class CreatePlanCubit extends Cubit<CreatePlanState> {
@@ -457,7 +458,8 @@ class CreatePlanCubit extends Cubit<CreatePlanState> {
await createPlanRepository.createPlan(model: planMap); await createPlanRepository.createPlan(model: planMap);
emit(state.copyWith(isLoading: false, planCreated: true)); emit(state.copyWith(isLoading: false, planCreated: true));
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit(state.copyWith(isLoading: false, planCreated: true)); emit(state.copyWith(isLoading: false, planCreated: true));
} }

View File

@@ -1,6 +1,5 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart'; import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CreatePlanRepository { class CreatePlanRepository {
final CreatePlanService createPlanService; final CreatePlanService createPlanService;
@@ -10,13 +9,9 @@ class CreatePlanRepository {
Future<void> createPlan({ Future<void> createPlan({
required Map<String, dynamic> model, required Map<String, dynamic> model,
}) async { }) async {
try { final formData = FormData.fromMap(model);
final formData = FormData.fromMap(model); return await createPlanService.createPlan(formData: formData);
return await createPlanService.createPlan(formData: formData);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -11,7 +11,7 @@ import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart'; import 'package:eatvio_app/features/glossary/repositories/glossary_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -166,7 +166,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
await recipeRepository.publishRecipe(model, update, slug); await recipeRepository.publishRecipe(model, update, slug);
return true; return true;
} catch (e, st) { } catch (e, st) {
await Sentry.captureException(e, stackTrace: st); await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -179,7 +179,7 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
emit(state.copyWith(drafted: true)); emit(state.copyWith(drafted: true));
return true; return true;
} catch (e, st) { } catch (e, st) {
await Sentry.captureException(e, stackTrace: st); await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -204,16 +204,19 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
)); ));
} }
Future<File> downloadImageAsFile(String imageUrl) async { Future<File?> downloadImageAsFile(String imageUrl) async {
final response = await http.get(Uri.parse(imageUrl)); try {
final response = await http.get(Uri.parse(imageUrl));
if (response.statusCode == 200) { if (response.statusCode != 200) {
throw Exception('Не удалось загрузить изображение');
}
final tempDir = await getTemporaryDirectory(); final tempDir = await getTemporaryDirectory();
final filePath = '${tempDir.path}/${imageUrl.split('/').last}'; final filePath = '${tempDir.path}/${imageUrl.split('/').last}';
final file = File(filePath); final file = File(filePath);
return await file.writeAsBytes(response.bodyBytes); return file.writeAsBytes(response.bodyBytes);
} else { } catch (e, st) {
throw Exception('Не удалось загрузить изображение'); await reportFrom(e, st, this);
return null;
} }
} }
@@ -222,44 +225,53 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
} }
Future<void> recipeToEntity(Recipe recipe) async { Future<void> recipeToEntity(Recipe recipe) async {
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}'); try {
final file = await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${recipe.image}');
if (file == null) {
disableLoading();
return;
}
final List<StepEntity> steps = await Future.wait( final List<StepEntity> steps = await Future.wait(
recipe.steps!.map( recipe.steps!.map(
(e) async => StepEntity( (e) async => StepEntity(
description: e.description, description: e.description,
image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'), image: e.image.isEmpty ? null : await downloadImageAsFile('${sl<AppConfig>().baseUrl}/storage/${e.image}'),
),
), ),
), );
); emit(
emit( CreateRecipeState(
CreateRecipeState( entity: RecipeCreateEntity(
entity: RecipeCreateEntity( title: recipe.title,
title: recipe.title, typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!),
typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!), countOfPortions: recipe.servings,
countOfPortions: recipe.servings, energyInOnePortion: recipe.calories,
energyInOnePortion: recipe.calories, totalCookHours: recipe.cookingTime ~/ 60,
totalCookHours: recipe.cookingTime ~/ 60, totalCookMinutes: recipe.cookingTime % 60,
totalCookMinutes: recipe.cookingTime % 60, activeCookHours: recipe.activeCookingTime ~/ 60,
activeCookHours: recipe.activeCookingTime ~/ 60, activeCookMinutes: recipe.activeCookingTime % 60,
activeCookMinutes: recipe.activeCookingTime % 60, description: recipe.description,
description: recipe.description, image: file,
image: file, videoUrl: recipe.videoUrl,
videoUrl: recipe.videoUrl, ingredients: recipe.ingredients!
ingredients: recipe.ingredients! .map((e) => i.Ingredient(
.map((e) => i.Ingredient( name: e.name,
name: e.name, unit: e.unit!,
unit: e.unit!, portion: e.portion,
portion: e.portion, productId: e.productId,
productId: e.productId, ))
)) .toList(),
.toList(), steps: steps,
steps: steps, tags: recipe.tags.map((e) => (e.name)).toList(),
tags: recipe.tags.map((e) => (e.name)).toList(), ),
loading: false,
), ),
loading: false, );
), } catch (e, st) {
); await reportFrom(e, st, this);
disableLoading();
}
} }
bool validateAll() { bool validateAll() {
@@ -376,13 +388,18 @@ class CreateRecipeCubit extends Cubit<CreateRecipeState> {
await recipeRepository.deleteRecipe(slug); await recipeRepository.deleteRecipe(slug);
return true; return true;
} catch (e, s) { } catch (e, s) {
await Sentry.captureException(e, stackTrace: s); await reportFrom(e, s, this);
return false; return false;
} }
} }
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
return await glossaryRepository.getGlossaryListByQuery(query); try {
return await glossaryRepository.getGlossaryListByQuery(query);
} catch (e, st) {
await reportFrom(e, st, this);
return [];
}
} }
bool validateSteps() { bool validateSteps() {

View File

@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/create_recipe/models/recipe_model.dart';
import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart'; import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/models/step.dart'; import 'package:eatvio_app/features/create_recipe/models/step.dart';
import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart'; import 'package:eatvio_app/features/create_recipe/services/recipe_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:eatvio_app/features/create_recipe/models/product.dart'; import 'package:eatvio_app/features/create_recipe/models/product.dart';
class RecipeRepository { class RecipeRepository {
@@ -16,12 +15,8 @@ class RecipeRepository {
RecipeRepository({required this.recipeService}); RecipeRepository({required this.recipeService});
Future<Map<String, List<Product>>> inputProduct(String name) async { Future<Map<String, List<Product>>> inputProduct(String name) async {
try { return await recipeService.getInputProduct(name);
return await recipeService.getInputProduct(name);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List> _buildStepsMap(List<Step?>? steps) async { Future<List> _buildStepsMap(List<Step?>? steps) async {
@@ -110,67 +105,43 @@ class RecipeRepository {
} }
Future<void> publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async { Future<void> publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async {
try { final formData = await _buildPublishFormData(recipe);
final formData = await _buildPublishFormData(recipe); if (update) {
if (update) { await recipeService.updateRecipe(slug: slug!, formData: formData);
await recipeService.updateRecipe(slug: slug!, formData: formData); } else {
} else { await recipeService.publishRecipe(formData: formData);
await recipeService.publishRecipe(formData: formData);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<void> saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async { Future<void> saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async {
try { final formData = await _buildDraftFormData(recipe);
final formData = await _buildDraftFormData(recipe); log(formData.fields.toString());
log(formData.fields.toString()); if (update) {
if (update) { await recipeService.updateRecipe(slug: slug!, formData: formData);
await recipeService.updateRecipe(slug: slug!, formData: formData); } else {
} else { await recipeService.createDraftedRecipe(formData: formData);
await recipeService.createDraftedRecipe(formData: formData);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<void> draftExistingRecipe(int id) async { Future<void> draftExistingRecipe(int id) async {
try { await recipeService.draftRecipe(id: id);
await recipeService.draftRecipe(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<RecipeModel> getRecipe(String recipeID) async { Future<RecipeModel> getRecipe(String recipeID) async {
try { return await recipeService.getRecipe(recipeID);
return await recipeService.getRecipe(recipeID);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<RecipeTag>> getRecipesTags() async { Future<List<RecipeTag>> getRecipesTags() async {
try { return (await recipeService.getTags()).data;
return (await recipeService.getTags()).data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deleteRecipe(String slug) async { Future<void> deleteRecipe(String slug) async {
try { return await recipeService.deleteRecipe(slug: slug);
return await recipeService.deleteRecipe(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -240,7 +240,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
} else { } else {
platform = 'ios'; platform = 'ios';
} }
await dio.post('/api/profile/notification/token/register', data: {'token': await FirebaseApi().getToken(), 'platform': platform}); await dio.post('/api/profile/notification/token/register', data: {'token': await sl<FirebaseApi>().getToken(), 'platform': platform});
showInfoToast('Токен добавлен'); showInfoToast('Токен добавлен');
} catch (e) { } catch (e) {
showInfoToast('Не удалось добавть токен'); showInfoToast('Не удалось добавть токен');
@@ -253,7 +253,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
buttonStyle: const ButtonStyle(), buttonStyle: const ButtonStyle(),
onPressed: () async { onPressed: () async {
try { try {
final token = await FirebaseApi().getToken(); final token = await sl<FirebaseApi>().getToken();
if (token != null) { if (token != null) {
Clipboard.setData(ClipboardData(text: token)); Clipboard.setData(ClipboardData(text: token));
showInfoToast('Токен скопирован'); showInfoToast('Токен скопирован');
@@ -278,7 +278,7 @@ class _DevToolsScreenContentState extends State<DevToolsScreenContent> {
} else { } else {
platform = 'ios'; platform = 'ios';
} }
await dio.post('/api/profile/notification/token/unregister', data: {'token': await FirebaseApi().getToken(), 'platform': platform}); await dio.post('/api/profile/notification/token/unregister', data: {'token': await sl<FirebaseApi>().getToken(), 'platform': platform});
showInfoToast('Токен удалён'); showInfoToast('Токен удалён');
} catch (e) { } catch (e) {
showInfoToast('Не удалось удалить токен'); showInfoToast('Не удалось удалить токен');

View File

@@ -36,7 +36,6 @@ import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart';
import 'package:eatvio_app/features/registration/services/registration_service.dart'; import 'package:eatvio_app/features/registration/services/registration_service.dart';
import 'package:eatvio_app/features/settings/services/settings_service.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart';
import 'package:eatvio_app/core/env_config.dart'; import 'package:eatvio_app/core/env_config.dart';
class TestResult { class TestResult {
final bool success; final bool success;
final String? error; final String? error;
@@ -57,7 +56,9 @@ class CreateRecipeTask implements TestCases {
Future<TestResult> run({required String baseUrl}) async { Future<TestResult> run({required String baseUrl}) async {
try { try {
final dio = sl<Dio>(); final dio = sl<Dio>();
final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl)); final recipeRepository = RecipeRepository(
recipeService: CreateRecipeService(dio, baseUrl: baseUrl),
);
await recipeRepository.publishRecipe( await recipeRepository.publishRecipe(
const RecipeModel( const RecipeModel(
title: 'Title', title: 'Title',

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart'; import 'package:eatvio_app/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_state.dart';
import 'package:eatvio_app/features/diary/repositories/diary_repository.dart'; import 'package:eatvio_app/features/diary/repositories/diary_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> { class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> {
final DiaryRepository diaryRepository; final DiaryRepository diaryRepository;
@@ -74,6 +75,7 @@ class ConsultantCommentsCubit extends Cubit<ConsultantCommentsState> {
} }
await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel); await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel);
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }

View File

@@ -19,8 +19,15 @@ import 'package:eatvio_app/features/glossary/repositories/glossary_repository.da
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:eatvio_app/features/settings/repositories/service_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class DiaryCubit extends Cubit<DiaryState> { class DiaryCubit extends Cubit<DiaryState> {
static const NutritionModel _zeroNutritionModel = NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0);
static const NutritionDataModel _fallbackNutrition = NutritionDataModel(
dailyNutritionIntake: _zeroNutritionModel,
dailyNutritionRecommendation: _zeroNutritionModel,
);
late final DateTime today; late final DateTime today;
late DateTime selectedDay; late DateTime selectedDay;
int selectedMeal = 1; int selectedMeal = 1;
@@ -102,6 +109,7 @@ class DiaryCubit extends Cubit<DiaryState> {
emit(currentState); emit(currentState);
return false; return false;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(DiaryState.error(errorMessage: e.toString())); emit(DiaryState.error(errorMessage: e.toString()));
return false; return false;
@@ -115,7 +123,8 @@ class DiaryCubit extends Cubit<DiaryState> {
if (diaryEntity != null) { if (diaryEntity != null) {
setDiary(diaryDay: diaryEntity, diaryId: diaryId); setDiary(diaryDay: diaryEntity, diaryId: diaryId);
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
} }
} }
@@ -126,6 +135,7 @@ class DiaryCubit extends Cubit<DiaryState> {
final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset()); final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset());
return diaryDay; return diaryDay;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
if (e is DioException) { if (e is DioException) {
if (e.response?.statusCode == 422) { if (e.response?.statusCode == 422) {
@@ -152,8 +162,9 @@ class DiaryCubit extends Cubit<DiaryState> {
return false; return false;
} }
emit(const DiaryState.loading()); emit(const DiaryState.loading());
if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) { try {
final diet = await dietRepository.getDiet((diaryDay.diet?.id)!); if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) {
final diet = await dietRepository.getDiet((diaryDay.diet?.id)!);
final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet]; final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet];
final firstMealPortions = meals?[1]?.values.map((elem) { final firstMealPortions = meals?[1]?.values.map((elem) {
return PortionModel( return PortionModel(
@@ -221,9 +232,14 @@ class DiaryCubit extends Cubit<DiaryState> {
selectedMeal: selectedMeal, selectedMeal: selectedMeal,
), ),
); );
return true; return true;
}
return false;
} catch (e, st) {
await reportFrom(e, st, this);
emit(DiaryState.error(errorMessage: e.toString()));
return false;
} }
return false;
} }
Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async { Future<void> setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async {
@@ -279,6 +295,7 @@ class DiaryCubit extends Cubit<DiaryState> {
), ),
); );
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(DiaryState.error(errorMessage: e.toString())); emit(DiaryState.error(errorMessage: e.toString()));
} }
@@ -290,13 +307,19 @@ class DiaryCubit extends Cubit<DiaryState> {
await updateCurrentDiary(); await updateCurrentDiary();
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
} }
Future<NutritionDataModel> getNutrition(int index) async { Future<NutritionDataModel> getNutrition(int index) async {
return (await diaryRepository.getNutritionData(index)); try {
return (await diaryRepository.getNutritionData(index));
} catch (e, st) {
await reportFrom(e, st, this);
return _fallbackNutrition;
}
} }
Future<void> addProductToMeal({ Future<void> addProductToMeal({
@@ -308,15 +331,23 @@ class DiaryCubit extends Cubit<DiaryState> {
int? productNutritionalValueId, int? productNutritionalValueId,
}) async { }) async {
emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true)); emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true));
await diaryRepository.addPortionToMeal( try {
diaryId: diaryId, await diaryRepository.addPortionToMeal(
mealId: mealId, diaryId: diaryId,
productName: productName, mealId: mealId,
portion: portion, productName: productName,
quantity: quantity, portion: portion,
productNutritionalValueId: productNutritionalValueId, quantity: quantity,
); productNutritionalValueId: productNutritionalValueId,
await updateCurrentDiary(); );
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
final s = state;
if (s is DiaryStateLoaded) {
emit(s.copyWith(isLoadingAddingProductInMeal: false));
}
}
} }
Future<void> updateProductInMeal({ Future<void> updateProductInMeal({
@@ -329,62 +360,95 @@ class DiaryCubit extends Cubit<DiaryState> {
int? productNutritionalValueId, int? productNutritionalValueId,
required PortionState portionState, required PortionState portionState,
}) async { }) async {
if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) { try {
await diaryRepository.addPortionToMeal( if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) {
diaryId: diaryId, await diaryRepository.addPortionToMeal(
mealId: mealId, diaryId: diaryId,
productName: productName, mealId: mealId,
portion: portion, productName: productName,
quantity: quantity, portion: portion,
productNutritionalValueId: productNutritionalValueId, quantity: quantity,
); productNutritionalValueId: productNutritionalValueId,
} else { );
await diaryRepository.updateProductInMeal( } else {
diaryId: diaryId, await diaryRepository.updateProductInMeal(
mealId: mealId, diaryId: diaryId,
portionId: portionId, mealId: mealId,
productName: productName, portionId: portionId,
portion: portion, productName: productName,
quantity: quantity, portion: portion,
productNutritionalValueId: productNutritionalValueId, quantity: quantity,
portionState: portionState, productNutritionalValueId: productNutritionalValueId,
); portionState: portionState,
);
}
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
} }
await updateCurrentDiary();
} }
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async { Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); try {
await updateCurrentDiary(); await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); try {
await updateCurrentDiary(); await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async { Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId); try {
await updateCurrentDiary(); await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId); try {
await updateCurrentDiary(); await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
return await glossaryRepository.getGlossaryListByQuery(query); try {
return await glossaryRepository.getGlossaryListByQuery(query);
} catch (e, st) {
await reportFrom(e, st, this);
return [];
}
} }
Future<void> uploadPhotos({required List<File> files, required int diaryId}) async { Future<void> uploadPhotos({required List<File> files, required int diaryId}) async {
await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal); try {
await updateCurrentDiary(); await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async { Future<void> deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async {
await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal); try {
await updateCurrentDiary(); await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<GlossaryModerationModel?> sendProductToModeration({ Future<GlossaryModerationModel?> sendProductToModeration({
@@ -397,34 +461,55 @@ class DiaryCubit extends Cubit<DiaryState> {
try { try {
return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates); return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates);
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log(e.toString(), stackTrace: stackTrace); log(e.toString(), stackTrace: stackTrace);
} }
return null; return null;
} }
Future<void> fillTime({required int diaryId, required String time}) async { Future<void> fillTime({required int diaryId, required String time}) async {
await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time); try {
await updateCurrentDiary(); await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> fillWakeup({required int diaryId, required String time}) async { Future<void> fillWakeup({required int diaryId, required String time}) async {
await diaryRepository.fillWakeup(diaryId: diaryId, time: time); try {
await updateCurrentDiary(); await diaryRepository.fillWakeup(diaryId: diaryId, time: time);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> fillRetire({required int diaryId, required String time}) async { Future<void> fillRetire({required int diaryId, required String time}) async {
await diaryRepository.fillRetire(diaryId: diaryId, time: time); try {
await updateCurrentDiary(); await diaryRepository.fillRetire(diaryId: diaryId, time: time);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> fillSatiety({required int diaryId, required int satiety}) async { Future<void> fillSatiety({required int diaryId, required int satiety}) async {
await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); try {
await updateCurrentDiary(); await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> fillMood({required int diaryId, required int mood}) async { Future<void> fillMood({required int diaryId, required int mood}) async {
await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); try {
await updateCurrentDiary(); await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood);
await updateCurrentDiary();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
void fillComment(String comment) { void fillComment(String comment) {

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart';
import 'package:collection/collection.dart';
import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_comment_model.dart';
import 'package:eatvio_app/features/diary/models/consultant_review_model.dart'; import 'package:eatvio_app/features/diary/models/consultant_review_model.dart';
import 'package:eatvio_app/features/diary/models/create_day_model.dart'; import 'package:eatvio_app/features/diary/models/create_day_model.dart';
@@ -10,7 +11,6 @@ import 'package:eatvio_app/features/diary/models/nutrition_model.dart';
import 'package:eatvio_app/features/diary/models/product_moderation_model.dart'; import 'package:eatvio_app/features/diary/models/product_moderation_model.dart';
import 'package:eatvio_app/features/diary/services/diary_service.dart'; import 'package:eatvio_app/features/diary/services/diary_service.dart';
import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class DiaryRepository { class DiaryRepository {
final DiaryService diaryService; final DiaryService diaryService;
@@ -20,134 +20,82 @@ class DiaryRepository {
DiaryRepository({required this.diaryService}); DiaryRepository({required this.diaryService});
Future<DiaryDayModel> getDiaryByDate(String date) async { PortionModel _portionWithGlossary(PortionModel portion, List<GlossaryModel> glossaries) {
try { final id = portion.productNutritionalValueId;
Set<int> ids = {}; if (id == null) return portion;
final diaryDays = await getDiaryIndexByDate(date); final glossaryItem = glossaries.firstWhereOrNull((g) => g.id == id);
if (diaryDays.isEmpty) { if (glossaryItem == null) return portion;
throw Exception('No diary entry found for $date'); return portion.copyWith(
} productCaloriesInfo: ProductCaloriesInfoModel(
final DiaryDayModel diaryDay = diaryDays.last; id: glossaryItem.id,
for (var meal in diaryDay.meals) { productName: glossaryItem.name,
for (var portion in meal.portions) { calories: glossaryItem.energyOn100Gr,
if (portion.productNutritionalValueId != null) { proteins: glossaryItem.proteinsOn100Gr,
ids.add(portion.productNutritionalValueId!); fats: glossaryItem.fatsOn100Gr,
} carbohydrates: glossaryItem.carbohydratesOn100Gr,
} energyOnPortion: glossaryItem.energyOnPortion,
} proteinsOnPortion: glossaryItem.proteinsOnPortion,
if (ids.isNotEmpty) { fatsOnPortion: glossaryItem.fatsOnPortion,
final glossary = await getGlossariesIndex(ids: ids.toList()); carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
final List<MealModel> updatedMeals = List.from(diaryDay.meals); ),
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) { );
final meal = updatedMeals[mealIndex]; }
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
}
}
return portion;
}).toList();
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions); Future<DiaryDayModel> getDiaryByDate(String date) async {
} final ids = <int>{};
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals); final diaryDays = await getDiaryIndexByDate(date);
return updatedDiaryDay; if (diaryDays.isEmpty) {
} throw Exception('No diary entry found for $date');
return diaryDay;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
final DiaryDayModel diaryDay = diaryDays.last;
for (var meal in diaryDay.meals) {
for (var portion in meal.portions) {
if (portion.productNutritionalValueId != null) {
ids.add(portion.productNutritionalValueId!);
}
}
}
if (ids.isEmpty) {
return diaryDay;
}
final glossary = await getGlossariesIndex(ids: ids.toList());
final glossaries = glossary.glossariesList;
final updatedMeals = diaryDay.meals.map((meal) {
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
return meal.copyWith(portions: updatedPortions);
}).toList();
return diaryDay.copyWith(meals: updatedMeals);
} }
Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async { Future<List<DiaryDayModel>> getDiaryIndexByDate(String date) async {
try { return diaryService.getDiaryIndexByDate(date: date);
return (await diaryService.getDiaryIndexByDate(date: date));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<DiaryDayModel> getDiaryDay(int diaryId) async { Future<DiaryDayModel> getDiaryDay(int diaryId) async {
try { final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId);
// Align with getDiaryByDate: enrich portions with glossary nutritional info final DiaryDayModel diaryDay = diaryWrapper.diary;
final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId); final ids = <int>{};
final DiaryDayModel diaryDay = diaryWrapper.diary; for (var meal in diaryDay.meals) {
Set<int> ids = {}; for (var portion in meal.portions) {
for (var meal in diaryDay.meals) { if (portion.productNutritionalValueId != null) {
for (var portion in meal.portions) { ids.add(portion.productNutritionalValueId!);
if (portion.productNutritionalValueId != null) {
ids.add(portion.productNutritionalValueId!);
}
} }
} }
if (ids.isNotEmpty) {
final glossary = await getGlossariesIndex(ids: ids.toList());
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
final meal = updatedMeals[mealIndex];
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
}
}
return portion;
}).toList();
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
}
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
return updatedDiaryDay;
}
return diaryDay;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
if (ids.isEmpty) {
return diaryDay;
}
final glossary = await getGlossariesIndex(ids: ids.toList());
final glossaries = glossary.glossariesList;
final updatedMeals = diaryDay.meals.map((meal) {
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
return meal.copyWith(portions: updatedPortions);
}).toList();
return diaryDay.copyWith(meals: updatedMeals);
} }
Future<NutritionDataModel> getNutritionData(int index) async { Future<NutritionDataModel> getNutritionData(int index) async {
try { return diaryService.getNutrition(diary: index);
return await diaryService.getNutrition(diary: index);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> addPortionToMeal({ Future<void> addPortionToMeal({
@@ -158,19 +106,14 @@ class DiaryRepository {
required int quantity, required int quantity,
int? productNutritionalValueId, int? productNutritionalValueId,
}) async { }) async {
try { return diaryService.addProductToMeal(
return await diaryService.addProductToMeal( diary: diaryId,
diary: diaryId, typeNo: mealId,
typeNo: mealId, productName: productName,
productName: productName, portion: portion,
portion: portion, quantity: quantity,
quantity: quantity, productNutritionalValueId: productNutritionalValueId,
productNutritionalValueId: productNutritionalValueId, );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> updateProductInMeal({ Future<void> updateProductInMeal({
@@ -183,83 +126,43 @@ class DiaryRepository {
int? productNutritionalValueId, int? productNutritionalValueId,
required PortionState portionState, required PortionState portionState,
}) async { }) async {
try { final data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson();
Map<String, Object?> data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson(); if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId;
if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId; data['state'] = portionState.name;
data['state'] = portionState.name; return diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async { Future<void> markEaten({required int diaryId, required int mealId, required int portionId}) async {
try { return diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { Future<void> unmarkEaten({required int diaryId, required int mealId, required int portionId}) async {
try { return diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> restore({required int diaryId, required int mealId, required int portionId}) async { Future<void> restore({required int diaryId, required int mealId, required int portionId}) async {
try { return diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { Future<void> deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async {
try { return diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async { Future<void> uploadPhotos({required int diaryId, required int mealId, required List<File> files}) async {
try { for (var i = 0; i < files.length; i++) {
for (int i = 0; i < files.length; i++) { await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]);
}
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
} }
} }
Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async { Future<void> deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async {
try { await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async { Future<void> finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async {
try { return diaryService.finishDiaryDay(
return await diaryService.finishDiaryDay( diary: diaryId,
diary: diaryId, finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment),
finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment), );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<GlossaryModerationModel> sendProductToModeration({ Future<GlossaryModerationModel> sendProductToModeration({
@@ -269,129 +172,66 @@ class DiaryRepository {
required double fats, required double fats,
required double carbohydrates, required double carbohydrates,
}) async { }) async {
try { return diaryService.sendProductToModeration(
return await diaryService.sendProductToModeration( data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(),
data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(), );
);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async { Future<void> fillSatiety({required int diaryId, required int mealId, required int satiety}) async {
try { return diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
return await diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async { Future<void> fillMood({required int diaryId, required int mealId, required int mood}) async {
try { return diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
return await diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> fillTime({required int diaryId, required int mealId, required String time}) async { Future<void> fillTime({required int diaryId, required int mealId, required String time}) async {
try { return diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
return await diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> fillWakeup({required int diaryId, required String time}) async { Future<void> fillWakeup({required int diaryId, required String time}) async {
try { return diaryService.fillWakeup(diary: diaryId, wakeUp: time);
return await diaryService.fillWakeup(diary: diaryId, wakeUp: time);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> fillRetire({required int diaryId, required String time}) async { Future<void> fillRetire({required int diaryId, required String time}) async {
try { return diaryService.fillRetire(diary: diaryId, retire: time);
return await diaryService.fillRetire(diary: diaryId, retire: time);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async { Future<void> commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async {
try { await diaryService.commentUserDay(id: diaryId, body: commentModel);
await diaryService.commentUserDay(id: diaryId, body: commentModel); _diaryForceFetchController.add('force');
_diaryForceFetchController.add('force');
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async { Future<ConsultantReviewModel> getDiaryComments({required int planId, required int diaryId}) async {
try { return diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async { Future<void> viewCommentOfDiaryDayByConsultant({required int diaryId}) async {
try { await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<GlossaryPaginationModel> getGlossariesIndex({required List<int> ids}) async { Future<GlossaryPaginationModel> getGlossariesIndex({required List<int> ids}) async {
Map<String, dynamic> filters = {}; final filters = <String, dynamic>{};
if (ids.isNotEmpty) { if (ids.isNotEmpty) {
for (int i = 0; i < ids.length; i++) { for (var i = 0; i < ids.length; i++) {
filters['filters[id][\$in][$i]'] = ids[i]; filters['filters[id][\$in][$i]'] = ids[i];
} }
} }
try { return diaryService.getGlossariesIndex(filters: filters);
return await diaryService.getGlossariesIndex(filters: filters);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<DiaryDayModel> getDiaryDayV2({required String day}) async { Future<DiaryDayModel> getDiaryDayV2({required String day}) async {
try { final diaryDay = await diaryService.getDiaryDayV2(day: day);
final diaryDay = await diaryService.getDiaryDayV2(day: day); return setCPFCToDiary(diaryDay);
return setCPFCToDiary(diaryDay);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> createDiaryDay({required String day}) async { Future<void> createDiaryDay({required String day}) async {
try { await diaryService.createDay(body: CreateDayModel(day: day));
await diaryService.createDay(body: CreateDayModel(day: day)); await getDiaryByDate(day);
await getDiaryByDate(day);
} catch (e, s) {
if (e is DioException && e.response?.statusCode == 422) {
} else {
await Sentry.captureException(e, stackTrace: s);
}
rethrow;
}
} }
Future<DiaryDayModel> setCPFCToDiary(DiaryDayModel diaryDay) async { Future<DiaryDayModel> setCPFCToDiary(DiaryDayModel diaryDay) async {
Set<int> ids = {}; final ids = <int>{};
for (var meal in diaryDay.meals) { for (var meal in diaryDay.meals) {
for (var portion in meal.portions) { for (var portion in meal.portions) {
if (portion.productNutritionalValueId != null) { if (portion.productNutritionalValueId != null) {
@@ -399,41 +239,15 @@ class DiaryRepository {
} }
} }
} }
if (ids.isNotEmpty) { if (ids.isEmpty) {
final glossary = await getGlossariesIndex(ids: ids.toList()); return diaryDay;
final List<MealModel> updatedMeals = List.from(diaryDay.meals);
for (var mealIndex = 0; mealIndex < updatedMeals.length; mealIndex++) {
final meal = updatedMeals[mealIndex];
final updatedPortions = meal.portions.map((portion) {
if (portion.productNutritionalValueId != null) {
try {
final glossaryItem = glossary.glossariesList.firstWhere((g) => g.id == portion.productNutritionalValueId);
return portion.copyWith(
productCaloriesInfo: ProductCaloriesInfoModel(
id: glossaryItem.id,
productName: glossaryItem.name,
calories: glossaryItem.energyOn100Gr,
proteins: glossaryItem.proteinsOn100Gr,
fats: glossaryItem.fatsOn100Gr,
carbohydrates: glossaryItem.carbohydratesOn100Gr,
energyOnPortion: glossaryItem.energyOnPortion,
proteinsOnPortion: glossaryItem.proteinsOnPortion,
fatsOnPortion: glossaryItem.fatsOnPortion,
carbohydratesOnPortion: glossaryItem.carbohydratesOnPortion,
),
);
} catch (e) {
return portion;
}
}
return portion;
}).toList();
updatedMeals[mealIndex] = meal.copyWith(portions: updatedPortions);
}
final updatedDiaryDay = diaryDay.copyWith(meals: updatedMeals);
return updatedDiaryDay;
} }
return diaryDay; final glossary = await getGlossariesIndex(ids: ids.toList());
final glossaries = glossary.glossariesList;
final updatedMeals = diaryDay.meals.map((meal) {
final updatedPortions = meal.portions.map((p) => _portionWithGlossary(p, glossaries)).toList();
return meal.copyWith(portions: updatedPortions);
}).toList();
return diaryDay.copyWith(meals: updatedMeals);
} }
} }

View File

@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/diets/presentation/cubit/about_diet_state.da
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class AboutDietCubit extends Cubit<AboutDietState> { class AboutDietCubit extends Cubit<AboutDietState> {
AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(const AboutDietState.initial()); AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(const AboutDietState.initial());
@@ -31,7 +32,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments)); dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments));
} }
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit(AboutDietState.error(message: e.toString())); emit(AboutDietState.error(message: e.toString()));
} }
@@ -62,7 +64,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
} }
_selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0); _selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0);
emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes)); emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit(AboutDietState.error(message: e.toString())); emit(AboutDietState.error(message: e.toString()));
} }
@@ -90,7 +93,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async { EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async {
try { try {
await dietsRepository.likeDiet(id); await dietsRepository.likeDiet(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
if (dietIndexInFeed != null) { if (dietIndexInFeed != null) {
dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed)); dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed));
@@ -114,7 +118,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async { EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
try { try {
await dietsRepository.addToFavorite(id); await dietsRepository.addToFavorite(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
if (dietIndex != null) { if (dietIndex != null) {
@@ -139,7 +144,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async { EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async {
try { try {
await dietsRepository.removeFromFavorite(id); await dietsRepository.removeFromFavorite(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
if (dietIndex != null) { if (dietIndex != null) {
@@ -159,6 +165,7 @@ class AboutDietCubit extends Cubit<AboutDietState> {
await dietsRepository.startDiet(id, stringDate); await dietsRepository.startDiet(id, stringDate);
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -170,10 +177,12 @@ class AboutDietCubit extends Cubit<AboutDietState> {
final id = (state as DietStateLoaded).diet.id; final id = (state as DietStateLoaded).diet.id;
await dietsRepository.cancelDiet(id); await dietsRepository.cancelDiet(id);
return true; return true;
} on DioException catch (e) { } on DioException catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
return false; return false;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
return false; return false;
} }
@@ -194,7 +203,8 @@ class AboutDietCubit extends Cubit<AboutDietState> {
Future<Diet?> getDietBySlug(String slug) async { Future<Diet?> getDietBySlug(String slug) async {
try { try {
return await dietsRepository.getDietBySlug(slug); return await dietsRepository.getDietBySlug(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
} }
return null; return null;

View File

@@ -4,7 +4,6 @@ import 'package:eatvio_app/features/diets/models/diet_likes_model.dart';
import 'package:eatvio_app/features/diets/services/diet_service.dart'; import 'package:eatvio_app/features/diets/services/diet_service.dart';
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class DietRepository { class DietRepository {
DietRepository({required this.dietsService}); DietRepository({required this.dietsService});
@@ -18,76 +17,44 @@ class DietRepository {
Stream get dietCommentsState => _dietCommentsStateStreamController.stream; Stream get dietCommentsState => _dietCommentsStateStreamController.stream;
Future<DietLikesModel> likeDiet(int id) async { Future<DietLikesModel> likeDiet(int id) async {
try { return await dietsService.likeDiet(id: id);
return await dietsService.likeDiet(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<Diet> getDiet(int id) async { Future<Diet> getDiet(int id) async {
try { final diet = await dietsService.getDiet(id: id);
final diet = await dietsService.getDiet(id: id); return diet;
return diet;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<Diet> getDietBySlug(String slug) async { Future<Diet> getDietBySlug(String slug) async {
try { final diet = await dietsService.getDietBySlug(slug: slug);
final diet = await dietsService.getDietBySlug(slug: slug); return diet;
return diet;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<Diet> addToFavorite(int id) async { Future<Diet> addToFavorite(int id) async {
try { return await dietsService.addToFavorite(id: id);
return await dietsService.addToFavorite(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<Diet> removeFromFavorite(int id) async { Future<Diet> removeFromFavorite(int id) async {
try { return await dietsService.removeFromFavorite(id: id);
return await dietsService.removeFromFavorite(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> startDiet(int id, String date) async { Future<void> startDiet(int id, String date) async {
try { return await dietsService.startDiet(id: id, body: {'date': date});
return await dietsService.startDiet(id: id, body: {'date': date});
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> cancelDiet(int id) async{ Future<void> cancelDiet(int id) async{
try { return await dietsService.cancelDiet(id: id);
return await dietsService.cancelDiet(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deleteDiet(int id) async { Future<void> deleteDiet(int id) async {
try { return dietsService.deleteDiet(id: id);
return await dietsService.deleteDiet(id: id);
} catch (e) {
rethrow;
}
} }
void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state); void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state);

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/consultants_cubit/consultants_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultantsCubit extends Cubit<ConsultantsState> { class ConsultantsCubit extends Cubit<ConsultantsState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
@@ -21,6 +22,7 @@ class ConsultantsCubit extends Cubit<ConsultantsState> {
), ),
); );
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(ConsultantsState.error(errorMessage: e.toString())); emit(ConsultantsState.error(errorMessage: e.toString()));
} }

View File

@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class DietsCubit extends Cubit<DietsState> { class DietsCubit extends Cubit<DietsState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
@@ -109,6 +110,7 @@ class DietsCubit extends Cubit<DietsState> {
), ),
); );
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit( emit(
state.copyWith(isLoading: false, errorMessage: e.toString()), state.copyWith(isLoading: false, errorMessage: e.toString()),
@@ -144,7 +146,8 @@ class DietsCubit extends Cubit<DietsState> {
), ),
); );
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(isLoading: false, errorMessage: e.toString()), state.copyWith(isLoading: false, errorMessage: e.toString()),
); );
@@ -191,7 +194,8 @@ class DietsCubit extends Cubit<DietsState> {
() async { () async {
try { try {
await dietsRepository.likeDiet(id); await dietsRepository.likeDiet(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit( emit(
state.copyWith( state.copyWith(
@@ -287,7 +291,8 @@ class DietsCubit extends Cubit<DietsState> {
() async { () async {
try { try {
await dietsRepository.addToFavorite(id); await dietsRepository.addToFavorite(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -327,7 +332,8 @@ class DietsCubit extends Cubit<DietsState> {
() async { () async {
try { try {
await dietsRepository.removeFromFavorite(id); await dietsRepository.removeFromFavorite(id);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -348,7 +354,8 @@ class DietsCubit extends Cubit<DietsState> {
try { try {
await dietsRepository.deleteDiet(dietId); await dietsRepository.deleteDiet(dietId);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
return false; return false;
} }
} }

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_state.dart';
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart'; import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class FeedCubit extends Cubit<FeedState> { class FeedCubit extends Cubit<FeedState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
@@ -26,6 +27,17 @@ class FeedCubit extends Cubit<FeedState> {
} }
} }
void incrementCommentsState(int id) {
final List<FeedBlogPostModel> feedBlogPostList = [...(state as FeedStateLoaded).feedBlogPostList];
final int index = feedBlogPostList.indexWhere((e) => e.id == id);
if (index == -1) return;
final current = feedBlogPostList[index];
feedBlogPostList[index] = current.copyWith(totalComments: current.totalComments + 1);
if (!isClosed) {
emit(FeedState.loaded(feedBlogPostList: feedBlogPostList, isLoading: isLoading));
}
}
Future<void> loadFeedBlogPostList({String? query}) async { Future<void> loadFeedBlogPostList({String? query}) async {
try { try {
_query = query; _query = query;
@@ -34,7 +46,8 @@ class FeedCubit extends Cubit<FeedState> {
if (!isClosed) { if (!isClosed) {
emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false)); emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false));
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
if (!isClosed) { if (!isClosed) {
emit(FeedState.error(errorMessage: e.toString())); emit(FeedState.error(errorMessage: e.toString()));
} }
@@ -58,7 +71,8 @@ class FeedCubit extends Cubit<FeedState> {
emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading)); emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading));
} }
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
if (!isClosed) { if (!isClosed) {
emit(FeedState.error(errorMessage: e.toString())); emit(FeedState.error(errorMessage: e.toString()));
} }
@@ -70,14 +84,48 @@ class FeedCubit extends Cubit<FeedState> {
try { try {
await feedRepository.likeBlogPost(id: id); await feedRepository.likeBlogPost(id: id);
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }
Future<void> unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id); Future<void> unlikedPost(int id) async {
try {
await feedRepository.unlikedBlogPost(id: id);
} catch (e, st) {
await reportFrom(e, st, this);
}
}
Future<void> viewPost(int id) async => await feedRepository.viewPost(id); Future<void> viewPost(int id) async {
Future<void> viewRecipe(String slug) async => await feedRepository.viewRecipe(slug); try {
Future<void> viewDiet(int id) async => await feedRepository.viewDiet(id); await feedRepository.viewPost(id);
Future<void> viewPlan(String slug) async => await feedRepository.viewPlan(slug); } catch (e, st) {
await reportFrom(e, st, this);
}
}
Future<void> viewRecipe(String slug) async {
try {
await feedRepository.viewRecipe(slug);
} catch (e, st) {
await reportFrom(e, st, this);
}
}
Future<void> viewDiet(int id) async {
try {
await feedRepository.viewDiet(id);
} catch (e, st) {
await reportFrom(e, st, this);
}
}
Future<void> viewPlan(String slug) async {
try {
await feedRepository.viewPlan(slug);
} catch (e, st) {
await reportFrom(e, st, this);
}
}
} }

View File

@@ -10,6 +10,7 @@ import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
import 'package:eatvio_app/features/plans/repositories/plan_repository.dart'; import 'package:eatvio_app/features/plans/repositories/plan_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class PlansCubit extends Cubit<PlansState> { class PlansCubit extends Cubit<PlansState> {
final FeedRepository feedRepository; final FeedRepository feedRepository;
@@ -41,6 +42,17 @@ class PlansCubit extends Cubit<PlansState> {
}); });
} }
@override
Future<void> close() async {
EasyDebounce.cancel('loadPlans');
EasyDebounce.cancel('add_or_remove_plan_from_favorite');
EasyDebounce.cancel('like_plan');
likesStateSubscription.cancel();
favoriteStateSubscription.cancel();
commentsStateSubscription.cancel();
await super.close();
}
void changeDurationFilter(int index) { void changeDurationFilter(int index) {
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null)); emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
EasyDebounce.debounce( EasyDebounce.debounce(
@@ -168,6 +180,7 @@ class PlansCubit extends Cubit<PlansState> {
), ),
); );
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log(e.toString()); log(e.toString());
log(stackTrace.toString()); log(stackTrace.toString());
emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString())); emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString()));
@@ -199,7 +212,8 @@ class PlansCubit extends Cubit<PlansState> {
), ),
); );
} }
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(errorMessage: e.toString())); emit(state.copyWith(errorMessage: e.toString()));
} }
} }
@@ -228,7 +242,8 @@ class PlansCubit extends Cubit<PlansState> {
() async { () async {
try { try {
await planRepository.addToFavorite(slug); await planRepository.addToFavorite(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -268,7 +283,8 @@ class PlansCubit extends Cubit<PlansState> {
() async { () async {
try { try {
await planRepository.removeFromFavorite(slug); await planRepository.removeFromFavorite(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -324,7 +340,8 @@ class PlansCubit extends Cubit<PlansState> {
() async { () async {
try { try {
await planRepository.likePlan(slug); await planRepository.likePlan(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit( emit(
state.copyWith( state.copyWith(

View File

@@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/profile_cubit/profile_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class FeedProfileCubit extends Cubit<ProfileState> { class FeedProfileCubit extends Cubit<ProfileState> {
final ProfileRepository profileRepository; final ProfileRepository profileRepository;
@@ -13,6 +14,7 @@ class FeedProfileCubit extends Cubit<ProfileState> {
final profile = await profileRepository.getMyProfile(); final profile = await profileRepository.getMyProfile();
emit(state.copyWith(profile: profile)); emit(state.copyWith(profile: profile));
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log(e.toString(), stackTrace: stackTrace); log(e.toString(), stackTrace: stackTrace);
} }
} }

View File

@@ -9,6 +9,7 @@ import 'package:eatvio_app/features/create_recipe/models/recipe_tag_model.dart';
import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart'; import 'package:eatvio_app/features/create_recipe/reporitories/recipe_repository.dart';
import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart'; import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'dart:developer'; import 'dart:developer';
class RecipesCubit extends Cubit<RecipesState> { class RecipesCubit extends Cubit<RecipesState> {
@@ -62,6 +63,7 @@ class RecipesCubit extends Cubit<RecipesState> {
); );
lastPage = currentPage; lastPage = currentPage;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(errorMessage: e.toString(), isLoading: false)); emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
} }
@@ -93,7 +95,8 @@ class RecipesCubit extends Cubit<RecipesState> {
); );
} }
lastPage = currentPage; lastPage = currentPage;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(errorMessage: e.toString(), isLoading: false)); emit(state.copyWith(errorMessage: e.toString(), isLoading: false));
} }
} }
@@ -132,7 +135,8 @@ class RecipesCubit extends Cubit<RecipesState> {
() async { () async {
try { try {
await aboutRecipeRepository.addToFavorite(slug); await aboutRecipeRepository.addToFavorite(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -172,7 +176,8 @@ class RecipesCubit extends Cubit<RecipesState> {
() async { () async {
try { try {
await aboutRecipeRepository.removeFromFavorite(slug); await aboutRecipeRepository.removeFromFavorite(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время');
emit( emit(
@@ -228,7 +233,8 @@ class RecipesCubit extends Cubit<RecipesState> {
() async { () async {
try { try {
await aboutRecipeRepository.likeRecipe(slug); await aboutRecipeRepository.likeRecipe(slug);
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit( emit(
state.copyWith( state.copyWith(
@@ -305,7 +311,8 @@ class RecipesCubit extends Cubit<RecipesState> {
try { try {
await aboutRecipeRepository.deleteRecipe(slug); await aboutRecipeRepository.deleteRecipe(slug);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
return false; return false;
} }
} }

View File

@@ -32,8 +32,8 @@ import 'package:go_router/go_router.dart';
import '../widgets/recipes.dart'; import '../widgets/recipes.dart';
class FeedScreen extends StatelessWidget { class FeedScreen extends StatelessWidget {
const FeedScreen({super.key}); const FeedScreen({super.key, this.initialTabIndex = 0});
final int? initialTabIndex;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@@ -44,14 +44,14 @@ class FeedScreen extends StatelessWidget {
if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()), if (!hidePlans) BlocProvider<PlansCubit>(create: (context) => sl<PlansCubit>()..loadPlans()),
BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()), BlocProvider<ConsultantsCubit>(create: (context) => sl<ConsultantsCubit>()..getConsultants()),
], ],
child: const FeedScreenContent(), child: FeedScreenContent(initialTabIndex: initialTabIndex),
); );
} }
} }
class FeedScreenContent extends StatefulWidget { class FeedScreenContent extends StatefulWidget {
const FeedScreenContent({super.key}); const FeedScreenContent({super.key, required this.initialTabIndex});
final int? initialTabIndex;
@override @override
State<FeedScreenContent> createState() => _FeedScreenContentState(); State<FeedScreenContent> createState() => _FeedScreenContentState();
} }
@@ -64,7 +64,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final consultantsController = TextEditingController(); final consultantsController = TextEditingController();
late final List<String> chipsLabelList; late final List<String> chipsLabelList;
int _selectedChipIndex = 0; late int _selectedChipIndex;
final blogPostsScrollController = ScrollController(); final blogPostsScrollController = ScrollController();
final recipesScrollController = ScrollController(); final recipesScrollController = ScrollController();
final dietsScrollController = ScrollController(); final dietsScrollController = ScrollController();
@@ -72,6 +72,7 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
final consultantsScrollController = ScrollController(); final consultantsScrollController = ScrollController();
@override @override
void initState() { void initState() {
_selectedChipIndex = widget.initialTabIndex ?? 0;
chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты']; chipsLabelList = hidePlans ? ['Все', 'Консультанты', 'Рецепты', 'Диеты'] : ['Все', 'Планы питания', 'Консультанты', 'Рецепты', 'Диеты'];
consultantsScrollController.addListener(_onScrollConsultants); consultantsScrollController.addListener(_onScrollConsultants);
_checkRegistrationDate(context); _checkRegistrationDate(context);
@@ -88,6 +89,17 @@ class _FeedScreenContentState extends State<FeedScreenContent> {
super.initState(); super.initState();
} }
@override
void didUpdateWidget(FeedScreenContent oldWidget) {
super.didUpdateWidget(oldWidget);
final initialTabIndex = widget.initialTabIndex;
setState(() {
if (initialTabIndex != null) {
_selectedChipIndex = initialTabIndex;
}
});
}
void _onScrollConsultants() { void _onScrollConsultants() {
if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {} if (consultantsScrollController.offset >= consultantsScrollController.position.maxScrollExtent && !consultantsScrollController.position.outOfRange) {}
} }

View File

@@ -7,7 +7,6 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart';
import 'package:eatvio_app/features/feed/models/view_model.dart'; import 'package:eatvio_app/features/feed/models/view_model.dart';
import 'package:eatvio_app/features/feed/services/feed_service.dart'; import 'package:eatvio_app/features/feed/services/feed_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class FeedRepository { class FeedRepository {
final FeedService feedService; final FeedService feedService;
@@ -15,191 +14,107 @@ class FeedRepository {
FeedRepository({required this.feedService}); FeedRepository({required this.feedService});
Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async { Future<FeedBlogPostSettingsModel> getFeedBlogPosts({String? query, int page = 1}) async {
try { return (await feedService.getBlogPostList(page: page, query: query));
return (await feedService.getBlogPostList(page: page, query: query));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<FeedBlogPostModel> getBlogPost({required int id}) async { Future<FeedBlogPostModel> getBlogPost({required int id}) async {
try { return await feedService.getBlogPost(postId: id);
return await feedService.getBlogPost(postId: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async { Future<List<Recipe>> getRecipes({String? query, List<int>? tags, int page = 1}) async {
try { return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async { Future<DietsPagination> getDiets({int page = 1, String? query, String? types, int? duration, String? sorting}) async {
try { return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async { Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
try { return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<CommentModel> getBlogPostComments({required String id}) async { Future<CommentModel> getBlogPostComments({required String id}) async {
try { return (await feedService.getBlogPostComments(id: id));
return (await feedService.getBlogPostComments(id: id));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async { Future<void> sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async {
try { await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<CommentModel> getDietsComments({required String id}) async { Future<CommentModel> getDietsComments({required String id}) async {
try { return (await feedService.getDietsComments(id: id));
return (await feedService.getDietsComments(id: id));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async { Future<void> sendDietsComment({required String id, required String commentText, int? parentCommentId}) async {
try { await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<CommentModel> getRecipesComments({required String id}) async { Future<CommentModel> getRecipesComments({required String id}) async {
try { return (await feedService.getRecipesComments(id: id));
return (await feedService.getRecipesComments(id: id));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async { Future<void> sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async {
try { await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<CommentModel> getPlansComments({required String slug}) async { Future<CommentModel> getPlansComments({required String slug}) async {
try { return (await feedService.getPlansComments(slug: slug));
return (await feedService.getPlansComments(slug: slug));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async { Future<void> sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async {
try { await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> likeBlogPost({required int id}) async { Future<void> likeBlogPost({required int id}) async {
try { await feedService.likeBlogPost(id: id);
await feedService.likeBlogPost(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> unlikedBlogPost({required int id}) async { Future<void> unlikedBlogPost({required int id}) async {
try { await feedService.unlikedBlogPost(id: id);
await feedService.unlikedBlogPost(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<FeedConsultantModel>> getConsultants([String? name]) async { Future<List<FeedConsultantModel>> getConsultants([String? name]) async {
try { return (await feedService.getConsultants(name: name)).data;
return (await feedService.getConsultants(name: name)).data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewPost(int id) async { Future<void> viewPost(int id) async {
try { return (await feedService.viewPost(id: id));
return (await feedService.viewPost(id: id));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewRecipe(String slug) async { Future<void> viewRecipe(String slug) async {
try { return (await feedService.viewRecipe(slug: slug));
return (await feedService.viewRecipe(slug: slug));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewDiet(int id) async { Future<void> viewDiet(int id) async {
try { return (await feedService.viewDiet(id: id));
return (await feedService.viewDiet(id: id));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewPlan(String slug) async { Future<void> viewPlan(String slug) async {
try { return (await feedService.viewPlan(slug: slug));
return (await feedService.viewPlan(slug: slug));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> viewContent({required ViewModel viewModel}) async { Future<void> viewContent({required ViewModel viewModel}) async {
try { await feedService.viewContent(viewModel: viewModel);
await feedService.viewContent(viewModel: viewModel);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/arc
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> { class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -34,7 +35,8 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
: FiltersComparisonEnum.equal, : FiltersComparisonEnum.equal,
); );
emit(state.copyWith(consultations: consultations, isLoading: false)); emit(state.copyWith(consultations: consultations, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(isLoading: false)); emit(state.copyWith(isLoading: false));
log(e.toString()); log(e.toString());
} }
@@ -55,7 +57,8 @@ class ConsultationsArchiveCubit extends Cubit<ConsultationsArchiveState> {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
final statistic = await consultationsRepository.getStatistic(); final statistic = await consultationsRepository.getStatistic();
emit(state.copyWith(statisticModel: statistic, isLoading: false)); emit(state.copyWith(statisticModel: statistic, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(isLoading: false)); emit(state.copyWith(isLoading: false));
log(e.toString()); log(e.toString());
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultations/repositories/consultations_rep
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> { class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -22,7 +23,8 @@ class ConsultantFinancesCubit extends Cubit<ConsultantFinancesState> {
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
); );
emit(state.copyWith(financesModels: consultantFinances, isLoading: false)); emit(state.copyWith(financesModels: consultantFinances, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(financesModels: [], isLoading: false)); emit(state.copyWith(financesModels: [], isLoading: false));
log(e.toString()); log(e.toString());
} }

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultations/repositories/consultations_rep
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class UserPurchasesCubit extends Cubit<UserPurchasesState> { class UserPurchasesCubit extends Cubit<UserPurchasesState> {
final ConsultationsRepository consultationsRepository; final ConsultationsRepository consultationsRepository;
@@ -23,7 +24,8 @@ class UserPurchasesCubit extends Cubit<UserPurchasesState> {
); );
emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false)); emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(purchasesModels: [], isLoading: false)); emit(state.copyWith(purchasesModels: [], isLoading: false));
log(e.toString()); log(e.toString());
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart';
import 'package:eatvio_app/features/glossary/services/glossary_service.dart'; import 'package:eatvio_app/features/glossary/services/glossary_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class GlossaryRepository { class GlossaryRepository {
final GlossaryService glossaryService; final GlossaryService glossaryService;
@@ -10,11 +9,7 @@ class GlossaryRepository {
}); });
Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async { Future<List<GlossaryModel>> getGlossaryListByQuery(String query) async {
try { return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList;
return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -3,16 +3,30 @@ import 'dart:developer';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class MenuCubit extends Cubit<MenuState> { class MenuCubit extends Cubit<MenuState> {
final ProfileRepository profileRepository; final ProfileRepository profileRepository;
MenuCubit(this.profileRepository) : super(MenuState()); MenuCubit(this.profileRepository) : super(const MenuState.initial());
Future<void> getMyProfile() async {
emit(const MenuState.loading());
try {
final profile = await profileRepository.getMyProfile();
emit(MenuState.loaded(profileModel: profile));
} catch (e, st) {
await reportFrom(e, st, this);
log(e.toString());
emit(const MenuState.error());
}
}
Future<String?> makePassKey() async { Future<String?> makePassKey() async {
try { try {
final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {}); final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {});
return passKey; return passKey;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
} }
return null; return null;

View File

@@ -1 +1,17 @@
class MenuState {} import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'menu_state.freezed.dart';
@freezed
abstract class MenuState with _$MenuState {
const factory MenuState.initial() = MenuStateInitial;
const factory MenuState.loading() = MenuStateLoading;
const factory MenuState.loaded({
required ProfileModel profileModel,
}) = MenuStateLoaded;
const factory MenuState.error() = MenuStateError;
}

View File

@@ -1,5 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart'; import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
@@ -16,6 +17,7 @@ class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
final notificationSettings = await _notificationSettingsRepository.getNotificationSettings(); final notificationSettings = await _notificationSettingsRepository.getNotificationSettings();
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log('Error getting notification settings: $e', stackTrace: stackTrace); log('Error getting notification settings: $e', stackTrace: stackTrace);
emit(NotificationSettingsStateError(errorMessage: e.toString())); emit(NotificationSettingsStateError(errorMessage: e.toString()));
} }
@@ -26,6 +28,7 @@ class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data); final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data);
emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings));
} catch (e, stackTrace) { } catch (e, stackTrace) {
await reportFrom(e, stackTrace, this);
log('Error updating notification settings: $e', stackTrace: stackTrace); log('Error updating notification settings: $e', stackTrace: stackTrace);
emit(NotificationSettingsStateError(errorMessage: e.toString())); emit(NotificationSettingsStateError(errorMessage: e.toString()));
} }

View File

@@ -1,6 +1,7 @@
import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart'; import 'package:eatvio_app/features/menu/presentation/blocked_user_cubit/blocked_users_state.dart';
import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart'; import 'package:eatvio_app/features/menu/presentation/repositories/blocked_users_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class BlockedUsersCubit extends Cubit<BlockedUsersState> { class BlockedUsersCubit extends Cubit<BlockedUsersState> {
final BlockedUsersRepository blockedUsersRepository; final BlockedUsersRepository blockedUsersRepository;
@@ -13,17 +14,26 @@ class BlockedUsersCubit extends Cubit<BlockedUsersState> {
try { try {
emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers())); emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers()));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(BlockedUsersState.error(errorMessage: '$e $st')); emit(BlockedUsersState.error(errorMessage: '$e $st'));
} }
} }
Future<void> blockUser(String slug) async { Future<void> blockUser(String slug) async {
await blockedUsersRepository.blockUserBySlug(slug); try {
await getBlockedUsers(); await blockedUsersRepository.blockUserBySlug(slug);
await getBlockedUsers();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
Future<void> unblockUser(String slug) async { Future<void> unblockUser(String slug) async {
await blockedUsersRepository.unblockUserBySlug(slug); try {
await getBlockedUsers(); await blockedUsersRepository.unblockUserBySlug(slug);
await getBlockedUsers();
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
} }

View File

@@ -1,11 +1,13 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_config.dart';
import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/core/token_storage.dart';
import 'package:eatvio_app/core/utils/image_constant.dart'; import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart'; import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/widgets/data_error_widget.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/websockets/ws_manager.dart'; import 'package:eatvio_app/core/websockets/ws_manager.dart';
@@ -13,9 +15,9 @@ import 'package:eatvio_app/features/consultant_rating/presentation/consultant_ra
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart'; import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart'; import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/menu_cubit/menu_state.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart'; import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart'; import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart'; import 'package:eatvio_app/features/recipe/presentation/pages/my_recipes_screen.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -25,90 +27,107 @@ import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
class MenuScreen extends StatelessWidget { class MenuScreen extends StatelessWidget {
final ProfileModel profileModel; const MenuScreen({super.key});
const MenuScreen({super.key, required this.profileModel});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => sl<MenuCubit>(), create: (context) => sl<MenuCubit>()..getMyProfile(),
child: Builder( child: Builder(
builder: (context) { builder: (context) {
return Scaffold( return BlocBuilder<MenuCubit, MenuState>(
appBar: AppBar(centerTitle: false, title: const Text('Меню')), builder: (BuildContext context, MenuState state) {
body: SingleChildScrollView( if (state is MenuStateLoaded) {
child: Column( final profileModel = state.profileModel;
children: [ return Scaffold(
_tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')), appBar: AppBar(centerTitle: false, title: const Text('Меню')),
if (profileModel.roles.contains('consultant')) _divider(context), body: SingleChildScrollView(
if (profileModel.roles.contains('consultant')) child: Column(
_tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)), children: [
if (profileModel.roles.contains('consultant')) _divider(context), _tile(title: 'Настройки профиля', leading: ImageConstant.icnProfileSettings, onTap: () => GoRouter.of(context).push('/settings-screen')),
if (profileModel.roles.contains('consultant')) if (profileModel.roles.contains('consultant')) _divider(context),
_tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)), if (profileModel.roles.contains('consultant'))
if (profileModel.roles.contains('consultant')) _divider(context), _tile(leading: ImageConstant.icnRecipes, title: 'Мои рецепты', onTap: () => GoRouter.of(context).push(MyRecipesScreen.path)),
if (profileModel.roles.contains('consultant')) if (profileModel.roles.contains('consultant')) _divider(context),
_tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)), if (profileModel.roles.contains('consultant'))
if (!hidePlans) ...[ _tile(leading: 'assets/icons/diets.svg', title: 'Мои диеты', onTap: () => GoRouter.of(context).push(MyDietsScreen.path)),
_divider(context), if (profileModel.roles.contains('consultant')) _divider(context),
_tile( if (profileModel.roles.contains('consultant'))
leading: 'assets/icons/finance.svg', _tile(leading: ImageConstant.icnPlans, title: 'Мои планы', onTap: () => GoRouter.of(context).push(MyPlansScreen.path)),
title: 'Мои финансы', if (!hidePlans) ...[
onTap: () { _divider(context),
if (profileModel.roles.contains('consultant')) { _tile(
GoRouter.of(context).push(ConsultantFinancesScreen.path); leading: 'assets/icons/finance.svg',
return; title: 'Мои финансы',
} onTap: () {
GoRouter.of(context).push(UserPurchasesScreen.path); if (profileModel.roles.contains('consultant')) {
}, GoRouter.of(context).push(ConsultantFinancesScreen.path);
return;
}
GoRouter.of(context).push(UserPurchasesScreen.path);
},
),
],
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/consultant_rating_icon.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);
},
),
_divider(context),
_tile(
leading: ImageConstant.icnGlossary,
title: 'Глоссарий',
onTap: () async {
final passKey = await context.read<MenuCubit>().makePassKey();
if (passKey != null && context.mounted) {
GoRouter.of(context).push('/glossary-screen', extra: passKey);
}
},
),
_divider(context),
_tile(
leading: 'assets/icons/notifications.svg',
title: 'Уведомления',
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'),
),
_divider(context),
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')),
_divider(context),
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
// _divider(context),
// _tile(
// leading: ImageConstant.icnDevTools,
// title: 'Для разработчиков',
// onTap: () {
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
// },
// ),
_divider(context),
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
const SizedBox(height: 10),
const AppInfo(),
],
), ),
],
if (profileModel.roles.contains('consultant')) _divider(context),
if (profileModel.roles.contains('consultant'))
_tile(
leading: 'assets/icons/consultant_rating_icon.svg',
title: 'Мой рейтинг',
onTap: () {
GoRouter.of(context).push(ConsultantRatingScreen.path);
},
),
_divider(context),
_tile(
leading: ImageConstant.icnGlossary,
title: 'Глоссарий',
onTap: () async {
final passKey = await context.read<MenuCubit>().makePassKey();
if (passKey != null && context.mounted) {
GoRouter.of(context).push('/glossary-screen', extra: passKey);
}
},
), ),
_divider(context), );
_tile( }
leading: 'assets/icons/notifications.svg',
title: 'Уведомления', if (state is MenuStateError) {
onTap: () => GoRouter.of(context).pushNamed('notificationsSettingsScreen'), return Scaffold(
), appBar: AppBar(centerTitle: false, title: const Text('Меню')),
_divider(context), body: DataErrorWidget(onReload: () async => context.read<MenuCubit>().getMyProfile()),
_tile(leading: ImageConstant.icnSupport, title: 'Поддержка', onTap: () => GoRouter.of(context).push('/support-screen')), );
_divider(context), }
_tile(leading: 'assets/icons/blocked_users.svg', title: 'Чёрный список', onTap: () => GoRouter.of(context).pushNamed('blockedUsersScreen')),
// _divider(context), return Scaffold(
// _tile( appBar: AppBar(centerTitle: false, title: const Text('Меню')),
// leading: ImageConstant.icnDevTools, body: const Loader(),
// title: 'Для разработчиков', );
// onTap: () { },
// GoRouter.of(context).pushNamed(DevToolsScreen.name);
// },
// ),
_divider(context),
_tile(leading: ImageConstant.icnExit, title: 'Выход', onTap: _onTapLogout(context)),
const SizedBox(height: 10),
const AppInfo(),
],
),
),
); );
}, },
), ),
@@ -144,7 +163,7 @@ class MenuScreen extends StatelessWidget {
try { try {
await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout'); await sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout');
} catch (e, s) { } catch (e, s) {
await Sentry.captureException(e, stackTrace: s); await sl<ErrorReporter>().report(e, s, origin: 'MenuScreen');
} finally { } finally {
await sl<WsManager>().clear(); await sl<WsManager>().clear();
await TokenStorage.removeTokenInStorage(); await TokenStorage.removeTokenInStorage();

View File

@@ -1,7 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/menu/services/blocked_users_service.dart'; import 'package:eatvio_app/features/menu/services/blocked_users_service.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class BlockedUsersRepository { class BlockedUsersRepository {
final BlockedUserService blockedUserService; final BlockedUserService blockedUserService;
@@ -11,40 +9,25 @@ class BlockedUsersRepository {
}); });
Future<List<ProfileModel>> getBlockedUsers() async { Future<List<ProfileModel>> getBlockedUsers() async {
try { return (await blockedUserService.getBlockedUsers()).profilesList;
return (await blockedUserService.getBlockedUsers()).profilesList;
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<void> blockUserBySlug(String slug) async { Future<void> blockUserBySlug(String slug) async {
try { return await blockedUserService.blockUser(
return await blockedUserService.blockUser( data: {
data: { 'slug': slug,
'slug': slug, },
}, );
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<void> unblockUserBySlug(String slug) async { Future<void> unblockUserBySlug(String slug) async {
try { return await blockedUserService.unblockUser(
return await blockedUserService.unblockUser( data: {
data: { 'slug': slug,
'slug': slug, },
}, );
);
} catch (e, st) {
log(e.toString(), stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/menu/models/notification_settings_model.dart'; import 'package:eatvio_app/features/menu/models/notification_settings_model.dart';
import 'package:eatvio_app/features/menu/services/notification_settings_service.dart'; import 'package:eatvio_app/features/menu/services/notification_settings_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class NotificationSettingsRepository { class NotificationSettingsRepository {
final NotificationSettingsService _notificationSettingsService; final NotificationSettingsService _notificationSettingsService;
@@ -8,20 +7,12 @@ class NotificationSettingsRepository {
NotificationSettingsRepository(this._notificationSettingsService); NotificationSettingsRepository(this._notificationSettingsService);
Future<NotificationSettingsModel> getNotificationSettings() async { Future<NotificationSettingsModel> getNotificationSettings() async {
try { return await _notificationSettingsService.getNotificationSettings();
return await _notificationSettingsService.getNotificationSettings();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async { Future<NotificationSettingsModel> updateNotificationSettings(Map<String, dynamic> data) async {
try { return await _notificationSettingsService.updateNotificationSettings(data: data);
return await _notificationSettingsService.updateNotificationSettings(data: data);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart'; import 'package:eatvio_app/features/my_diets/presentation/cubit/my_diets_state.dart';
import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart'; import 'package:eatvio_app/features/my_diets/repositories/my_diets_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class MyDietsCubit extends Cubit<MyDietsState> { class MyDietsCubit extends Cubit<MyDietsState> {
final MyDietsRepository myDietsRepository; final MyDietsRepository myDietsRepository;
@@ -212,6 +213,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
), ),
); );
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit( emit(
state.copyWith(activeDietsError: e.toString()), state.copyWith(activeDietsError: e.toString()),
@@ -242,7 +244,8 @@ class MyDietsCubit extends Cubit<MyDietsState> {
error: null, error: null,
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(draftDietsError: e.toString()), state.copyWith(draftDietsError: e.toString()),
); );
@@ -272,7 +275,8 @@ class MyDietsCubit extends Cubit<MyDietsState> {
error: null, error: null,
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(personalDietsError: e.toString()), state.copyWith(personalDietsError: e.toString()),
); );
@@ -283,7 +287,8 @@ class MyDietsCubit extends Cubit<MyDietsState> {
try { try {
await myDietsRepository.deleteDiet(dietId); await myDietsRepository.deleteDiet(dietId);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(error: e.toString()), state.copyWith(error: e.toString()),
); );
@@ -299,6 +304,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
); );
return statusCode; return statusCode;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return 0; return 0;
} }
@@ -312,6 +318,7 @@ class MyDietsCubit extends Cubit<MyDietsState> {
); );
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/diets_pagination_model.dart';
import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart'; import 'package:eatvio_app/features/my_diets/services/my_diets_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class MyDietsRepository { class MyDietsRepository {
final MyDietsService myDietsService; final MyDietsService myDietsService;
@@ -8,12 +7,8 @@ class MyDietsRepository {
MyDietsRepository({required this.myDietsService}); MyDietsRepository({required this.myDietsService});
Future<void> deleteDiet(int dietId) async { Future<void> deleteDiet(int dietId) async {
try { await myDietsService.deleteDiet(dietId: dietId);
await myDietsService.deleteDiet(dietId: dietId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<DietsPagination> getDiets({ Future<DietsPagination> getDiets({
@@ -24,18 +19,14 @@ class MyDietsRepository {
String? sorting, String? sorting,
String? state, String? state,
}) async { }) async {
try { return (await myDietsService.getDiets(
return (await myDietsService.getDiets( page: page,
page: page, query: query,
query: query, types: types,
types: types, duration: duration,
duration: duration, sorting: sorting,
sorting: sorting, state: state,
state: state, ));
));
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart'; import 'package:eatvio_app/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_state.dart';
import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart'; import 'package:eatvio_app/features/my_finance/repositories/cards_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class UserCardsCubit extends Cubit<UserCardsState> { class UserCardsCubit extends Cubit<UserCardsState> {
final CardsRepository cardsRepository; final CardsRepository cardsRepository;
@@ -15,6 +16,7 @@ class UserCardsCubit extends Cubit<UserCardsState> {
try { try {
return await cardsRepository.addCard(); return await cardsRepository.addCard();
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
return null; return null;
@@ -27,6 +29,7 @@ class UserCardsCubit extends Cubit<UserCardsState> {
final cards = await cardsRepository.getCards(); final cards = await cardsRepository.getCards();
emit(state.copyWith(cards: cards, state: BaseState.success)); emit(state.copyWith(cards: cards, state: BaseState.success));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(state: BaseState.error, error: e.toString())); emit(state.copyWith(state: BaseState.error, error: e.toString()));
} }
@@ -47,6 +50,7 @@ class UserCardsCubit extends Cubit<UserCardsState> {
try { try {
await cardsRepository.markAsDefault(id: id); await cardsRepository.markAsDefault(id: id);
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(cards: oldCards)); emit(state.copyWith(cards: oldCards));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
@@ -61,6 +65,7 @@ class UserCardsCubit extends Cubit<UserCardsState> {
emit(state.copyWith(cards: cards)); emit(state.copyWith(cards: cards));
await cardsRepository.deleteCard(id: id); await cardsRepository.deleteCard(id: id);
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
emit(state.copyWith(cards: oldCards, error: e.toString(), state: BaseState.error)); emit(state.copyWith(cards: oldCards, error: e.toString(), state: BaseState.error));
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
@@ -71,6 +76,7 @@ class UserCardsCubit extends Cubit<UserCardsState> {
await cardsRepository.collectCharge(chosenPlaId: chosenPlaId); await cardsRepository.collectCharge(chosenPlaId: chosenPlaId);
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
return false; return false;

View File

@@ -1,53 +1,32 @@
import 'package:eatvio_app/features/my_finance/models/card_model.dart'; import 'package:eatvio_app/features/my_finance/models/card_model.dart';
import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart'; import 'package:eatvio_app/features/my_finance/models/payment_url_model.dart';
import 'package:eatvio_app/features/my_finance/services/cards_service.dart'; import 'package:eatvio_app/features/my_finance/services/cards_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class CardsRepository { class CardsRepository {
final CardsService cardsService; final CardsService cardsService;
CardsRepository({required this.cardsService}); CardsRepository({required this.cardsService});
Future<PaymentUrlModel> addCard() async { Future<PaymentUrlModel> addCard() async {
try { return await cardsService.addCard();
return await cardsService.addCard();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<List<CardModel>> getCards() async { Future<List<CardModel>> getCards() async {
try { return await cardsService.getCards();
return await cardsService.getCards();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> markAsDefault({required int id}) async { Future<void> markAsDefault({required int id}) async {
try { await cardsService.markAsDefault(id: id);
await cardsService.markAsDefault(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> deleteCard({required int id}) async { Future<void> deleteCard({required int id}) async {
try { await cardsService.deleteCard(id: id);
await cardsService.deleteCard(id: id);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> collectCharge({required int chosenPlaId}) async { Future<void> collectCharge({required int chosenPlaId}) async {
try { await cardsService.collectCharge(chosenPlanId: chosenPlaId);
await cardsService.collectCharge(chosenPlanId: chosenPlaId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart'; import 'package:eatvio_app/features/my_plans/presentation/cubit/my_plans_state.dart';
import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart'; import 'package:eatvio_app/features/my_plans/repositories/my_diets_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class MyPlansCubit extends Cubit<MyPlansState> { class MyPlansCubit extends Cubit<MyPlansState> {
final MyPlansRepository myPlansRepository; final MyPlansRepository myPlansRepository;
@@ -145,7 +146,8 @@ class MyPlansCubit extends Cubit<MyPlansState> {
maxActivePlanPrice: maxPlanPrice, maxActivePlanPrice: maxPlanPrice,
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(error: e.toString()), state.copyWith(error: e.toString()),
); );
@@ -185,7 +187,8 @@ class MyPlansCubit extends Cubit<MyPlansState> {
maxDraftPlanPrice: maxPlanPrice, maxDraftPlanPrice: maxPlanPrice,
), ),
); );
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(error: e.toString()), state.copyWith(error: e.toString()),
); );
@@ -197,7 +200,8 @@ class MyPlansCubit extends Cubit<MyPlansState> {
await myPlansRepository.deletePlan(slug); await myPlansRepository.deletePlan(slug);
await loadAllPlans(); await loadAllPlans();
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
emit( emit(
state.copyWith(error: e.toString()), state.copyWith(error: e.toString()),
); );
@@ -209,7 +213,8 @@ class MyPlansCubit extends Cubit<MyPlansState> {
try { try {
await myPlansRepository.unpublish(slug); await myPlansRepository.unpublish(slug);
return true; return true;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
return false; return false;
} }
} }

View File

@@ -1,8 +1,5 @@
import 'dart:developer';
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart'; import 'package:eatvio_app/features/my_plans/services/my_plans_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class MyPlansRepository { class MyPlansRepository {
final MyPlansService myPlansService; final MyPlansService myPlansService;
@@ -10,30 +7,17 @@ class MyPlansRepository {
MyPlansRepository({required this.myPlansService}); MyPlansRepository({required this.myPlansService});
Future<void> deletePlan(String slug) async { Future<void> deletePlan(String slug) async {
try { await myPlansService.deletePlan(slug: slug);
await myPlansService.deletePlan(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async { Future<PlanPaginationModel> getPlans({int page = 1, String? query, String? types, int? duration, int? minPrice, int? maxPrice}) async {
try { return (await myPlansService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
return (await myPlansService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice));
} catch (e, st) {
log(e.toString(), error: e, stackTrace: st);
await Sentry.captureException(e, stackTrace: st);
rethrow;
}
} }
Future<void> unpublish(String slug) async { Future<void> unpublish(String slug) async {
try { return await myPlansService.unpublish(slug: slug);
return await myPlansService.unpublish(slug: slug);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:eatvio_app/features/notifications/models/notification_model.dart
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart'; import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_state.dart';
import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart'; import 'package:eatvio_app/features/notifications/repository/notifications_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class NotificationsCubit extends Cubit<NotificationsState> { class NotificationsCubit extends Cubit<NotificationsState> {
final List<BaseNotificationModel> _notifications = []; final List<BaseNotificationModel> _notifications = [];
@@ -16,10 +17,14 @@ class NotificationsCubit extends Cubit<NotificationsState> {
NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty()); NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty());
Future<void> connectToWebSocket() async { Future<void> connectToWebSocket() async {
await _newNotificationsSubscription?.cancel(); try {
_newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) { await _newNotificationsSubscription?.cancel();
_handleNotification(event.data); _newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) {
}); _handleNotification(event.data);
});
} catch (e, st) {
await reportFrom(e, st, this);
}
} }
BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) { BaseNotificationModel _parseNotification(Map<String, dynamic> notificationJson, NotificationType type) {
@@ -341,12 +346,21 @@ class NotificationsCubit extends Cubit<NotificationsState> {
), ),
NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt ?? jsonEncode(notificationJson))), NotificationType.unknown => UnknownNotification(type: type, createdAt: parseDate(createdAt ?? jsonEncode(notificationJson))),
}; };
} catch (e) { } catch (e, st) {
unawaited(reportFrom(e, st, this));
return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now()); return UnknownNotification(type: NotificationType.unknown, createdAt: DateTime.now());
} }
} }
void _handleNotification(String notificationData) { void _handleNotification(String notificationData) {
try {
_handleNotificationParsed(notificationData);
} catch (e, st) {
unawaited(reportFrom(e, st, this));
}
}
void _handleNotificationParsed(String notificationData) {
final json = jsonDecode(notificationData) as Map<String, dynamic>; final json = jsonDecode(notificationData) as Map<String, dynamic>;
// Улучшенная логика определения типа уведомления (порядок: database_type → type → notification_class_name) // Улучшенная логика определения типа уведомления (порядок: database_type → type → notification_class_name)
@@ -420,6 +434,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
); );
} }
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
} }
} }
@@ -456,6 +471,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
emit(currentState.copyWith(isLoadingMore: false)); emit(currentState.copyWith(isLoadingMore: false));
} }
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(currentState.copyWith(isLoadingMore: false)); emit(currentState.copyWith(isLoadingMore: false));
} }
@@ -471,6 +487,7 @@ class NotificationsCubit extends Cubit<NotificationsState> {
await getNotifications(); await getNotifications();
emit(currentState.copyWith(isLoading: false, hasUnreadNotifications: false)); emit(currentState.copyWith(isLoading: false, hasUnreadNotifications: false));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(currentState.copyWith(isLoading: false)); emit(currentState.copyWith(isLoading: false));
} }

View File

@@ -1,3 +1,4 @@
import 'package:eatvio_app/core/const.dart';
import 'package:eatvio_app/core/routing/route_constants.dart'; import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_nav_scope.dart';
@@ -594,7 +595,6 @@ class _EntityCommentedWidget extends StatelessWidget {
} }
} }
// Новые виджеты для дополнительных типов уведомлений // Новые виджеты для дополнительных типов уведомлений
class _PlanSubscriptionExpiredWidget extends StatelessWidget { class _PlanSubscriptionExpiredWidget extends StatelessWidget {
const _PlanSubscriptionExpiredWidget({required this.notification}); const _PlanSubscriptionExpiredWidget({required this.notification});
@@ -737,7 +737,11 @@ class _DietCancelledWidget extends StatelessWidget {
createdAt: notification.createdAt, createdAt: notification.createdAt,
onTap: () { onTap: () {
if (notification.dietId > 0) { if (notification.dietId > 0) {
context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId}); const dietTabIndexWithHiddenPlansTab = 3;
const dietTabIndexWithoutHiddenPlansTab = 4;
final initialTabIndex = hidePlans ? dietTabIndexWithHiddenPlansTab : dietTabIndexWithoutHiddenPlansTab;
_navigateToBottomNavRoute(context, RouteConstants.feedScreen, initialTabIndex);
} }
}, },
); );
@@ -754,9 +758,7 @@ class _DietStartedWidget extends StatelessWidget {
notificationsText: 'Старт диеты "${notification.dietName}"! Успехов! 🥗', notificationsText: 'Старт диеты "${notification.dietName}"! Успехов! 🥗',
createdAt: notification.createdAt, createdAt: notification.createdAt,
onTap: () { onTap: () {
if (notification.dietId > 0) { _navigateToBottomNavRoute(context, RouteConstants.diaryScreen);
context.push(RouteConstants.aboutDietScreen, extra: {'id': notification.dietId});
}
}, },
); );
} }

View File

@@ -1,27 +1,16 @@
import 'dart:developer';
import 'package:eatvio_app/features/notifications/service/notifications_service.dart'; import 'package:eatvio_app/features/notifications/service/notifications_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class NotificationsRepository { class NotificationsRepository {
final NotificationsService notificationsService; final NotificationsService notificationsService;
NotificationsRepository({required this.notificationsService}); NotificationsRepository({required this.notificationsService});
Future<Map<String, dynamic>?> getNotifications({required int page}) async { Future<Map<String, dynamic>?> getNotifications({required int page}) async {
try { return await notificationsService.getNotifications(page: page);
return await notificationsService.getNotifications(page: page);
} catch (e, s) {
log(e.toString());
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> markNotificationsAsRead() async { Future<void> markNotificationsAsRead() async {
try { await notificationsService.markNotificationsAsRead();
await notificationsService.markNotificationsAsRead();
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart'; import 'package:eatvio_app/features/order_info/cubit/order_info_state.dart';
import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class OrderInfoCubit extends Cubit<OrderInfoState> { class OrderInfoCubit extends Cubit<OrderInfoState> {
OrderInfoCubit({required this.chatRepository, required this.profileRepository}) : super(OrderInfoState()); OrderInfoCubit({required this.chatRepository, required this.profileRepository}) : super(OrderInfoState());
@@ -15,22 +16,32 @@ class OrderInfoCubit extends Cubit<OrderInfoState> {
if (plan == null) return null; if (plan == null) return null;
final paymentModel = await chatRepository.getPaymentLink(id: plan.id); final paymentModel = await chatRepository.getPaymentLink(id: plan.id);
return paymentModel.paymentUrl; return paymentModel.paymentUrl;
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
return null; return null;
} }
} }
Future<void> getChosenPlan({required int id, String? customerSlug}) async { Future<void> getChosenPlan({required int id, String? customerSlug, bool isConsultantView = false}) async {
try { try {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
final chosenPlan = await chatRepository.getChosenPlan(id: id); final chosenPlan = await chatRepository.getChosenPlan(id: id);
if (customerSlug != null) { if (customerSlug != null) {
final profile = await profileRepository.getProfileBySlug(customerSlug); final profile = await profileRepository.getProfileBySlug(customerSlug);
emit(state.copyWith(customerProfile: profile)); emit(state.copyWith(customerProfile: profile, isLoading: false, chosenPlan: chosenPlan));
return;
}
if (isConsultantView) {
final chatRoom = await chatRepository.getChatRoom(chosenPlan.chatId);
final customerSlug = chatRoom.users.receiver.slug;
final profile = await profileRepository.getProfileBySlug(customerSlug);
emit(state.copyWith(customerProfile: profile, isLoading: false, chosenPlan: chosenPlan));
} }
emit(state.copyWith(isLoading: false, chosenPlan: chosenPlan)); emit(state.copyWith(isLoading: false, chosenPlan: chosenPlan));
} catch (e) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString()); log(e.toString());
emit(state.copyWith(isLoading: false)); emit(state.copyWith(isLoading: false));
} }

View File

@@ -1,4 +1,5 @@
import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/routing/route_constants.dart';
import 'package:eatvio_app/core/theme/custom_text_style.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/theme/theme_helper.dart';
import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart'; import 'package:eatvio_app/features/cancel_plan_bottom_sheet/cancel_plan_bottom_sheet.dart';
@@ -24,20 +25,21 @@ import 'package:go_router/go_router.dart';
class InfoByOrderScreen extends StatelessWidget { class InfoByOrderScreen extends StatelessWidget {
static const name = 'infoByOrderScreen'; static const name = 'infoByOrderScreen';
static const path = '/info-by-order-screen'; static const path = '/info-by-order-screen';
const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId, this.customerSlug}); const InfoByOrderScreen({super.key, this.successfulPayment, required this.planId, this.customerSlug, this.isConsultantView = false});
final bool? successfulPayment; final bool? successfulPayment;
final int planId; final int planId;
final String? customerSlug; final String? customerSlug;
final bool isConsultantView;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => sl<OrderInfoCubit>()..getChosenPlan(id: planId, customerSlug: customerSlug), create: (context) => sl<OrderInfoCubit>()..getChosenPlan(id: planId, customerSlug: customerSlug, isConsultantView: isConsultantView),
child: InfoByOrderScreenContent(successfulPayment: successfulPayment, planId: planId, customerSlug: customerSlug), child: InfoByOrderScreenContent(successfulPayment: successfulPayment, planId: planId, customerSlug: customerSlug),
); );
} }
} }
class InfoByOrderScreenContent extends StatefulWidget { class InfoByOrderScreenContent extends StatefulWidget {
const InfoByOrderScreenContent({super.key, required this.planId, required this.successfulPayment, this.customerSlug}); const InfoByOrderScreenContent({super.key, required this.planId, required this.successfulPayment, this.customerSlug});
final int planId; final int planId;
final bool? successfulPayment; final bool? successfulPayment;
@@ -61,7 +63,11 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); if (GoRouter.of(context).canPop()) {
GoRouter.of(context).pop();
} else {
GoRouter.of(context).go(RouteConstants.feedScreen);
}
}, },
), ),
), ),
@@ -75,7 +81,11 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); if (GoRouter.of(context).canPop()) {
GoRouter.of(context).pop();
} else {
GoRouter.of(context).go(RouteConstants.feedScreen);
}
}, },
), ),
), ),
@@ -110,7 +120,11 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
GoRouter.of(context).pop(); if (GoRouter.of(context).canPop()) {
GoRouter.of(context).pop();
} else {
GoRouter.of(context).go(RouteConstants.feedScreen);
}
}, },
), ),
), ),
@@ -134,7 +148,7 @@ class _InfoByOrderScreenContentState extends State<InfoByOrderScreenContent> {
const SizedBox(height: 8), const SizedBox(height: 8),
OptionWidget(option: chosenOption, disableDuration: true, duration: plan.plan.duration), OptionWidget(option: chosenOption, disableDuration: true, duration: plan.plan.duration),
const SizedBox(height: 8), const SizedBox(height: 8),
PlanState(state: plan.chosenPlanState, hasPersonalDiet: chosenOption?.hasPersonalDiet ?? false,), PlanState(state: plan.chosenPlanState, hasPersonalDiet: chosenOption?.hasPersonalDiet ?? false),
const SizedBox(height: 20), const SizedBox(height: 20),
if (widget.successfulPayment == null) if (widget.successfulPayment == null)
CustomOutlinedButton( CustomOutlinedButton(

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_state.dart'; import 'package:eatvio_app/features/personal_diet_suggestion/cubit/personal_diet_suggestion_state.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart'; import 'package:eatvio_app/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:eatvio_app/core/error_reporting/report_from.dart';
class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> { class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> {
final DietRepository dietRepository; final DietRepository dietRepository;
@@ -17,6 +18,7 @@ class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> {
final diet = await dietRepository.getDiet(dietId); final diet = await dietRepository.getDiet(dietId);
emit(state.copyWith(isLoading: false, dietName: diet.name)); emit(state.copyWith(isLoading: false, dietName: diet.name));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(isLoading: false, error: e.toString())); emit(state.copyWith(isLoading: false, error: e.toString()));
} }
@@ -36,6 +38,7 @@ class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> {
consultationsWithoutDiet: consultationWithoutDiet, consultationsWithoutDiet: consultationWithoutDiet,
)); ));
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
emit(state.copyWith(isLoading: false, error: e.toString())); emit(state.copyWith(isLoading: false, error: e.toString()));
} }
@@ -48,6 +51,7 @@ class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> {
await personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId); await personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }
@@ -60,6 +64,7 @@ class PersonalDietSuggestionCubit extends Cubit<PersonalDietSuggestionState> {
await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId); await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
return true; return true;
} catch (e, st) { } catch (e, st) {
await reportFrom(e, st, this);
log(e.toString(), stackTrace: st); log(e.toString(), stackTrace: st);
return false; return false;
} }

View File

@@ -1,6 +1,5 @@
import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart'; import 'package:eatvio_app/features/personal_diet_suggestion/services/personal_diet_suggestion_service.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class PersonalDietSuggestionRepository { class PersonalDietSuggestionRepository {
final PersonalDietSuggestionService personalDietSuggestionService; final PersonalDietSuggestionService personalDietSuggestionService;
@@ -8,29 +7,17 @@ class PersonalDietSuggestionRepository {
PersonalDietSuggestionRepository({required this.personalDietSuggestionService}); PersonalDietSuggestionRepository({required this.personalDietSuggestionService});
Future<List<ConsultationModel>> getConsultationWithPersonalDiets() async{ Future<List<ConsultationModel>> getConsultationWithPersonalDiets() async{
try { return (await personalDietSuggestionService.getConsultationWithPersonalDiets()).data;
return (await personalDietSuggestionService.getConsultationWithPersonalDiets()).data;
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> attachPersonalDiet({required int consultationId, required int dietId}) async { Future<void> attachPersonalDiet({required int consultationId, required int dietId}) async {
try { return personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
return personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
Future<void> reattachPersonalDiet({required int consultationId, required int dietId}) async { Future<void> reattachPersonalDiet({required int consultationId, required int dietId}) async {
try { return await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
return await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId);
} catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
rethrow;
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More