diff --git a/lib/core/app_initializer.dart b/lib/core/app_initializer.dart index a4e91210..e9e3e9f6 100644 --- a/lib/core/app_initializer.dart +++ b/lib/core/app_initializer.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'dart:io' as io; import 'package:eatvio_app/core/app_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'; import 'package:eatvio_app/firebase_options.dart'; @@ -16,6 +17,7 @@ import 'package:sentry_flutter/sentry_flutter.dart'; /// Класс для инициализации приложения class AppInitializer { + /// Инициализация всех сервисов приложения static Future initialize({required AppBuildType buildType}) async { await _initializeEnvironment(); @@ -79,11 +81,12 @@ class AppInitializer { options.dsn = EnvConfig.sentryDsn; options.tracesSampleRate = 1.0; options.debug = false; + options.beforeSend = (event, hint) { + if (kDebugMode) return null; + if (!ErrorReportPolicy.allowSentryEvent(event, hint)) return null; + return event; + }; }, appRunner: appRunner); - FlutterError.onError = (details) async { - if (kDebugMode) return; - await Sentry.captureException(details.exception, stackTrace: details.stack); - }; } } diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 13f139a1..2b32db51 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -7,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/settings_routes.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_native_splash/flutter_native_splash.dart'; import 'package:go_router/go_router.dart'; @@ -35,7 +36,6 @@ class AppRouter { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: recipe/$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/about-recipes-screen?slug=$contentIdentifier'; } @@ -43,7 +43,6 @@ class AppRouter { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: blog_post/$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/blog-post-screen?id=$contentIdentifier'; } @@ -53,11 +52,9 @@ class AppRouter { final planId = int.tryParse(contentIdentifier); if (planId != null) { developer.log('Deep link: diary', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.diaryScreen; } developer.log('Deep link: plan/$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/about-plan-screen?slug=$contentIdentifier'; } @@ -67,11 +64,9 @@ class AppRouter { final dietId = int.tryParse(contentIdentifier); if (dietId != null) { developer.log('Deep link: diet/id=$dietId', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/about-diet-screen?id=$dietId'; } developer.log('Deep link: diet/slug=$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/about-diet-screen?slug=$contentIdentifier'; } const dietTabIndexWithHiddenPlansTab = 3; @@ -85,17 +80,14 @@ class AppRouter { if (effectiveSegments.length == 2) { final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: chat/$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); 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'); - FlutterNativeSplash.remove(); return '${RouteConstants.chatScreen}?uuid=$contentIdentifier'; } developer.log('Deep link: chat', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.chatSelectScreen; } @@ -103,7 +95,6 @@ class AppRouter { if (effectiveSegments.length == 2) { final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: profile/$contentIdentifier', name: 'AppRouter'); - FlutterNativeSplash.remove(); return '/profile-screen?slug=$contentIdentifier'; } return RouteConstants.myProfileScreen; @@ -115,7 +106,6 @@ class AppRouter { static String? _resolveDiaryDeepLink(List effectiveSegments) { developer.log('Deep link: diary', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.diaryScreen; } @@ -123,7 +113,6 @@ class AppRouter { if (effectiveSegments.length != 2) return null; final contentIdentifier = effectiveSegments[1]; developer.log('Deep link: consultant_diary_comment/$contentIdentifier → diary', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.diaryScreen; } @@ -131,8 +120,7 @@ class AppRouter { if (effectiveSegments.length != 3 || effectiveSegments[1] != 'status') return RouteConstants.consultationsScreen; final consultationId = effectiveSegments[2]; developer.log('Deep link: consultations/status/$consultationId', name: 'AppRouter'); - FlutterNativeSplash.remove(); - return '${RouteConstants.consultationsScreen}?id=$consultationId'; + return '${InfoByOrderScreen.path}?planId=$consultationId'; } static String? _resolveDeepLinkUri(Uri uri) { @@ -149,14 +137,12 @@ class AppRouter { if (segments.isEmpty) { developer.log('Empty segments in deep link, redirecting to feed', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.feedScreen; } final effectiveSegments = segments.where((segment) => !metaSegments.contains(segment)).toList(); if (effectiveSegments.isEmpty) { developer.log('No effective segments after language check, redirecting to feed', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.feedScreen; } @@ -206,7 +192,6 @@ class AppRouter { } developer.log('Unknown deep link format: ${effectiveSegments.length} effective segments, type=$type, redirecting to feed', name: 'AppRouter'); - FlutterNativeSplash.remove(); return RouteConstants.feedScreen; } @@ -246,12 +231,14 @@ class AppRouter { } final target = _resolveDeepLinkUri(uri); + print('target: $target'); + FlutterNativeSplash.remove(); if (target != null) { appRouter.go(target); - } else { - developer.log('navigateByDeepLinkUrl: unresolved url=$url (normalized=${uri.toString()}), redirecting to feed', name: 'AppRouter'); - appRouter.go(RouteConstants.feedScreen); + return; } + 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}) { diff --git a/lib/core/comments/comments_cubit/comments_cubit.dart b/lib/core/comments/comments_cubit/comments_cubit.dart index a65a9c6e..71adc23a 100644 --- a/lib/core/comments/comments_cubit/comments_cubit.dart +++ b/lib/core/comments/comments_cubit/comments_cubit.dart @@ -5,6 +5,7 @@ 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/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class CommentsCubit extends Cubit { final FeedRepository feedRepository; @@ -33,7 +34,8 @@ class CommentsCubit extends Cubit { emit(CommentsState.loaded(comment: await feedRepository.getPlansComments(slug: id))); break; } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(CommentsState.error(errorMessage: e.toString())); } } @@ -56,7 +58,8 @@ class CommentsCubit extends Cubit { } getComments(id, commentSourceType); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(CommentsState.error(errorMessage: e.toString())); return false; } @@ -71,6 +74,7 @@ class CommentsCubit extends Cubit { emit(CommentsState.loaded(comment: updatedCommentModel)); return true; } catch (e, st) { + await reportFrom(e, st, this); emit(CommentsState.loaded(comment: commentModel)); log(e.toString(), stackTrace: st); } @@ -87,6 +91,7 @@ class CommentsCubit extends Cubit { emit(CommentsState.loaded(comment: updatedCommentModel)); return true; } catch (e, st) { + await reportFrom(e, st, this); emit(CommentsState.loaded(comment: commentModel)); log(e.toString(), stackTrace: st); } diff --git a/lib/core/comments/repositories/comments_repository.dart b/lib/core/comments/repositories/comments_repository.dart index 9ad676c9..4a0b91a7 100644 --- a/lib/core/comments/repositories/comments_repository.dart +++ b/lib/core/comments/repositories/comments_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/core/comments/models/comment_update_model.dart'; import 'package:eatvio_app/core/comments/services/comments_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class CommentsRepository { final CommentsService service; @@ -8,20 +7,12 @@ class CommentsRepository { CommentsRepository({required this.service}); Future deleteComment({required int id}) async { - try { - return service.deleteComment(id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return service.deleteComment(id); + } Future updateComment({required int id, required String body}) async { - try { - return service.updateComment(id, CommentUpdateModel(body: body)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return service.updateComment(id, CommentUpdateModel(body: body)); + } } diff --git a/lib/core/cubit/marketing/marketing_cubit.dart b/lib/core/cubit/marketing/marketing_cubit.dart index cf5b0c13..7f058a47 100644 --- a/lib/core/cubit/marketing/marketing_cubit.dart +++ b/lib/core/cubit/marketing/marketing_cubit.dart @@ -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_keys.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class MarketingCubit extends Cubit { final Prefs prefs; @@ -22,8 +23,9 @@ class MarketingCubit extends Cubit { await prefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true); } return needToShowMarketingBottomSheet; - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); + return false; } } } diff --git a/lib/core/di/external_registrations.dart b/lib/core/di/external_registrations.dart index 34f93aa6..cb285f40 100644 --- a/lib/core/di/external_registrations.dart +++ b/lib/core/di/external_registrations.dart @@ -1,10 +1,13 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/app_config.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/token_interceptor.dart'; import 'package:eatvio_app/core/prefs.dart'; import 'package:get_it/get_it.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sentry_dio/sentry_dio.dart'; @@ -14,6 +17,7 @@ class ExternalRegistrations { /// Регистрация всех внешних зависимостей static void registerAll() { + sl.registerLazySingleton(SentryErrorReporter.new); _registerDio(); _registerPrefs(); } @@ -30,7 +34,9 @@ class ExternalRegistrations { sendTimeout: AppConstants.pendingDelay, ), )..interceptors.addAll([TokenInterceptor(), PhoneVerifyErrorInterceptor()]) - ..addSentry(), + ..addSentry( + failedRequestStatusCodes: const [SentryStatusCode.defaultRange()], + ), ); } diff --git a/lib/core/di/service_registrations.dart b/lib/core/di/service_registrations.dart index 62439f4a..dd1371b1 100644 --- a/lib/core/di/service_registrations.dart +++ b/lib/core/di/service_registrations.dart @@ -72,7 +72,7 @@ class ServiceRegistrations { sl.registerLazySingleton(() => CancelPlanService(sl())); sl.registerLazySingleton(() => CreatePlanService(sl())); sl.registerLazySingleton(() => NotificationsService(sl())); - sl.registerLazySingleton(() => FirebaseApi()); + sl.registerLazySingleton(() => FirebaseApi(errorReporter: sl())); sl.registerLazySingleton(() => ResetPasswordService(sl())); sl.registerLazySingleton(() => BlockedUserService(sl())); sl.registerLazySingleton(() => CommentsService(sl())); diff --git a/lib/core/error_reporting/error_report_policy.dart b/lib/core/error_reporting/error_report_policy.dart new file mode 100644 index 00000000..5e813fd8 --- /dev/null +++ b/lib/core/error_reporting/error_report_policy.dart @@ -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; + } +} diff --git a/lib/core/error_reporting/error_reporter.dart b/lib/core/error_reporting/error_reporter.dart new file mode 100644 index 00000000..e61183e9 --- /dev/null +++ b/lib/core/error_reporting/error_reporter.dart @@ -0,0 +1,9 @@ +/// Application-wide error reporting (e.g. Sentry). +abstract class ErrorReporter { + Future report( + Object error, + StackTrace? stackTrace, { + String? origin, + Map? extras, + }); +} diff --git a/lib/core/error_reporting/no_op_error_reporter.dart b/lib/core/error_reporting/no_op_error_reporter.dart new file mode 100644 index 00000000..d9b2fd67 --- /dev/null +++ b/lib/core/error_reporting/no_op_error_reporter.dart @@ -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 report( + Object error, + StackTrace? stackTrace, { + String? origin, + Map? extras, + }) async {} +} diff --git a/lib/core/error_reporting/report_from.dart b/lib/core/error_reporting/report_from.dart new file mode 100644 index 00000000..831953cd --- /dev/null +++ b/lib/core/error_reporting/report_from.dart @@ -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 reportFrom(Object error, StackTrace? stackTrace, Object context) { + final reporter = sl.isRegistered() + ? sl() + : const NoOpErrorReporter(); + return reporter.report( + error, + stackTrace, + origin: context.runtimeType.toString(), + ); +} diff --git a/lib/core/error_reporting/sentry_error_reporter.dart b/lib/core/error_reporting/sentry_error_reporter.dart new file mode 100644 index 00000000..e0bd8192 --- /dev/null +++ b/lib/core/error_reporting/sentry_error_reporter.dart @@ -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 report( + Object error, + StackTrace? stackTrace, { + String? origin, + Map? 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())), + ); + } + }, + ); + } +} diff --git a/lib/core/firebase_api.dart b/lib/core/firebase_api.dart index 1d09d7a3..2c8c64c8 100644 --- a/lib/core/firebase_api.dart +++ b/lib/core/firebase_api.dart @@ -11,11 +11,14 @@ import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; 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 { + final ErrorReporter _errorReporter; final _firebaseMessaging = FirebaseMessaging.instance; + FirebaseApi({required ErrorReporter errorReporter}) : _errorReporter = errorReporter; + Future getToken() async { String? token; @@ -61,7 +64,7 @@ class FirebaseApi { ); } catch (e, 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) { 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) { showErrorToast('$e $st'); log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); + await _errorReporter.report(e, st, origin: 'FirebaseApi'); return null; } catch (e, st) { showErrorToast('$e $st'); log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); + await _errorReporter.report(e, st, origin: 'FirebaseApi'); return null; } } diff --git a/lib/core/internet_connection_cubit/internet_connection_cubit.dart b/lib/core/internet_connection_cubit/internet_connection_cubit.dart index 67427c6c..ef77c185 100644 --- a/lib/core/internet_connection_cubit/internet_connection_cubit.dart +++ b/lib/core/internet_connection_cubit/internet_connection_cubit.dart @@ -8,6 +8,7 @@ import 'package:eatvio_app/core/utils/toasts.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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; class InternetConnectionCubit extends Cubit { @@ -33,7 +34,8 @@ class InternetConnectionCubit extends Cubit { try { 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); return; } @@ -42,9 +44,13 @@ class InternetConnectionCubit extends Cubit { } Future _updateAccessStatus(List result) async { - final hasInternet = await _checkInternetAccess(result); - if (!hasInternet) showInternetConnectionToast(); - emit(state.copyWith(isInternetAvailable: hasInternet)); + try { + final hasInternet = await _checkInternetAccess(result); + if (!hasInternet) showInternetConnectionToast(); + emit(state.copyWith(isInternetAvailable: hasInternet)); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future _checkInternetAccess(List result) async { @@ -57,7 +63,8 @@ class InternetConnectionCubit extends Cubit { try { final response = await http.get(uri).timeout(const Duration(seconds: 3)); return response.statusCode >= 100 && response.statusCode < 600; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); continue; } } diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index 2f457269..6f06c668 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -32,5 +32,5 @@ Future init({required AppBuildType buildType}) async { CubitRegistrations.registerAll(); // Регистрация WebSocket менеджера - sl.registerLazySingleton(() => WsManager(wsService: sl())); + sl.registerLazySingleton(() => WsManager(wsService: sl(), errorReporter: sl())); } diff --git a/lib/core/routing/other_routes.dart b/lib/core/routing/other_routes.dart index 9d5200e3..58369798 100644 --- a/lib/core/routing/other_routes.dart +++ b/lib/core/routing/other_routes.dart @@ -211,7 +211,7 @@ class OtherRoutes { builder: (context, state) { final uuidFromQuery = state.uri.queryParameters['uuid']; if (uuidFromQuery != null) { - return ChatScreen(uuid: uuidFromQuery, sendInitialMessage: false); + return ChatScreen(uuid: uuidFromQuery, sendInitialMessage: false); } final data = state.extra as Map; final uuid = data['uuid'] as String; @@ -268,6 +268,11 @@ class OtherRoutes { path: InfoByOrderScreen.path, name: InfoByOrderScreen.name, 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; final planId = data['planId'] as int; final successfulPayment = data['successfulPayment'] as bool?; diff --git a/lib/core/views/cubit/views_cubit.dart b/lib/core/views/cubit/views_cubit.dart index ee15af22..64cfefe2 100644 --- a/lib/core/views/cubit/views_cubit.dart +++ b/lib/core/views/cubit/views_cubit.dart @@ -1,4 +1,6 @@ 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/repository/views_repository.dart'; import 'package:eatvio_app/features/feed/models/view_model.dart'; @@ -86,9 +88,13 @@ class ViewsCubit extends Cubit { } Future _viewContent({required ViewableContentType type, required List ids}) async { - await viewsRepository.viewContent( - viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids), - ); + try { + await viewsRepository.viewContent( + viewModel: ViewModel(type: type.getStringifiedVersion(), ids: ids), + ); + } catch (e, st) { + await reportFrom(e, st, this); + } } void _scheduleDebouncedSend() { diff --git a/lib/core/views/repository/views_repository.dart b/lib/core/views/repository/views_repository.dart index 869819ca..398f8703 100644 --- a/lib/core/views/repository/views_repository.dart +++ b/lib/core/views/repository/views_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/core/views/service/views_service.dart'; import 'package:eatvio_app/features/feed/models/view_model.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ViewsRepository { final ViewsService viewsService; @@ -8,11 +7,7 @@ class ViewsRepository { ViewsRepository({required this.viewsService}); Future viewContent({required ViewModel viewModel}) async { - try { - await viewsService.viewContent(viewModel: viewModel); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await viewsService.viewContent(viewModel: viewModel); + } } diff --git a/lib/core/websockets/ws_manager.dart b/lib/core/websockets/ws_manager.dart index cf84354c..cb90f4f8 100644 --- a/lib/core/websockets/ws_manager.dart +++ b/lib/core/websockets/ws_manager.dart @@ -9,14 +9,16 @@ import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/websockets/ws_service.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/notifications/presentation/notifications_cubit/notifications_cubit.dart'; class WsManager { final WsService wsService; + final ErrorReporter _errorReporter; - WsManager({required this.wsService}); + WsManager({required this.wsService, required ErrorReporter errorReporter}) + : _errorReporter = errorReporter; PusherChannelsClient? client; int? userId; @@ -114,7 +116,7 @@ class WsManager { _subscribe(); await _connectCubitsToStreams(); } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); + await _errorReporter.report(e, st, origin: 'WsManager'); log(e.toString(), stackTrace: st); } } @@ -130,7 +132,7 @@ class WsManager { await sl().connectToWebSocket(); } catch (e, st) { log('WsManager._connectCubitsToStreams: $e', stackTrace: st); - await Sentry.captureException(e, stackTrace: st); + await _errorReporter.report(e, st, origin: 'WsManager'); } } diff --git a/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart b/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart index 942b3191..f36f43cf 100644 --- a/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart +++ b/lib/features/auth/presentation/bloc/auth_cubit/auth_cubit.dart @@ -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/profile/repositories/profile_repository.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'; class AuthCubit extends Cubit { @@ -26,7 +27,8 @@ class AuthCubit extends Cubit { emit(AuthState.loaded(token: token)); await TokenStorage.saveTokenInStorage(token); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Неверный адрес электронной почты или пароль'); emit(AuthState.error(errorMessage: '$e')); @@ -37,22 +39,30 @@ class AuthCubit extends Cubit { Future initializeUser() async { try { await registerAnonymousPushToken(); - FlutterNativeSplash.remove(); - } catch (e) { - log('Error in initializeUser: $e'); - FlutterNativeSplash.remove(); + FlutterNativeSplash.remove(); + } catch (e, st) { + log('Error in initializeUser: $e', stackTrace: st); + FlutterNativeSplash.remove(); } } Future registerAnonymousPushToken() async { - final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false; - if (!hasAnonymousTokenBeenSent) { - await firebaseApi.registerAnonymousPushToken(); - prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true); + try { + final hasAnonymousTokenBeenSent = prefs.getBool(PrefsKeys.hasAnonymousTokenBeenSent) ?? false; + if (!hasAnonymousTokenBeenSent) { + await firebaseApi.registerAnonymousPushToken(); + prefs.setBool(PrefsKeys.hasAnonymousTokenBeenSent, true); + } + } catch (e, st) { + await reportFrom(e, st, this); } } Future registerPushToken() async { - await firebaseApi.registerPushToken(); + try { + await firebaseApi.registerPushToken(); + } catch (e, st) { + await reportFrom(e, st, this); + } } } diff --git a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart index a066c959..45d00f13 100644 --- a/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart +++ b/lib/features/auth/presentation/bloc/oauth_cubit/oauth_cubit.dart @@ -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/repositories/auth_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class OAuthCubit extends Cubit with RegistrationDateCacheMixin { final AuthRepository authRepository; @@ -29,7 +30,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { log('OAuthCubit: Got token model: ${tokenModel.token}'); TokenStorage.saveTokenInStorage(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'); // Проверяем, является ли ошибка ошибкой валидации email/phone final errors = e.response?.data['errors'] as Map?; @@ -62,7 +64,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { } else { emit(OAuthState.error(errorMessage: e.toString())); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log('OAuthCubit: Error occurred: $e'); emit(OAuthState.error(errorMessage: e.toString())); } @@ -103,7 +106,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { TokenStorage.saveTokenInStorage(token); cacheRegistrationDate(DateTime.now()); 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())); final errors = e.response?.data['errors'] as Map?; final emailErrors = errors?['email'] as List?; @@ -153,7 +157,8 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { // Сбрасываем состояние после успешного запроса кода emit(const OAuthState()); return response; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log('OAuthCubit: Error requesting verification code: $e'); emit(OAuthState.error(errorMessage: e.toString())); return null; @@ -200,10 +205,10 @@ class OAuthCubit extends Cubit with RegistrationDateCacheMixin { log('OAuthCubit: OAuth verification confirmed successfully, token received'); TokenStorage.saveTokenInStorage(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'); emit(OAuthState.error(errorMessage: e.toString())); - rethrow; // Пробрасываем ошибку дальше, чтобы UI мог её обработать } } } diff --git a/lib/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart b/lib/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart index 136ca9b8..bb6be7fe 100644 --- a/lib/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart +++ b/lib/features/auth/presentation/bloc/reset_password_cubit/reset_password_cubit.dart @@ -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/repositories/reset_password_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ResetPasswordCubit extends Cubit { final ResetPasswordRepository resetPasswordRepository; @@ -15,7 +16,8 @@ class ResetPasswordCubit extends Cubit { } else { emit(const ResetPasswordState.codeSendError()); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const ResetPasswordState.codeSendError()); } } @@ -28,7 +30,8 @@ class ResetPasswordCubit extends Cubit { } else { emit(const ResetPasswordState.resetPasswordError()); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const ResetPasswordState.resetPasswordError()); } } diff --git a/lib/features/auth/repositories/auth_repository.dart b/lib/features/auth/repositories/auth_repository.dart index 6c2b4c15..eec5b30a 100644 --- a/lib/features/auth/repositories/auth_repository.dart +++ b/lib/features/auth/repositories/auth_repository.dart @@ -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/token_model.dart'; import 'package:eatvio_app/features/auth/services/auth_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class AuthRepository { final AuthService authService; @@ -15,14 +14,10 @@ class AuthRepository { AuthRepository({required this.authService}); Future getTokenByCredentials(String email, String password) async { - try { - return await authService.getTokenByCredentials( - credentials: CredentialsModel(email: email, password: password), - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await authService.getTokenByCredentials( + credentials: CredentialsModel(email: email, password: password), + ); + } Future signInViaSocialMedia({ @@ -31,14 +26,10 @@ class AuthRepository { required String refreshToken, required String clientId, }) async { - try { - return await authService.signInViaSocialMedia( - body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId), - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await authService.signInViaSocialMedia( + body: OAuthLoginRequest(provider: provider, accessToken: accessToken, refreshToken: refreshToken, clientId: clientId), + ); + } Future signUpViaSocialMedia({ @@ -56,47 +47,39 @@ class AuthRepository { required int height, required String clientId, }) async { - try { - return (await authService.signUpViaSocialMedia( - body: OAuthModel( - provider: provider, - accessToken: accessToken, - id: id, - refreshToken: refreshToken, - firstName: firstName, - lastName: lastName, - email: email, - phone: phone, - dateOfBirth: dateOfBirth, - gender: gender == 'М' ? 'male' : 'female', - weight: weight, - height: height, - clientId: clientId, - ), - ))['token']! - as String; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await authService.signUpViaSocialMedia( + body: OAuthModel( + provider: provider, + accessToken: accessToken, + id: id, + refreshToken: refreshToken, + firstName: firstName, + lastName: lastName, + email: email, + phone: phone, + dateOfBirth: dateOfBirth, + gender: gender == 'М' ? 'male' : 'female', + weight: weight, + height: height, + clientId: clientId, + ), + ))['token']! + as String; + } Future requestOAuthVerification({String? email, String? phone}) async { - try { - Map? body; - if (email != null) { - body = {'email': email}; - } else if (phone != null) { - body = {'phone': phone}; - } - if (body != null) { - return await authService.requestOAuthVerification(body: body); - } - return null; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + Map? body; + if (email != null) { + body = {'email': email}; + } else if (phone != null) { + body = {'phone': phone}; } + if (body != null) { + return await authService.requestOAuthVerification(body: body); + } + return null; + } Future confirmOAuthVerification({ @@ -116,33 +99,28 @@ class AuthRepository { required int weight, required int height, }) async { - try { - final request = OAuthVerificationConfirmRequest( - provider: provider, - accessToken: accessToken, - refreshToken: refreshToken, - clientId: clientId, - id: id, - email: email, - phone: phone, - code: code, - verificationType: verificationType, - firstName: firstName, - lastName: lastName, - dateOfBirth: dateOfBirth, - gender: gender, - weight: weight, - height: height, - ); - // Логируем запрос перед отправкой - log( - 'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone', - ); - return await authService.confirmOAuthVerification(body: request); - } catch (e, s) { - log('AuthRepository: Error in confirmOAuthVerification: $e'); - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final request = OAuthVerificationConfirmRequest( + provider: provider, + accessToken: accessToken, + refreshToken: refreshToken, + clientId: clientId, + id: id, + email: email, + phone: phone, + code: code, + verificationType: verificationType, + firstName: firstName, + lastName: lastName, + dateOfBirth: dateOfBirth, + gender: gender, + weight: weight, + height: height, + ); + // Логируем запрос перед отправкой + log( + 'AuthRepository: Sending OAuth verification confirm request: provider=$provider, verificationType=$verificationType, code=$code, email=$email, phone=$phone', + ); + return await authService.confirmOAuthVerification(body: request); + } } diff --git a/lib/features/auth/repositories/reset_password_repository.dart b/lib/features/auth/repositories/reset_password_repository.dart index d04270cb..2cccf86a 100644 --- a/lib/features/auth/repositories/reset_password_repository.dart +++ b/lib/features/auth/repositories/reset_password_repository.dart @@ -1,5 +1,4 @@ import 'package:eatvio_app/features/auth/services/reset_password_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ResetPasswordRepository { final ResetPasswordService resetPasswordService; @@ -9,22 +8,18 @@ class ResetPasswordRepository { }); Future sendEmailCodeForPasswordChange(String email) async { - try { - final response = await resetPasswordService.sendEmailCodeForPasswordChange( - data: { - 'email': email, - }, - ); - int statusCode = response.response.statusCode ?? 0; - if (statusCode >= 200 && statusCode < 300) { - return true; - } else { - return false; - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final response = await resetPasswordService.sendEmailCodeForPasswordChange( + data: { + 'email': email, + }, + ); + int statusCode = response.response.statusCode ?? 0; + if (statusCode >= 200 && statusCode < 300) { + return true; + } else { + return false; } + } Future confirmPasswordChange( @@ -33,24 +28,20 @@ class ResetPasswordRepository { String passwordConfirmation, String email, ) async { - try { - final response = await resetPasswordService.confirmPasswordChange( - data: { - 'code': code, - 'password': password, - 'password_confirmation': passwordConfirmation, - 'email': email, - }, - ); - int statusCode = response.response.statusCode ?? 0; - if (statusCode >= 200 && statusCode < 300) { - return true; - } else { - return false; - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final response = await resetPasswordService.confirmPasswordChange( + data: { + 'code': code, + 'password': password, + 'password_confirmation': passwordConfirmation, + 'email': email, + }, + ); + int statusCode = response.response.statusCode ?? 0; + if (statusCode >= 200 && statusCode < 300) { + return true; + } else { + return false; } + } } diff --git a/lib/features/auth/sdk/social_auth_facade.dart b/lib/features/auth/sdk/social_auth_facade.dart index af787e61..341e98a2 100644 --- a/lib/features/auth/sdk/social_auth_facade.dart +++ b/lib/features/auth/sdk/social_auth_facade.dart @@ -8,10 +8,11 @@ import 'package:eatvio_app/features/auth/models/yandex_profile_model.dart'; import 'package:flutter/material.dart'; import 'package:eatvio_app/core/app_router.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:go_router/go_router.dart'; import 'package:http/http.dart' as http; -import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:vk_id/vk_id.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); } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); + await sl().report(e, s, origin: 'SocialAuthFacade'); return null; } } @@ -161,7 +162,7 @@ class SocialAuthFacade { ); } catch (e, s) { log(e.toString()); - await Sentry.captureException(e, stackTrace: s); + await sl().report(e, s, origin: 'SocialAuthFacade'); } return null; } @@ -209,7 +210,7 @@ class SocialAuthFacade { return await _parseVKAuthResult(redirectParams: redirectParams, controller: controller, codeVerifier: codeVerifier, clientId: clientIdStr); } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); + await sl().report(e, s, origin: 'SocialAuthFacade'); return null; } } diff --git a/lib/features/barcode/repositories/barcode_repository.dart b/lib/features/barcode/repositories/barcode_repository.dart index 030eca7e..4b03212e 100644 --- a/lib/features/barcode/repositories/barcode_repository.dart +++ b/lib/features/barcode/repositories/barcode_repository.dart @@ -1,7 +1,6 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/barcode/models/barcode_product_model.dart'; import 'package:eatvio_app/features/barcode/services/barcode_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class BarcodeRepository { final BarcodeService barcodeService; @@ -15,49 +14,46 @@ class BarcodeRepository { /// Получить информацию о продукте по штрихкоду /// Возвращает либо данные продукта, либо статус задачи обработки Future getProductByBarcode(String barcode) async { - try { - final response = await dio.get('/api/gsone/barcode/$barcode'); - - if (response.statusCode == 200) { - // Данные найдены в БД - final product = BarcodeProductModel.fromJson(response.data as Map); - return BarcodeResult.success(product); - } else if (response.statusCode == 202) { - // Запущена задача обработки - final taskStatus = BarcodeTaskStatusModel.fromJson(response.data as Map); - return BarcodeResult.processing(taskStatus.taskId ?? ''); - } - - 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); - return BarcodeResult.processing(taskStatus.taskId ?? ''); - } - await Sentry.captureException(e, stackTrace: s); - rethrow; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final response = await dio.get>( + '/api/gsone/barcode/$barcode', + options: Options(validateStatus: (_) => true), + ); + + final code = response.statusCode ?? 0; + final data = response.data; + if (data is! Map) { + throw DioException( + requestOptions: response.requestOptions, + response: response, + message: 'Invalid barcode response', + ); } + 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 getTaskStatus(String taskId) async { - try { - final statusModel = await barcodeService.getTaskStatus(taskId: taskId); - - if (statusModel.status == 'completed' && statusModel.data != null) { - return BarcodeTaskStatus.completed(statusModel.data!); - } else if (statusModel.status == 'failed') { - return BarcodeTaskStatus.failed(); - } else { - return BarcodeTaskStatus.processing(); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final statusModel = await barcodeService.getTaskStatus(taskId: taskId); + + if (statusModel.status == 'completed' && statusModel.data != null) { + return BarcodeTaskStatus.completed(statusModel.data!); + } else if (statusModel.status == 'failed') { + return BarcodeTaskStatus.failed(); + } else { + return BarcodeTaskStatus.processing(); } } } @@ -109,4 +105,3 @@ enum BarcodeTaskStatusType { processing, failed, } - diff --git a/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart b/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart index 844c8043..42cc5006 100644 --- a/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart +++ b/lib/features/blog_post/presentation/cubit/blog_post_cubit.dart @@ -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/feed/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class BlogPostCubit extends Cubit { final FeedRepository feedRepository; @@ -16,6 +17,7 @@ class BlogPostCubit extends Cubit { emit(state.copyWith(blogPostModel: postModel, isLoading: false)); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(isLoading: false)); } @@ -37,6 +39,7 @@ class BlogPostCubit extends Cubit { await feedRepository.likeBlogPost(id: current.id); } } catch (e, st) { + await reportFrom(e, st, this); // rollback on error log(e.toString(), stackTrace: st); emit(state.copyWith(blogPostModel: current)); diff --git a/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart b/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart index 2617b664..5d04bdaf 100644 --- a/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart +++ b/lib/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart @@ -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/repositories/blog_post_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; -class BlogPostCreateCubit extends BaseCubit { +class BlogPostCreateCubit extends Cubit { final BlogPostRepository blogPostRepository; BlogPostCreateCubit({required this.blogPostRepository}) : super(const BlogPostCreateState()); @@ -15,7 +18,8 @@ class BlogPostCreateCubit extends BaseCubit { emit(const BlogPostCreateState.loaded()); return true; } catch (error, stackTrace) { - handleError(error, stackTrace); + log('BlogPostCreateCubit: $error', stackTrace: stackTrace); + await reportFrom(error, stackTrace, this); emit(BlogPostCreateState.error(errorMessage: error.toString())); return false; } @@ -28,7 +32,8 @@ class BlogPostCreateCubit extends BaseCubit { emit(const BlogPostCreateState.loaded()); return true; } catch (error, stackTrace) { - handleError(error, stackTrace); + log('BlogPostCreateCubit: $error', stackTrace: stackTrace); + await reportFrom(error, stackTrace, this); emit(BlogPostCreateState.error(errorMessage: error.toString())); return false; } @@ -39,7 +44,8 @@ class BlogPostCreateCubit extends BaseCubit { await blogPostRepository.deleteBlogPost(id); return true; } catch (error, stackTrace) { - handleError(error, stackTrace); + log('BlogPostCreateCubit: $error', stackTrace: stackTrace); + await reportFrom(error, stackTrace, this); return false; } } diff --git a/lib/features/blog_post_create/repositories/blog_post_repository.dart b/lib/features/blog_post_create/repositories/blog_post_repository.dart index 7f1fc49b..337854dc 100644 --- a/lib/features/blog_post_create/repositories/blog_post_repository.dart +++ b/lib/features/blog_post_create/repositories/blog_post_repository.dart @@ -1,5 +1,5 @@ -import 'dart:developer'; import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:eatvio_app/core/app_config.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:flutter_image_compress/flutter_image_compress.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class BlogPostRepository { final BlogPostService blogPostService; @@ -23,48 +22,30 @@ class BlogPostRepository { String? description, XFile? image, ) async { - FormData formData = FormData.fromMap({ + final formData = FormData.fromMap({ if (title != null && title.isNotEmpty) 'title': title, if (description != null && description.isNotEmpty) 'description_md': description, 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), }); - try { - return await blogPostService.createBlogPost( - formData: formData, - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return blogPostService.createBlogPost( + formData: formData, + ); } Future downloadImage(String imageUrl) async { - try { - // Загрузка изображения - final response = await http.get(Uri.parse('${sl().baseUrl}/storage/$imageUrl')); + final response = await http.get(Uri.parse('${sl().baseUrl}/storage/$imageUrl')); - if (response.statusCode == 200) { - // Получение временной директории - final directory = await getTemporaryDirectory(); - // Создание файла в временной директории - final filePath = '${directory.path}/image.jpg'; // Вы можете изменить название файла - final file = File(filePath); - // Запись данных в файл - await file.writeAsBytes(response.bodyBytes); + if (response.statusCode == 200) { + final directory = await getTemporaryDirectory(); + final filePath = '${directory.path}/image.jpg'; + final file = File(filePath); + await file.writeAsBytes(response.bodyBytes); - // Возвращаем объект XFile - return XFile(file.path); - } else { - log('Ошибка загрузки изображения: ${response.statusCode}'); - return null; - } - } catch (e, s) { - log('Произошла ошибка: $e'); - await Sentry.captureException(e, stackTrace: s); - return null; + return XFile(file.path); } + return null; } Future updateBlogPost( @@ -75,9 +56,9 @@ class BlogPostRepository { XFile? image, String? oldImageUrl, ) async { - FormData formData = FormData.fromMap({ + final formData = FormData.fromMap({ 'title': title, - 'description_md': description, + 'description_md': description, 'video_url': videoUrl, 'image': image != null ? await MultipartFile.fromFile((await ImageCompressUtility.compressFile(File(image.path))).path, filename: image.name) @@ -86,23 +67,13 @@ class BlogPostRepository { : null, }); - try { - return await blogPostService.updateBlogPost( - id: id, - formData: formData, - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return blogPostService.updateBlogPost( + id: id, + formData: formData, + ); } Future deleteBlogPost(int id) async { - try { - await blogPostService.deleteBlogPost(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await blogPostService.deleteBlogPost(id: id); } } diff --git a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart index a0910fcb..2a51eca4 100644 --- a/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart +++ b/lib/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart @@ -1,8 +1,11 @@ +import 'dart:async'; + import 'package:appmetrica_plugin/appmetrica_plugin.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/profile/repositories/profile_repository.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:permission_handler/permission_handler.dart'; @@ -28,7 +31,8 @@ class ProfileAvatarCubit extends Cubit { return age; } - } catch (e) { + } catch (e, st) { + unawaited(reportFrom(e, st, this)); return 0; } return 0; @@ -51,10 +55,12 @@ class ProfileAvatarCubit extends Cubit { ]); AppMetrica.setUserProfileID(profile.slug); 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'); emit(ProfileAvatarState.error(errorMessage: e.toString())); } diff --git a/lib/features/calendar/presentation/bloc/calendar_cubit.dart b/lib/features/calendar/presentation/bloc/calendar_cubit.dart index 84fa9f0e..055d133a 100644 --- a/lib/features/calendar/presentation/bloc/calendar_cubit.dart +++ b/lib/features/calendar/presentation/bloc/calendar_cubit.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_state.dart'; import 'package:eatvio_app/features/calendar/repositories/calendar_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class CalendarCubit extends Cubit { final CalendarRepository calendarRepository; @@ -12,7 +13,8 @@ class CalendarCubit extends Cubit { try { final calendarList = await calendarRepository.getCalendar(); emit(CalendarState.loaded(calendarList: calendarList)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(CalendarState.error(errorMessage: e.toString())); } } diff --git a/lib/features/calendar/repositories/calendar_repository.dart b/lib/features/calendar/repositories/calendar_repository.dart index 87af6b20..4c8178a3 100644 --- a/lib/features/calendar/repositories/calendar_repository.dart +++ b/lib/features/calendar/repositories/calendar_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/calendar/models/calendar_model.dart'; import 'package:eatvio_app/features/calendar/services/calendar_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class CalendarRepository { final CalendarService calendarService; @@ -8,11 +7,7 @@ class CalendarRepository { CalendarRepository({required this.calendarService}); Future> getCalendar() async { - try { - return await calendarService.getCalendar(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await calendarService.getCalendar(); + } } diff --git a/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart index aa6689a1..2d16bb09 100644 --- a/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart +++ b/lib/features/cancel_plan_bottom_sheet/cubit/cancel_plan_cubit.dart @@ -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/repository/cancel_plan_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class CancelPlanCubit extends Cubit { final CancelPlanRepository cancelPlanRepository; @@ -18,6 +19,7 @@ class CancelPlanCubit extends Cubit { await cancelPlanRepository.cancelChosenPlan(id: id, body: body); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } return false; diff --git a/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart b/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart index 2a7ccc66..1b34e117 100644 --- a/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart +++ b/lib/features/cancel_plan_bottom_sheet/repository/cancel_plan_repository.dart @@ -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/service/cancel_plan_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class CancelPlanRepository { final CancelPlanService cancelService; @@ -9,11 +8,7 @@ class CancelPlanRepository { required int id, CancelationModel? body }) async { - try { - await cancelService.cancelChosenPlan(id: id, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await cancelService.cancelChosenPlan(id: id, body: body); + } } diff --git a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart index d77eacc7..d61f8ddc 100644 --- a/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart +++ b/lib/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart @@ -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/plans/models/chosen_plan_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; import 'package:uuid/uuid.dart'; class ChatRoomCubit extends Cubit { @@ -64,7 +65,8 @@ class ChatRoomCubit extends Cubit { ); currentPage++; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); } } @@ -75,6 +77,7 @@ class ChatRoomCubit extends Cubit { final cards = await cardsRepository.getCards(); _setCard(cards: cards); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } } @@ -111,7 +114,8 @@ class ChatRoomCubit extends Cubit { if (chosenPlan?.consultantId == chatRoom.users.sender.id) { await checkCards(); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); typingSubscription?.cancel(); messagesSubscription?.cancel(); @@ -121,13 +125,17 @@ class ChatRoomCubit extends Cubit { } Future updateChosenPlanState() async { - final currentState = state; - if (currentState is ChatRoomStateLoaded) { - final chosenPlanId = currentState.chatRoom.chosenPlanId; - if (chosenPlanId != null) { - final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId); - emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan)); + try { + final currentState = state; + if (currentState is ChatRoomStateLoaded) { + final chosenPlanId = currentState.chatRoom.chosenPlanId; + if (chosenPlanId != null) { + final chosenPlan = await chatRepository.getChosenPlan(id: chosenPlanId); + emit(currentState.copyWith.chatRoom(chosenPlan: chosenPlan)); + } } + } catch (e, st) { + await reportFrom(e, st, this); } } @@ -142,6 +150,7 @@ class ChatRoomCubit extends Cubit { emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: chosenPlan)); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -151,7 +160,8 @@ class ChatRoomCubit extends Cubit { try { await chatRepository.cancelChosenPlan(id: planId); emit((state as ChatRoomStateLoaded).copyWith.chatRoom(chosenPlan: null)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const ChatRoomState.error(errorMessage: 'Ошибка получеия данных')); } } @@ -238,7 +248,8 @@ class ChatRoomCubit extends Cubit { }).toList(), ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( (state as ChatRoomStateLoaded).copyWith( chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) { @@ -253,6 +264,7 @@ class ChatRoomCubit extends Cubit { sl().getChatRooms(); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -274,6 +286,7 @@ class ChatRoomCubit extends Cubit { } return false; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -286,6 +299,7 @@ class ChatRoomCubit extends Cubit { final paymentModel = await chatRepository.getPaymentLink(id: planId); return paymentModel.paymentUrl; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return null; } @@ -300,7 +314,8 @@ class ChatRoomCubit extends Cubit { body: PlanComplainModel(description: description), ); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); return false; } @@ -312,7 +327,8 @@ class ChatRoomCubit extends Cubit { if (planId == null) return false; await chatRepository.finishChosenPlan(id: planId); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); return false; } @@ -400,7 +416,8 @@ class ChatRoomCubit extends Cubit { }).toList(), ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( (state as ChatRoomStateLoaded).copyWith( chatMessages: (state as ChatRoomStateLoaded).chatMessages.map((elem) { @@ -414,6 +431,7 @@ class ChatRoomCubit extends Cubit { } sl().getChatRooms(); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } } diff --git a/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart index 174108b3..52e1631e 100644 --- a/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart +++ b/lib/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart @@ -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/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ChatsCubit extends Cubit { final ChatRepository chatRepository; @@ -31,10 +32,14 @@ class ChatsCubit extends Cubit { late int lastPage; Future connectToWebSocket() async { - await newIncomingMessagesSubscription?.cancel(); - newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) { - _handleNewMessageViaSocket(event.data); - }); + try { + await newIncomingMessagesSubscription?.cancel(); + newIncomingMessagesSubscription = wsManager.newMessagesStream?.listen((event) { + _handleNewMessageViaSocket(event.data); + }); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future getChatRoomsWithLoading({String? query}) async { @@ -53,7 +58,8 @@ class ChatsCubit extends Cubit { ), ); page++; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(errorMessage: e.toString(), state: BaseState.error), ); @@ -76,6 +82,7 @@ class ChatsCubit extends Cubit { ); page++; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit( state.copyWith(errorMessage: e.toString(), state: BaseState.error), @@ -99,6 +106,7 @@ class ChatsCubit extends Cubit { ); page++; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit( state.copyWith( @@ -132,33 +140,37 @@ class ChatsCubit extends Cubit { } Future _handleNewMessageViaSocket(dynamic data) async { - final json = jsonDecode(data); - final message = ChatMessageModel.fromJson(json['message']); + try { + final json = jsonDecode(data); + final message = ChatMessageModel.fromJson(json['message']); - final currentChatRooms = [...state.chatRooms]; + final currentChatRooms = [...state.chatRooms]; - bool wasChanged = false; - bool willIncrementUnreadChats = false; - final updatedChatRooms = currentChatRooms.map((chatRoom) { - if (chatRoom.id == message.roomId) { - wasChanged = true; - if (chatRoom.unreadMessagesCount == 0) { - willIncrementUnreadChats = true; + bool wasChanged = false; + bool willIncrementUnreadChats = false; + final updatedChatRooms = currentChatRooms.map((chatRoom) { + if (chatRoom.id == message.roomId) { + wasChanged = true; + if (chatRoom.unreadMessagesCount == 0) { + 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) { - getChatRooms(); - } else { - emit( - state.copyWith( - chatRooms: updatedChatRooms, - chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount, - ), - ); + if (!wasChanged && _query == null) { + getChatRooms(); + } else { + emit( + state.copyWith( + chatRooms: updatedChatRooms, + chatsWithUnreadMessagesCount: willIncrementUnreadChats ? state.chatsWithUnreadMessagesCount + 1 : state.chatsWithUnreadMessagesCount, + ), + ); + } + } catch (e, st) { + await reportFrom(e, st, this); } } diff --git a/lib/features/chat/repositories/chat_repository.dart b/lib/features/chat/repositories/chat_repository.dart index 36682421..fc079241 100644 --- a/lib/features/chat/repositories/chat_repository.dart +++ b/lib/features/chat/repositories/chat_repository.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; import 'package:dio/dio.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/plans/models/chosen_plan_model.dart'; import 'package:http_parser/http_parser.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ChatRepository { final ChatService chatService; @@ -34,160 +32,104 @@ class ChatRepository { String? data, List? files, }) async { - try { - final multipartFiles = []; + final multipartFiles = []; - if (files != null) { - for (final file in files) { - final fileBytes = await file.readAsBytes(); - final multipartFile = MultipartFile.fromBytes( - fileBytes, - filename: file.path.split('/').last, - contentType: MediaType('application', 'octet-stream'), - ); - multipartFiles.add(multipartFile); - } + if (files != null) { + for (final file in files) { + final fileBytes = await file.readAsBytes(); + final multipartFile = MultipartFile.fromBytes( + fileBytes, + filename: file.path.split('/').last, + contentType: MediaType('application', 'octet-stream'), + ); + 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 getChatRoom(String uuid) async { - try { - return (await chatService.getChatRoom(uuid: uuid)); - } catch (e, st) { - log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); - rethrow; - } + return (await chatService.getChatRoom(uuid: uuid)); + } Future getChatRooms({String? query, int page = 1}) async { - try { - return (await chatService.getChatRooms(query: query, page: page)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await chatService.getChatRooms(query: query, page: page)); + } Future getChatMessages(String id, int page) async { - try { - return (await chatService.getChatMessages(id: id, page: page)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await chatService.getChatMessages(id: id, page: page)); + } Future getChatRoomIdByUserSlug({required String slug}) async { - try { - return (await chatService.getChatRoomIdByUserSlug(user: slug)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await chatService.getChatRoomIdByUserSlug(user: slug)); + } Future markMessagesAsRead(String id, List messageIds) async { - try { - await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds}); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await chatService.markMessagesAsRead(id: id, body: {'messages_ids': messageIds}); + } Future cancelChosenPlan({ required int id, }) async { - try { - await chatService.cancelChosenPlan(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await chatService.cancelChosenPlan(id: id); + } Future choseStartPlanDate({required int id, required Map date}) async { - try { - await chatService.choseStartPlanDate(id: id, date: date); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await chatService.choseStartPlanDate(id: id, date: date); + } Future getPaymentLink({ required int id, }) async { - try { - return await chatService.getPaymentLink(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await chatService.getPaymentLink(id: id); + } Future getChosenPlan({ required int id, }) async { - try { - return await chatService.getChosenPlan(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await chatService.getChosenPlan(id: id); + } Future finishChosenPlan({ required int id, }) async { - try { - await chatService.finishChosenPlan(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await chatService.finishChosenPlan(id: id); + } Future complainAboutConsultation({ required int id, required PlanComplainModel body, }) async { - try { - await chatService.complainAboutConsultation(id: id, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await chatService.complainAboutConsultation(id: id, body: body); + } Future getChatByUserSlug(String slug) async { - try { - final response = await chatService.getChatByUserSlug(slug: slug); - if (response.response.statusCode == 404) { - return '404'; - } else { - return response.data['id']; - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - return 'error'; + final response = await chatService.getChatByUserSlug(slug: slug); + if (response.response.statusCode == 404) { + return '404'; } + return response.data['id']; } } diff --git a/lib/features/complaining/presentation/cubit/complaining_cubit.dart b/lib/features/complaining/presentation/cubit/complaining_cubit.dart index 896fa6e9..b531e6b8 100644 --- a/lib/features/complaining/presentation/cubit/complaining_cubit.dart +++ b/lib/features/complaining/presentation/cubit/complaining_cubit.dart @@ -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/repositories/complaining_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ComplainingCubit extends Cubit { final ComplainingRepository repository; @@ -81,6 +82,7 @@ class ComplainingCubit extends Cubit { } } } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } emit(state.copyWith(wasSentSuccessfully: false)); diff --git a/lib/features/complaining/repositories/complaining_repository.dart b/lib/features/complaining/repositories/complaining_repository.dart index 10a1cce3..d6a234c4 100644 --- a/lib/features/complaining/repositories/complaining_repository.dart +++ b/lib/features/complaining/repositories/complaining_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/complaining/models/complain_model.dart'; import 'package:eatvio_app/features/complaining/services/complaining_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ComplainingRepository { final ComplainingService service; @@ -11,12 +10,8 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendPlanComplain(slug: slug, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendPlanComplain(slug: slug, category: category, body: body); + } Future sendRecipeComplain({ @@ -24,12 +19,8 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendRecipeComplain(slug: slug, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendRecipeComplain(slug: slug, category: category, body: body); + } Future sendBlogPostsComplain({ @@ -37,12 +28,8 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendBlogPostsComplain(id: id, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendBlogPostsComplain(id: id, category: category, body: body); + } Future sendProfileComplain({ @@ -50,12 +37,8 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendProfileComplain(slug: slug, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendProfileComplain(slug: slug, category: category, body: body); + } Future sendDietComplain({ @@ -63,12 +46,8 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendDietComplain(id: id, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendDietComplain(id: id, category: category, body: body); + } Future sendCommentComplain({ @@ -76,11 +55,7 @@ class ComplainingRepository { required String category, required ComplainModel body, }) async { - try { - await service.sendCommentComplain(id: id, category: category, body: body); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await service.sendCommentComplain(id: id, category: category, body: body); + } } diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart index 76c2fd69..0410ae04 100644 --- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart +++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_cubit.dart @@ -5,6 +5,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/models/consulta 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:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class RegisterConsultantCubit extends Cubit { final ConsultantRepository consultantRepository; @@ -54,45 +55,73 @@ class RegisterConsultantCubit extends Cubit { requestModel: myRequestStatus, certPhotos: certPhotos, isUpdating: _update, + error: null, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); + emit(state.copyWith(loading: false, error: e.toString())); } } Future disableLoading() async { try { final certPhotos = await consultantRepository.getConsultantCertPhotos(); - emit(state.copyWith(loading: false, certPhotos: certPhotos)); - } catch (e) { + 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 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]) { - emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false)); + emit(state.copyWith(consultant: consultant, acceptOffer: acceptOffer, loading: false, error: null)); } Future registerConsultant() async { - if (state.newAddedPhotos.isNotEmpty) { - await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos); - } - if (state.deletedPhotoIds.isNotEmpty) { - for (final int id in state.deletedPhotoIds) { - await consultantRepository.deletePhoto(id); + emit(state.copyWith(error: null)); + try { + if (state.newAddedPhotos.isNotEmpty) { + await consultantRepository.storeConsultantCertPhotos(files: state.newAddedPhotos); } + 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 addCertPhotos(List files) async { if (files.isEmpty) { 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 removeCertPhoto(int? id, String? path) async { diff --git a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart index 45baa2c6..e9635eb6 100644 --- a/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart +++ b/lib/features/consultant/become_consultant/presentation/bloc/register_consultant_cubit/register_consultant_state.dart @@ -18,5 +18,6 @@ abstract class RegisterConsultantState with _$RegisterConsultantState { @Default([]) List certPhotos, @Default([]) List newAddedPhotos, @Default([]) List deletedPhotoIds, + String? error, }) = _RegisterConsultantState; } diff --git a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart index 515728c1..a27e5c67 100644 --- a/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart +++ b/lib/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart @@ -1,4 +1,5 @@ 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/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'; @@ -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/social_networks_screen.dart'; import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/custom_text_style.dart'; import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -48,8 +50,37 @@ class _StepperBecomeConsultantScreenState extends State registrationConsultantCubit, child: BlocBuilder( builder: (context, state) { - if (state.loading == false) { + if (state.loading) { + return const Scaffold(body: Loader()); + } + if (state.error != null) { 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().reloadData(widget.slug), + ), + ); + } + return Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: Row( children: [ @@ -136,6 +167,9 @@ class _StepperBecomeConsultantScreenState extends State( context: context, builder: (context) => AlertDialog( actionsPadding: const EdgeInsets.symmetric(horizontal: 4), @@ -191,9 +225,7 @@ class _StepperBecomeConsultantScreenState extends State Navigator.of(context).pop(), ), ], ), @@ -282,9 +314,6 @@ class _StepperBecomeConsultantScreenState extends State registerConsultant({required ConsultantProfileModel consultant, bool update = false}) async { - try { - if (update) { - return await consultantService.consultantUpdate(consultantModel: consultant); - } else { - return await consultantService.consultantRegistration(consultantModel: consultant); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + if (update) { + return await consultantService.consultantUpdate(consultantModel: consultant); + } else { + return await consultantService.consultantRegistration(consultantModel: consultant); } + } Future getConsultantRequestStatus(String slug) async { - try { - return await consultantService.getConsultantRequestStatus(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await consultantService.getConsultantRequestStatus(slug: slug); + } Future getMyRequestStatus(String slug) async { - try { - return await consultantService.getMyRequestStatus(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await consultantService.getMyRequestStatus(slug: slug); + } Future> getConsultantCertPhotos() async { - try { - return await consultantService.getConsultantCertPhotos(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await consultantService.getConsultantCertPhotos(); + } Future> storeConsultantCertPhotos({required List files}) async { - try { - final multipartFiles = []; + final multipartFiles = []; - for (final file in files) { - final fileBytes = await file.readAsBytes(); - final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream')); - multipartFiles.add(multipartFile); - } - - if (multipartFiles.isEmpty) { - return []; - } - - return await consultantService.storeConsultantCertPhotos(photos: multipartFiles); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + for (final file in files) { + final fileBytes = await file.readAsBytes(); + final multipartFile = MultipartFile.fromBytes(fileBytes, filename: file.path.split('/').last, contentType: MediaType('application', 'octet-stream')); + multipartFiles.add(multipartFile); } + + if (multipartFiles.isEmpty) { + return []; + } + + return await consultantService.storeConsultantCertPhotos(photos: multipartFiles); + } Future hideConsultantCertPhoto(int id) async { - try { - await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id}); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await consultantService.hideConsultantCertPhoto(payload: {'cert_photo_id': id}); + } Future unHideConsultantCertPhoto(int id) async { - try { - await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id}); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await consultantService.unHideConsultantCertPhoto(payload: {'cert_photo_id': id}); + } Future deletePhoto(int id) async { - try { - await consultantService.deletePhoto(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await consultantService.deletePhoto(id: id); + } } diff --git a/lib/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart b/lib/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart index d3ff72a9..1580561c 100644 --- a/lib/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart +++ b/lib/features/consultant_rating/presentation/cubit/consultant_rating_cubit.dart @@ -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/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultantRatingCubit extends Cubit { final ConsultantRatingRepository repository; @@ -20,6 +21,7 @@ class ConsultantRatingCubit extends Cubit { final absoluteRating = await feedRepository.getConsultants((await profileRepository.getMyProfile()).name); emit(state.copyWith(isStatisticsLoading: false, statistics: statistics, error: null, absoluteRating: absoluteRating.first.totalScores)); } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(isStatisticsLoading: true, error: e.toString())); log(e.toString(), stackTrace: st); } @@ -31,6 +33,7 @@ class ConsultantRatingCubit extends Cubit { final scoreLogs = await repository.getScoreLogs(date: date); emit(state.copyWith(isRatingLoading: false, ratings: scoreLogs, error: null)); } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(isRatingLoading: true, error: e.toString())); log(e.toString(), stackTrace: st); } diff --git a/lib/features/consultant_rating/repositories/consultant_rating_repository.dart b/lib/features/consultant_rating/repositories/consultant_rating_repository.dart index 3d66e879..38bd5ec9 100644 --- a/lib/features/consultant_rating/repositories/consultant_rating_repository.dart +++ b/lib/features/consultant_rating/repositories/consultant_rating_repository.dart @@ -1,7 +1,6 @@ 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/services/consultant_rating_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ConsultantRatingRepository { final ConsultantRatingService service; @@ -9,21 +8,13 @@ class ConsultantRatingRepository { ConsultantRatingRepository(this.service); Future getConsultantStatistics({String? date}) async { - try { - return await service.getConsultantStatistics(date: date); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await service.getConsultantStatistics(date: date); + } Future> getScoreLogs({String? date}) async { - try { - final data = (await service.getScoreLogs(date: date)).data; - return data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final data = (await service.getScoreLogs(date: date)).data; + return data; + } } diff --git a/lib/features/consultations/presentation/cubit/consultations_cubit.dart b/lib/features/consultations/presentation/cubit/consultations_cubit.dart index 5972e8d9..80f8b206 100644 --- a/lib/features/consultations/presentation/cubit/consultations_cubit.dart +++ b/lib/features/consultations/presentation/cubit/consultations_cubit.dart @@ -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/repositories/consultations_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultationsCubit extends Cubit { final ConsultationsRepository consultationsRepository; @@ -14,6 +15,7 @@ class ConsultationsCubit extends Cubit { final consultations = await consultationsRepository.getConsultations(); emit(state.copyWith(consultations: consultations, state: BaseState.success)); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(state: BaseState.error)); } diff --git a/lib/features/consultations/repositories/consultations_repository.dart b/lib/features/consultations/repositories/consultations_repository.dart index 06c2ee39..f29c792e 100644 --- a/lib/features/consultations/repositories/consultations_repository.dart +++ b/lib/features/consultations/repositories/consultations_repository.dart @@ -1,7 +1,6 @@ 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/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/statistic_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}); Future> getConsultations() async { - try { - final consultations = await consultationService.getConsultations(); - return consultations.data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final consultations = await consultationService.getConsultations(); + return consultations.data; } Future> getFilteredConsultations({ @@ -26,44 +20,24 @@ class ConsultationsRepository { FiltersComparisonEnum? stateComparison, FiltersComparisonEnum? dateComparison, }) async { - try { - final model = await consultationService.getFilteredConsultations( - state: state, - date: date, - stateComparison: stateComparison, - dateComparison: dateComparison, - ); - return model.data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final model = await consultationService.getFilteredConsultations( + state: state, + date: date, + stateComparison: stateComparison, + dateComparison: dateComparison, + ); + return model.data; } Future getStatistic() async { - try { - return await consultationService.getStatistics(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return consultationService.getStatistics(); } Future> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async { - try { - return await consultationService.getConsultantFinances(date, dateComparison); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return consultationService.getConsultantFinances(date, dateComparison); } Future> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async { - try { - return await consultationService.getUserPurchases(date, dateComparison); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return consultationService.getUserPurchases(date, dateComparison); } } diff --git a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart index 4b7315ab..9788c7ce 100644 --- a/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart +++ b/lib/features/create_diet/presentation/cubit/create_diet_cubit.dart @@ -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/profile/repositories/profile_repository.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:http/http.dart' as http; -import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:path_provider/path_provider.dart'; class CreateDietCubit extends Cubit { @@ -29,9 +29,10 @@ class CreateDietCubit extends Cubit { Future setInitialData(Diet? diet) async { if (diet == null) return; - emit(state.copyWith(isLoading: true, isDietBeingEdited: true)); - dietId = diet.id; - final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id); + try { + emit(state.copyWith(isLoading: true, isDietBeingEdited: true)); + dietId = diet.id; + final dailyScheduleWithNutrition = await createDietRepository.getDietPortionsNutrition(diet.id); final countOfDays = diet.duration; final List>> dailySchedule = List.generate(countOfDays, (index) => [[], [], [], [], []]); @@ -66,7 +67,11 @@ class CreateDietCubit extends Cubit { 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) { @@ -112,7 +117,12 @@ class CreateDietCubit extends Cubit { } Future> 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}) { @@ -204,7 +214,8 @@ class CreateDietCubit extends Cubit { Future getDietForPreview() async { if (!_isFormValidForDraft()) return null; - final authorProfile = await profileRepository.getMyProfile(); + try { + final authorProfile = await profileRepository.getMyProfile(); final imageBytes = (await state.dietData.image?.readAsBytes()) ?? []; String image = ''; if (imageBytes.isNotEmpty && imageBytes.length <= _maxImageBytesForBase64) { @@ -297,19 +308,26 @@ class CreateDietCubit extends Cubit { isFavorite: false, dailyNutrition: dailyNutrition, ); - return diet; + return diet; + } catch (e, st) { + await reportFrom(e, st, this); + return null; + } } - Future downloadImageAsFile(String imageUrl) async { - final response = await http.get(Uri.parse(imageUrl)); - - if (response.statusCode == 200) { + Future downloadImageAsFile(String imageUrl) async { + try { + final response = await http.get(Uri.parse(imageUrl)); + if (response.statusCode != 200) { + throw Exception('Не удалось загрузить изображение'); + } final tempDir = await getTemporaryDirectory(); final filePath = '${tempDir.path}/${imageUrl.split('/').last}'; final file = File(filePath); - return await file.writeAsBytes(response.bodyBytes); - } else { - throw Exception('Не удалось загрузить изображение'); + return file.writeAsBytes(response.bodyBytes); + } catch (e, st) { + await reportFrom(e, st, this); + return null; } } @@ -353,7 +371,7 @@ class CreateDietCubit extends Cubit { emit(state.copyWith(published: dietState != DietState.draft, drafted: dietState == DietState.draft)); return null; } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(isDietPublishing: false)); } @@ -391,7 +409,7 @@ class CreateDietCubit extends Cubit { emit(state.copyWith(isDietBeingEdited: false)); return null; } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(isDietBeingEdited: false)); return 'error'; diff --git a/lib/features/create_diet/repositories/create_diet_repository.dart b/lib/features/create_diet/repositories/create_diet_repository.dart index feb37496..6898399a 100644 --- a/lib/features/create_diet/repositories/create_diet_repository.dart +++ b/lib/features/create_diet/repositories/create_diet_repository.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:dio/dio.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:sentry_flutter/sentry_flutter.dart'; class CreateDietRepository { final CreateDietService createDietService; @@ -62,48 +61,40 @@ class CreateDietRepository { dietMap.addAll({'name': name, 'duration': duration}); final formData = FormData.fromMap(dietMap); - try { - switch (state) { - case DietState.active: - return await createDietService.createPublishedDiet(formData: formData); - case DietState.draft: - return await createDietService.createDraftedDiet(formData: formData); - case DietState.personal: - return await createDietService.createPersonalDiet(formData: formData); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + switch (state) { + case DietState.active: + return await createDietService.createPublishedDiet(formData: formData); + case DietState.draft: + return await createDietService.createDraftedDiet(formData: formData); + case DietState.personal: + return await createDietService.createPersonalDiet(formData: formData); } + } Future>>> getDietPortionsNutrition(int dietId) async { - try { - final response = await createDietService.getDietPortionsNutrition(diet: dietId); - final data = response.data; - final dailySchedule = (data as Map).map((key, value) { - return MapEntry( - int.parse(key), - (value as Map).map((key, value) { - return MapEntry( - int.parse(key), - (value as Map).map((key, value) { - final portionMap = Map.from(value as Map); - final pnvid = portionMap['product_nutritional_value_id']; - if (pnvid is String) { - portionMap['product_nutritional_value_id'] = int.tryParse(pnvid); - } - return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap)); - }), - ); - }), - ); - }); - return dailySchedule; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final response = await createDietService.getDietPortionsNutrition(diet: dietId); + final data = response.data; + final dailySchedule = (data as Map).map((key, value) { + return MapEntry( + int.parse(key), + (value as Map).map((key, value) { + return MapEntry( + int.parse(key), + (value as Map).map((key, value) { + final portionMap = Map.from(value as Map); + final pnvid = portionMap['product_nutritional_value_id']; + if (pnvid is String) { + portionMap['product_nutritional_value_id'] = int.tryParse(pnvid); + } + return MapEntry(int.parse(key), DailySchedule.fromJson(portionMap)); + }), + ); + }), + ); + }); + return dailySchedule; + } Future updateDiet({ @@ -161,23 +152,16 @@ class CreateDietRepository { } final formData = FormData.fromMap(dietMap); - try { - switch (state) { - case DietState.active: - final response = await createDietService.updatePublished(diet: dietId, formData: formData); - return response.response.statusCode ?? 0; - case DietState.draft: - final response = await createDietService.updateDrafted(diet: dietId, formData: formData); - return response.response.statusCode ?? 0; - case DietState.personal: - final response = await createDietService.updatePersonal(diet: dietId, formData: formData); - return response.response.statusCode ?? 0; - } - } on DioException catch (e) { - return e.response?.statusCode ?? 0; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + switch (state) { + case DietState.active: + final response = await createDietService.updatePublished(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; + case DietState.draft: + final response = await createDietService.updateDrafted(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; + case DietState.personal: + final response = await createDietService.updatePersonal(diet: dietId, formData: formData); + return response.response.statusCode ?? 0; } } } diff --git a/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart b/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart index 21b07b68..be09737e 100644 --- a/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart +++ b/lib/features/create_plan/presentation/cubit/create_plan_cubit.dart @@ -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/repositories/create_plan_repository.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'; class CreatePlanCubit extends Cubit { @@ -457,7 +458,8 @@ class CreatePlanCubit extends Cubit { await createPlanRepository.createPlan(model: planMap); emit(state.copyWith(isLoading: false, planCreated: true)); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(state.copyWith(isLoading: false, planCreated: true)); } diff --git a/lib/features/create_plan/repositories/create_plan_repository.dart b/lib/features/create_plan/repositories/create_plan_repository.dart index 1011005e..17ed3298 100644 --- a/lib/features/create_plan/repositories/create_plan_repository.dart +++ b/lib/features/create_plan/repositories/create_plan_repository.dart @@ -1,6 +1,5 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class CreatePlanRepository { final CreatePlanService createPlanService; @@ -10,13 +9,9 @@ class CreatePlanRepository { Future createPlan({ required Map model, }) async { - try { - final formData = FormData.fromMap(model); - return await createPlanService.createPlan(formData: formData); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final formData = FormData.fromMap(model); + return await createPlanService.createPlan(formData: formData); + } } diff --git a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart index e7537af7..e65fe5ef 100644 --- a/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart +++ b/lib/features/create_recipe/presentation/bloc/create_recipe_cubit/create_recipe_cubit.dart @@ -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/repositories/glossary_repository.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:path_provider/path_provider.dart'; @@ -166,7 +166,7 @@ class CreateRecipeCubit extends Cubit { await recipeRepository.publishRecipe(model, update, slug); return true; } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -179,7 +179,7 @@ class CreateRecipeCubit extends Cubit { emit(state.copyWith(drafted: true)); return true; } catch (e, st) { - await Sentry.captureException(e, stackTrace: st); + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -204,16 +204,19 @@ class CreateRecipeCubit extends Cubit { )); } - Future downloadImageAsFile(String imageUrl) async { - final response = await http.get(Uri.parse(imageUrl)); - - if (response.statusCode == 200) { + Future downloadImageAsFile(String imageUrl) async { + try { + final response = await http.get(Uri.parse(imageUrl)); + if (response.statusCode != 200) { + throw Exception('Не удалось загрузить изображение'); + } final tempDir = await getTemporaryDirectory(); final filePath = '${tempDir.path}/${imageUrl.split('/').last}'; final file = File(filePath); - return await file.writeAsBytes(response.bodyBytes); - } else { - throw Exception('Не удалось загрузить изображение'); + return file.writeAsBytes(response.bodyBytes); + } catch (e, st) { + await reportFrom(e, st, this); + return null; } } @@ -222,44 +225,53 @@ class CreateRecipeCubit extends Cubit { } Future recipeToEntity(Recipe recipe) async { - final file = await downloadImageAsFile('${sl().baseUrl}/storage/${recipe.image}'); + try { + final file = await downloadImageAsFile('${sl().baseUrl}/storage/${recipe.image}'); + if (file == null) { + disableLoading(); + return; + } - final List steps = await Future.wait( - recipe.steps!.map( - (e) async => StepEntity( - description: e.description, - image: e.image.isEmpty ? null : await downloadImageAsFile('${sl().baseUrl}/storage/${e.image}'), + final List steps = await Future.wait( + recipe.steps!.map( + (e) async => StepEntity( + description: e.description, + image: e.image.isEmpty ? null : await downloadImageAsFile('${sl().baseUrl}/storage/${e.image}'), + ), ), - ), - ); - emit( - CreateRecipeState( - entity: RecipeCreateEntity( - title: recipe.title, - typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!), - countOfPortions: recipe.servings, - energyInOnePortion: recipe.calories, - totalCookHours: recipe.cookingTime ~/ 60, - totalCookMinutes: recipe.cookingTime % 60, - activeCookHours: recipe.activeCookingTime ~/ 60, - activeCookMinutes: recipe.activeCookingTime % 60, - description: recipe.description, - image: file, - videoUrl: recipe.videoUrl, - ingredients: recipe.ingredients! - .map((e) => i.Ingredient( - name: e.name, - unit: e.unit!, - portion: e.portion, - productId: e.productId, - )) - .toList(), - steps: steps, - tags: recipe.tags.map((e) => (e.name)).toList(), + ); + emit( + CreateRecipeState( + entity: RecipeCreateEntity( + title: recipe.title, + typeOfRecipe: TypeOfRecipe.fromApiType(recipe.type!), + countOfPortions: recipe.servings, + energyInOnePortion: recipe.calories, + totalCookHours: recipe.cookingTime ~/ 60, + totalCookMinutes: recipe.cookingTime % 60, + activeCookHours: recipe.activeCookingTime ~/ 60, + activeCookMinutes: recipe.activeCookingTime % 60, + description: recipe.description, + image: file, + videoUrl: recipe.videoUrl, + ingredients: recipe.ingredients! + .map((e) => i.Ingredient( + name: e.name, + unit: e.unit!, + portion: e.portion, + productId: e.productId, + )) + .toList(), + steps: steps, + tags: recipe.tags.map((e) => (e.name)).toList(), + ), + loading: false, ), - loading: false, - ), - ); + ); + } catch (e, st) { + await reportFrom(e, st, this); + disableLoading(); + } } bool validateAll() { @@ -376,13 +388,18 @@ class CreateRecipeCubit extends Cubit { await recipeRepository.deleteRecipe(slug); return true; } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); + await reportFrom(e, s, this); return false; } } Future> 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() { diff --git a/lib/features/create_recipe/reporitories/recipe_repository.dart b/lib/features/create_recipe/reporitories/recipe_repository.dart index 6acc905b..9659a520 100644 --- a/lib/features/create_recipe/reporitories/recipe_repository.dart +++ b/lib/features/create_recipe/reporitories/recipe_repository.dart @@ -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/step.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'; class RecipeRepository { @@ -16,12 +15,8 @@ class RecipeRepository { RecipeRepository({required this.recipeService}); Future>> inputProduct(String name) async { - try { - return await recipeService.getInputProduct(name); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await recipeService.getInputProduct(name); + } Future _buildStepsMap(List? steps) async { @@ -110,67 +105,43 @@ class RecipeRepository { } Future publishRecipe(RecipeModel recipe, [bool update = false, String? slug]) async { - try { - final formData = await _buildPublishFormData(recipe); - if (update) { - await recipeService.updateRecipe(slug: slug!, formData: formData); - } else { - await recipeService.publishRecipe(formData: formData); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final formData = await _buildPublishFormData(recipe); + if (update) { + await recipeService.updateRecipe(slug: slug!, formData: formData); + } else { + await recipeService.publishRecipe(formData: formData); } + } Future saveAsDraft(RecipeModel recipe, [bool update = false, String? slug]) async { - try { - final formData = await _buildDraftFormData(recipe); - log(formData.fields.toString()); - if (update) { - await recipeService.updateRecipe(slug: slug!, formData: formData); - } else { - await recipeService.createDraftedRecipe(formData: formData); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final formData = await _buildDraftFormData(recipe); + log(formData.fields.toString()); + if (update) { + await recipeService.updateRecipe(slug: slug!, formData: formData); + } else { + await recipeService.createDraftedRecipe(formData: formData); } + } Future draftExistingRecipe(int id) async { - try { - await recipeService.draftRecipe(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await recipeService.draftRecipe(id: id); + } Future getRecipe(String recipeID) async { - try { - return await recipeService.getRecipe(recipeID); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await recipeService.getRecipe(recipeID); + } Future> getRecipesTags() async { - try { - return (await recipeService.getTags()).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await recipeService.getTags()).data; + } Future deleteRecipe(String slug) async { - try { - return await recipeService.deleteRecipe(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await recipeService.deleteRecipe(slug: slug); + } } diff --git a/lib/features/dev_tools/presentation/dev_tools_screen.dart b/lib/features/dev_tools/presentation/dev_tools_screen.dart index 37c85b79..a21f9282 100644 --- a/lib/features/dev_tools/presentation/dev_tools_screen.dart +++ b/lib/features/dev_tools/presentation/dev_tools_screen.dart @@ -240,7 +240,7 @@ class _DevToolsScreenContentState extends State { } else { 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().getToken(), 'platform': platform}); showInfoToast('Токен добавлен'); } catch (e) { showInfoToast('Не удалось добавть токен'); @@ -253,7 +253,7 @@ class _DevToolsScreenContentState extends State { buttonStyle: const ButtonStyle(), onPressed: () async { try { - final token = await FirebaseApi().getToken(); + final token = await sl().getToken(); if (token != null) { Clipboard.setData(ClipboardData(text: token)); showInfoToast('Токен скопирован'); @@ -278,7 +278,7 @@ class _DevToolsScreenContentState extends State { } else { 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().getToken(), 'platform': platform}); showInfoToast('Токен удалён'); } catch (e) { showInfoToast('Не удалось удалить токен'); diff --git a/lib/features/dev_tools/test_cases.dart b/lib/features/dev_tools/test_cases.dart index 3d43bd2a..be5cd9ff 100644 --- a/lib/features/dev_tools/test_cases.dart +++ b/lib/features/dev_tools/test_cases.dart @@ -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/settings/services/settings_service.dart'; import 'package:eatvio_app/core/env_config.dart'; - class TestResult { final bool success; final String? error; @@ -57,7 +56,9 @@ class CreateRecipeTask implements TestCases { Future run({required String baseUrl}) async { try { final dio = sl(); - final recipeRepository = RecipeRepository(recipeService: CreateRecipeService(dio, baseUrl: baseUrl)); + final recipeRepository = RecipeRepository( + recipeService: CreateRecipeService(dio, baseUrl: baseUrl), + ); await recipeRepository.publishRecipe( const RecipeModel( title: 'Title', diff --git a/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart index 5ef8dea1..e1f8efc5 100644 --- a/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart +++ b/lib/features/diary/presentation/bloc/consultant_comments_cubit/consultant_comments_cubit.dart @@ -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/repositories/diary_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultantCommentsCubit extends Cubit { final DiaryRepository diaryRepository; @@ -74,6 +75,7 @@ class ConsultantCommentsCubit extends Cubit { } await diaryRepository.commentUserDay(diaryId: diaryId, commentModel: state.commentsModel); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } } diff --git a/lib/features/diary/presentation/bloc/diary_cubit.dart b/lib/features/diary/presentation/bloc/diary_cubit.dart index 39a0a7e7..289e3ecf 100644 --- a/lib/features/diary/presentation/bloc/diary_cubit.dart +++ b/lib/features/diary/presentation/bloc/diary_cubit.dart @@ -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/settings/repositories/service_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class DiaryCubit extends Cubit { + 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 DateTime selectedDay; int selectedMeal = 1; @@ -102,6 +109,7 @@ class DiaryCubit extends Cubit { emit(currentState); return false; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(DiaryState.error(errorMessage: e.toString())); return false; @@ -115,7 +123,8 @@ class DiaryCubit extends Cubit { if (diaryEntity != null) { setDiary(diaryDay: diaryEntity, diaryId: diaryId); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); } } @@ -126,6 +135,7 @@ class DiaryCubit extends Cubit { final diaryDay = await diaryRepository.getDiaryDayV2(day: selectedDay.toISO8601WithTimeZoneOffset()); return diaryDay; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); if (e is DioException) { if (e.response?.statusCode == 422) { @@ -152,8 +162,9 @@ class DiaryCubit extends Cubit { return false; } emit(const DiaryState.loading()); - if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) { - final diet = await dietRepository.getDiet((diaryDay.diet?.id)!); + try { + if (diaryDay.diet?.id != null && diaryDay.dayOfChosenDiet != null) { + final diet = await dietRepository.getDiet((diaryDay.diet?.id)!); final meals = diet.dailySchedule[diaryDay.dayOfChosenDiet]; final firstMealPortions = meals?[1]?.values.map((elem) { return PortionModel( @@ -221,9 +232,14 @@ class DiaryCubit extends Cubit { 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 setDiary({int? diaryId, required DiaryDayEntity diaryDay}) async { @@ -279,6 +295,7 @@ class DiaryCubit extends Cubit { ), ); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(DiaryState.error(errorMessage: e.toString())); } @@ -290,13 +307,19 @@ class DiaryCubit extends Cubit { await updateCurrentDiary(); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } } Future 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 addProductToMeal({ @@ -308,15 +331,23 @@ class DiaryCubit extends Cubit { int? productNutritionalValueId, }) async { emit((state as DiaryStateLoaded).copyWith(isLoadingAddingProductInMeal: true)); - await diaryRepository.addPortionToMeal( - diaryId: diaryId, - mealId: mealId, - productName: productName, - portion: portion, - quantity: quantity, - productNutritionalValueId: productNutritionalValueId, - ); - await updateCurrentDiary(); + try { + await diaryRepository.addPortionToMeal( + diaryId: diaryId, + mealId: mealId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + ); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + final s = state; + if (s is DiaryStateLoaded) { + emit(s.copyWith(isLoadingAddingProductInMeal: false)); + } + } } Future updateProductInMeal({ @@ -329,62 +360,95 @@ class DiaryCubit extends Cubit { int? productNutritionalValueId, required PortionState portionState, }) async { - if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) { - await diaryRepository.addPortionToMeal( - diaryId: diaryId, - mealId: mealId, - productName: productName, - portion: portion, - quantity: quantity, - productNutritionalValueId: productNutritionalValueId, - ); - } else { - await diaryRepository.updateProductInMeal( - diaryId: diaryId, - mealId: mealId, - portionId: portionId, - productName: productName, - portion: portion, - quantity: quantity, - productNutritionalValueId: productNutritionalValueId, - portionState: portionState, - ); + try { + if (portionState == PortionState.filledDiet || portionState == PortionState.eatenFromDiet) { + await diaryRepository.addPortionToMeal( + diaryId: diaryId, + mealId: mealId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + ); + } else { + await diaryRepository.updateProductInMeal( + diaryId: diaryId, + mealId: mealId, + portionId: portionId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + portionState: portionState, + ); + } + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); } - await updateCurrentDiary(); } Future markEaten({required int diaryId, required int mealId, required int portionId}) async { - await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); - await updateCurrentDiary(); + try { + await diaryRepository.markEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { - await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); - await updateCurrentDiary(); + try { + await diaryRepository.unmarkEaten(diaryId: diaryId, mealId: mealId, portionId: portionId); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future restore({required int diaryId, required int mealId, required int portionId}) async { - await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId); - await updateCurrentDiary(); + try { + await diaryRepository.restore(diaryId: diaryId, mealId: mealId, portionId: portionId); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { - await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId); - await updateCurrentDiary(); + try { + await diaryRepository.deleteProductFromMeal(diaryId: diaryId, mealId: mealId, portionId: portionId); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future> 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 uploadPhotos({required List files, required int diaryId}) async { - await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal); - await updateCurrentDiary(); + try { + await diaryRepository.uploadPhotos(files: await ImageCompressUtility.compressFilesList(files), diaryId: diaryId, mealId: selectedMeal); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future deletePhoto({required DiaryPhotoModel photo, required int diaryId}) async { - await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal); - await updateCurrentDiary(); + try { + await diaryRepository.deletePhoto(photo: photo, diaryId: diaryId, mealId: selectedMeal); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future sendProductToModeration({ @@ -397,34 +461,55 @@ class DiaryCubit extends Cubit { try { return await diaryRepository.sendProductToModeration(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); } return null; } Future fillTime({required int diaryId, required String time}) async { - await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time); - await updateCurrentDiary(); + try { + await diaryRepository.fillTime(diaryId: diaryId, mealId: selectedMeal, time: time); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future fillWakeup({required int diaryId, required String time}) async { - await diaryRepository.fillWakeup(diaryId: diaryId, time: time); - await updateCurrentDiary(); + try { + await diaryRepository.fillWakeup(diaryId: diaryId, time: time); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future fillRetire({required int diaryId, required String time}) async { - await diaryRepository.fillRetire(diaryId: diaryId, time: time); - await updateCurrentDiary(); + try { + await diaryRepository.fillRetire(diaryId: diaryId, time: time); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future fillSatiety({required int diaryId, required int satiety}) async { - await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); - await updateCurrentDiary(); + try { + await diaryRepository.fillSatiety(diaryId: diaryId, mealId: selectedMeal, satiety: satiety); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future fillMood({required int diaryId, required int mood}) async { - await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); - await updateCurrentDiary(); + try { + await diaryRepository.fillMood(diaryId: diaryId, mealId: selectedMeal, mood: mood); + await updateCurrentDiary(); + } catch (e, st) { + await reportFrom(e, st, this); + } } void fillComment(String comment) { diff --git a/lib/features/diary/repositories/diary_repository.dart b/lib/features/diary/repositories/diary_repository.dart index 071d03df..3072da4e 100644 --- a/lib/features/diary/repositories/diary_repository.dart +++ b/lib/features/diary/repositories/diary_repository.dart @@ -1,6 +1,7 @@ import 'dart:async'; 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_review_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/services/diary_service.dart'; import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class DiaryRepository { final DiaryService diaryService; @@ -20,134 +20,82 @@ class DiaryRepository { DiaryRepository({required this.diaryService}); - Future getDiaryByDate(String date) async { - try { - Set ids = {}; - final diaryDays = await getDiaryIndexByDate(date); - if (diaryDays.isEmpty) { - throw Exception('No diary entry found for $date'); - } - 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.isNotEmpty) { - final glossary = await getGlossariesIndex(ids: ids.toList()); - final List 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(); + PortionModel _portionWithGlossary(PortionModel portion, List glossaries) { + final id = portion.productNutritionalValueId; + if (id == null) return portion; + final glossaryItem = glossaries.firstWhereOrNull((g) => g.id == id); + if (glossaryItem == null) return portion; + 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, + ), + ); + } - 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; + Future getDiaryByDate(String date) async { + final ids = {}; + final diaryDays = await getDiaryIndexByDate(date); + if (diaryDays.isEmpty) { + throw Exception('No diary entry found for $date'); } + 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> getDiaryIndexByDate(String date) async { - try { - return (await diaryService.getDiaryIndexByDate(date: date)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.getDiaryIndexByDate(date: date); } Future getDiaryDay(int diaryId) async { - try { - // Align with getDiaryByDate: enrich portions with glossary nutritional info - final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId); - final DiaryDayModel diaryDay = diaryWrapper.diary; - Set ids = {}; - for (var meal in diaryDay.meals) { - for (var portion in meal.portions) { - if (portion.productNutritionalValueId != null) { - ids.add(portion.productNutritionalValueId!); - } + final diaryWrapper = await diaryService.getDiaryDay(diary: diaryId); + final DiaryDayModel diaryDay = diaryWrapper.diary; + final ids = {}; + for (var meal in diaryDay.meals) { + for (var portion in meal.portions) { + if (portion.productNutritionalValueId != null) { + ids.add(portion.productNutritionalValueId!); } } - if (ids.isNotEmpty) { - final glossary = await getGlossariesIndex(ids: ids.toList()); - final List 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 getNutritionData(int index) async { - try { - return await diaryService.getNutrition(diary: index); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.getNutrition(diary: index); } Future addPortionToMeal({ @@ -158,19 +106,14 @@ class DiaryRepository { required int quantity, int? productNutritionalValueId, }) async { - try { - return await diaryService.addProductToMeal( - diary: diaryId, - typeNo: mealId, - productName: productName, - portion: portion, - quantity: quantity, - productNutritionalValueId: productNutritionalValueId, - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.addProductToMeal( + diary: diaryId, + typeNo: mealId, + productName: productName, + portion: portion, + quantity: quantity, + productNutritionalValueId: productNutritionalValueId, + ); } Future updateProductInMeal({ @@ -183,83 +126,43 @@ class DiaryRepository { int? productNutritionalValueId, required PortionState portionState, }) async { - try { - Map data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson(); - if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId; - data['state'] = portionState.name; - return await diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final data = ProductInMeal(productName: productName, portion: portion, quantity: quantity).toJson(); + if (productNutritionalValueId != null) data['product_nutritional_value_id'] = productNutritionalValueId; + data['state'] = portionState.name; + return diaryService.updateProductInMeal(diary: diaryId, typeNo: mealId, portionModel: portionId, productInMeal: data); } Future markEaten({required int diaryId, required int mealId, required int portionId}) async { - try { - return await diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.markEaten(diary: diaryId, typeNo: mealId, portionModel: portionId); } Future unmarkEaten({required int diaryId, required int mealId, required int portionId}) async { - try { - return await diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.unmarkEaten(diary: diaryId, typeNo: mealId, portionModel: portionId); } Future restore({required int diaryId, required int mealId, required int portionId}) async { - try { - return await diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.restore(diary: diaryId, typeNo: mealId, portionModel: portionId); } Future deleteProductFromMeal({required int diaryId, required int mealId, required int portionId}) async { - try { - return await diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.deleteProductFromMeal(diary: diaryId, typeNo: mealId, portionModel: portionId); } Future uploadPhotos({required int diaryId, required int mealId, required List files}) async { - try { - for (int i = 0; i < files.length; i++) { - await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + for (var i = 0; i < files.length; i++) { + await diaryService.uploadPhotoOfMeal(diary: diaryId, typeNo: mealId, photo: files[i]); } } Future deletePhoto({required int diaryId, required int mealId, required DiaryPhotoModel photo}) async { - try { - await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await diaryService.deletePhotoOfMeal(diaryId: diaryId, typeNo: mealId, userAttachment: photo.id); } Future finishDiaryDay({required int diaryId, required String wakeUp, required String retire, String? comment}) async { - try { - return await diaryService.finishDiaryDay( - diary: diaryId, - finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment), - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.finishDiaryDay( + diary: diaryId, + finishDiaryDayModel: FinishDiaryDayModel(wakeUp: wakeUp, retire: retire, comment: comment), + ); } Future sendProductToModeration({ @@ -269,129 +172,66 @@ class DiaryRepository { required double fats, required double carbohydrates, }) async { - try { - return await diaryService.sendProductToModeration( - data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(), - ); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.sendProductToModeration( + data: ProductModerationModel(name: name, energy: energy, proteins: proteins, fats: fats, carbohydrates: carbohydrates).toJson(), + ); } Future fillSatiety({required int diaryId, required int mealId, required int satiety}) async { - try { - return await diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.fillSatiety(diary: diaryId, typeNo: mealId, satiety: satiety); } Future fillMood({required int diaryId, required int mealId, required int mood}) async { - try { - return await diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.fillMood(diary: diaryId, typeNo: mealId, mood: mood); } Future fillTime({required int diaryId, required int mealId, required String time}) async { - try { - return await diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.fillTime(diary: diaryId, typeNo: mealId, time: time); } Future fillWakeup({required int diaryId, required String time}) async { - try { - return await diaryService.fillWakeup(diary: diaryId, wakeUp: time); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.fillWakeup(diary: diaryId, wakeUp: time); } Future fillRetire({required int diaryId, required String time}) async { - try { - return await diaryService.fillRetire(diary: diaryId, retire: time); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.fillRetire(diary: diaryId, retire: time); } Future commentUserDay({required int diaryId, required ConsultantCommentModel commentModel}) async { - try { - await diaryService.commentUserDay(id: diaryId, body: commentModel); - _diaryForceFetchController.add('force'); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await diaryService.commentUserDay(id: diaryId, body: commentModel); + _diaryForceFetchController.add('force'); } Future getDiaryComments({required int planId, required int diaryId}) async { - try { - return await diaryService.getDiaryComments(planId: planId, diaryId: diaryId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.getDiaryComments(planId: planId, diaryId: diaryId); } Future viewCommentOfDiaryDayByConsultant({required int diaryId}) async { - try { - await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await diaryService.viewCommentOfDiaryDayByConsultant(diaryId: diaryId); } Future getGlossariesIndex({required List ids}) async { - Map filters = {}; + final filters = {}; 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]; } } - try { - return await diaryService.getGlossariesIndex(filters: filters); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return diaryService.getGlossariesIndex(filters: filters); } Future getDiaryDayV2({required String day}) async { - try { - final diaryDay = await diaryService.getDiaryDayV2(day: day); - return setCPFCToDiary(diaryDay); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final diaryDay = await diaryService.getDiaryDayV2(day: day); + return setCPFCToDiary(diaryDay); } Future createDiaryDay({required String day}) async { - try { - await diaryService.createDay(body: CreateDayModel(day: day)); - await getDiaryByDate(day); - } catch (e, s) { - if (e is DioException && e.response?.statusCode == 422) { - } else { - await Sentry.captureException(e, stackTrace: s); - } - rethrow; - } + await diaryService.createDay(body: CreateDayModel(day: day)); + await getDiaryByDate(day); } Future setCPFCToDiary(DiaryDayModel diaryDay) async { - Set ids = {}; + final ids = {}; for (var meal in diaryDay.meals) { for (var portion in meal.portions) { if (portion.productNutritionalValueId != null) { @@ -399,41 +239,15 @@ class DiaryRepository { } } } - if (ids.isNotEmpty) { - final glossary = await getGlossariesIndex(ids: ids.toList()); - final List 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; + if (ids.isEmpty) { + return diaryDay; } - 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); } } diff --git a/lib/features/diets/presentation/cubit/about_diet_cubit.dart b/lib/features/diets/presentation/cubit/about_diet_cubit.dart index 6ba7980f..5336c0e8 100644 --- a/lib/features/diets/presentation/cubit/about_diet_cubit.dart +++ b/lib/features/diets/presentation/cubit/about_diet_cubit.dart @@ -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/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class AboutDietCubit extends Cubit { AboutDietCubit({required this.dietsRepository, required this.profileRepository}) : super(const AboutDietState.initial()); @@ -31,7 +32,8 @@ class AboutDietCubit extends Cubit { dietsRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: diet.totalComments)); } emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(AboutDietState.error(message: e.toString())); } @@ -62,7 +64,8 @@ class AboutDietCubit extends Cubit { } _selectedIndexes = List.generate(diet.dailySchedule.length, (index) => 0); emit(AboutDietState.loaded(diet: diet, isStarted: isCurrentDietStarted, hasStartedDiet: hasStartedDiet, selectedIndexes: _selectedIndexes)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(AboutDietState.error(message: e.toString())); } @@ -90,7 +93,8 @@ class AboutDietCubit extends Cubit { EasyDebounce.debounce('like_diet', const Duration(seconds: 1), () async { try { await dietsRepository.likeDiet(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); if (dietIndexInFeed != null) { dietsRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentDiet.wasLikedByMe, totalLikes: currentDiet.totalLikes, index: dietIndexInFeed)); @@ -114,7 +118,8 @@ class AboutDietCubit extends Cubit { EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async { try { await dietsRepository.addToFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (dietIndex != null) { @@ -139,7 +144,8 @@ class AboutDietCubit extends Cubit { EasyDebounce.debounce('add_or_remove_diet_to_favorites', const Duration(seconds: 1), () async { try { await dietsRepository.removeFromFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (dietIndex != null) { @@ -159,6 +165,7 @@ class AboutDietCubit extends Cubit { await dietsRepository.startDiet(id, stringDate); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -170,10 +177,12 @@ class AboutDietCubit extends Cubit { final id = (state as DietStateLoaded).diet.id; await dietsRepository.cancelDiet(id); return true; - } on DioException catch (e) { + } on DioException catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); return false; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); return false; } @@ -194,7 +203,8 @@ class AboutDietCubit extends Cubit { Future getDietBySlug(String slug) async { try { return await dietsRepository.getDietBySlug(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); } return null; diff --git a/lib/features/diets/repositories/diet_repository.dart b/lib/features/diets/repositories/diet_repository.dart index 7ae0b6bc..9dc83ecb 100644 --- a/lib/features/diets/repositories/diet_repository.dart +++ b/lib/features/diets/repositories/diet_repository.dart @@ -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/feed/models/diets_pagination_model.dart'; import 'package:equatable/equatable.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class DietRepository { DietRepository({required this.dietsService}); @@ -18,76 +17,44 @@ class DietRepository { Stream get dietCommentsState => _dietCommentsStateStreamController.stream; Future likeDiet(int id) async { - try { - return await dietsService.likeDiet(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await dietsService.likeDiet(id: id); + } Future getDiet(int id) async { - try { - final diet = await dietsService.getDiet(id: id); - return diet; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final diet = await dietsService.getDiet(id: id); + return diet; + } Future getDietBySlug(String slug) async { - try { - final diet = await dietsService.getDietBySlug(slug: slug); - return diet; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final diet = await dietsService.getDietBySlug(slug: slug); + return diet; + } Future addToFavorite(int id) async { - try { - return await dietsService.addToFavorite(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await dietsService.addToFavorite(id: id); + } Future removeFromFavorite(int id) async { - try { - return await dietsService.removeFromFavorite(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await dietsService.removeFromFavorite(id: id); + } Future startDiet(int id, String date) async { - try { - return await dietsService.startDiet(id: id, body: {'date': date}); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await dietsService.startDiet(id: id, body: {'date': date}); + } Future cancelDiet(int id) async{ - try { - return await dietsService.cancelDiet(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await dietsService.cancelDiet(id: id); + } Future deleteDiet(int id) async { - try { - return await dietsService.deleteDiet(id: id); - } catch (e) { - rethrow; - } + return dietsService.deleteDiet(id: id); } void emitLikesStateToFeed(LikesState state) => _dietLikeStateStreamController.add(state); diff --git a/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart b/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart index 2c3473bd..dde50bce 100644 --- a/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart +++ b/lib/features/feed/presentation/bloc/consultants_cubit/consultants_cubit.dart @@ -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/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultantsCubit extends Cubit { final FeedRepository feedRepository; @@ -21,6 +22,7 @@ class ConsultantsCubit extends Cubit { ), ); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(ConsultantsState.error(errorMessage: e.toString())); } diff --git a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart index e2f9b95b..5d1d5357 100644 --- a/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart +++ b/lib/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart @@ -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/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class DietsCubit extends Cubit { final FeedRepository feedRepository; @@ -109,6 +110,7 @@ class DietsCubit extends Cubit { ), ); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit( state.copyWith(isLoading: false, errorMessage: e.toString()), @@ -144,7 +146,8 @@ class DietsCubit extends Cubit { ), ); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(isLoading: false, errorMessage: e.toString()), ); @@ -191,7 +194,8 @@ class DietsCubit extends Cubit { () async { try { await dietsRepository.likeDiet(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit( state.copyWith( @@ -287,7 +291,8 @@ class DietsCubit extends Cubit { () async { try { await dietsRepository.addToFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -327,7 +332,8 @@ class DietsCubit extends Cubit { () async { try { await dietsRepository.removeFromFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -348,7 +354,8 @@ class DietsCubit extends Cubit { try { await dietsRepository.deleteDiet(dietId); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); return false; } } diff --git a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart index c749f6d4..9254bf07 100644 --- a/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart +++ b/lib/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart @@ -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/repositories/feed_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class FeedCubit extends Cubit { final FeedRepository feedRepository; @@ -45,7 +46,8 @@ class FeedCubit extends Cubit { if (!isClosed) { emit(FeedState.loaded(feedBlogPostList: feedBlogPosts.feedBlogPostList, isLoading: false)); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); if (!isClosed) { emit(FeedState.error(errorMessage: e.toString())); } @@ -69,7 +71,8 @@ class FeedCubit extends Cubit { emit(FeedState.loaded(feedBlogPostList: [...(state as FeedStateLoaded).feedBlogPostList, ...feedBlogPosts.feedBlogPostList], isLoading: isLoading)); } } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); if (!isClosed) { emit(FeedState.error(errorMessage: e.toString())); } @@ -81,14 +84,48 @@ class FeedCubit extends Cubit { try { await feedRepository.likeBlogPost(id: id); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } } - Future unlikedPost(int id) async => await feedRepository.unlikedBlogPost(id: id); + Future unlikedPost(int id) async { + try { + await feedRepository.unlikedBlogPost(id: id); + } catch (e, st) { + await reportFrom(e, st, this); + } + } - Future viewPost(int id) async => await feedRepository.viewPost(id); - Future viewRecipe(String slug) async => await feedRepository.viewRecipe(slug); - Future viewDiet(int id) async => await feedRepository.viewDiet(id); - Future viewPlan(String slug) async => await feedRepository.viewPlan(slug); + Future viewPost(int id) async { + try { + await feedRepository.viewPost(id); + } catch (e, st) { + await reportFrom(e, st, this); + } + } + + Future viewRecipe(String slug) async { + try { + await feedRepository.viewRecipe(slug); + } catch (e, st) { + await reportFrom(e, st, this); + } + } + + Future viewDiet(int id) async { + try { + await feedRepository.viewDiet(id); + } catch (e, st) { + await reportFrom(e, st, this); + } + } + + Future viewPlan(String slug) async { + try { + await feedRepository.viewPlan(slug); + } catch (e, st) { + await reportFrom(e, st, this); + } + } } diff --git a/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart b/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart index 99432c22..b99dbcac 100644 --- a/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart +++ b/lib/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart @@ -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:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class PlansCubit extends Cubit { final FeedRepository feedRepository; @@ -41,6 +42,17 @@ class PlansCubit extends Cubit { }); } + @override + Future 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) { emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null)); EasyDebounce.debounce( @@ -168,6 +180,7 @@ class PlansCubit extends Cubit { ), ); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString()); log(stackTrace.toString()); emit(state.copyWith(isLoading: isLoading, errorMessage: e.toString())); @@ -199,7 +212,8 @@ class PlansCubit extends Cubit { ), ); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(errorMessage: e.toString())); } } @@ -228,7 +242,8 @@ class PlansCubit extends Cubit { () async { try { await planRepository.addToFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -268,7 +283,8 @@ class PlansCubit extends Cubit { () async { try { await planRepository.removeFromFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -324,7 +340,8 @@ class PlansCubit extends Cubit { () async { try { await planRepository.likePlan(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit( state.copyWith( diff --git a/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart index 2136624b..81c5d7f7 100644 --- a/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/feed/presentation/bloc/profile_cubit/profile_cubit.dart @@ -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/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class FeedProfileCubit extends Cubit { final ProfileRepository profileRepository; @@ -13,6 +14,7 @@ class FeedProfileCubit extends Cubit { final profile = await profileRepository.getMyProfile(); emit(state.copyWith(profile: profile)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); } } diff --git a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart index 001a7ec2..233cf7aa 100644 --- a/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart +++ b/lib/features/feed/presentation/bloc/recipes_cubit/recipes_cubit.dart @@ -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/recipe/repositories/about_recipe_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; import 'dart:developer'; class RecipesCubit extends Cubit { @@ -62,6 +63,7 @@ class RecipesCubit extends Cubit { ); lastPage = currentPage; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(errorMessage: e.toString(), isLoading: false)); } @@ -93,7 +95,8 @@ class RecipesCubit extends Cubit { ); } lastPage = currentPage; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(errorMessage: e.toString(), isLoading: false)); } } @@ -132,7 +135,8 @@ class RecipesCubit extends Cubit { () async { try { await aboutRecipeRepository.addToFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -172,7 +176,8 @@ class RecipesCubit extends Cubit { () async { try { await aboutRecipeRepository.removeFromFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); emit( @@ -228,7 +233,8 @@ class RecipesCubit extends Cubit { () async { try { await aboutRecipeRepository.likeRecipe(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit( state.copyWith( @@ -305,7 +311,8 @@ class RecipesCubit extends Cubit { try { await aboutRecipeRepository.deleteRecipe(slug); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); return false; } } diff --git a/lib/features/feed/repositories/feed_repository.dart b/lib/features/feed/repositories/feed_repository.dart index 6263d766..b5128be8 100644 --- a/lib/features/feed/repositories/feed_repository.dart +++ b/lib/features/feed/repositories/feed_repository.dart @@ -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/view_model.dart'; import 'package:eatvio_app/features/feed/services/feed_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class FeedRepository { final FeedService feedService; @@ -15,191 +14,107 @@ class FeedRepository { FeedRepository({required this.feedService}); Future getFeedBlogPosts({String? query, int page = 1}) async { - try { - return (await feedService.getBlogPostList(page: page, query: query)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getBlogPostList(page: page, query: query)); + } Future getBlogPost({required int id}) async { - try { - return await feedService.getBlogPost(postId: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await feedService.getBlogPost(postId: id); + } Future> getRecipes({String? query, List? tags, int page = 1}) async { - try { - return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getRecipes(query: query, tags: tags, page: page)).recipesList; + } Future 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)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getDiets(page: page, query: query, types: types, duration: duration, sorting: sorting)); + } Future 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)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice)); + } Future getBlogPostComments({required String id}) async { - try { - return (await feedService.getBlogPostComments(id: id)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getBlogPostComments(id: id)); + } Future sendBlogPostComment({required String id, required String commentText, int? parentCommentId}) async { - try { - await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.sendBlogPostComment(id: id, body: commentText, parentCommentId: parentCommentId); + } Future getDietsComments({required String id}) async { - try { - return (await feedService.getDietsComments(id: id)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getDietsComments(id: id)); + } Future sendDietsComment({required String id, required String commentText, int? parentCommentId}) async { - try { - await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.sendDietsComment(id: id, body: commentText, parentCommentId: parentCommentId); + } Future getRecipesComments({required String id}) async { - try { - return (await feedService.getRecipesComments(id: id)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getRecipesComments(id: id)); + } Future sendRecipesComment({required String id, required String commentText, int? parentCommentId}) async { - try { - await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.sendRecipesComment(id: id, body: commentText, parentCommentId: parentCommentId); + } Future getPlansComments({required String slug}) async { - try { - return (await feedService.getPlansComments(slug: slug)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getPlansComments(slug: slug)); + } Future sendPlansComment({required String slug, required String commentText, int? parentCommentId}) async { - try { - await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.sendPlansComment(slug: slug, body: commentText, parentCommentId: parentCommentId); + } Future likeBlogPost({required int id}) async { - try { - await feedService.likeBlogPost(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.likeBlogPost(id: id); + } Future unlikedBlogPost({required int id}) async { - try { - await feedService.unlikedBlogPost(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.unlikedBlogPost(id: id); + } Future> getConsultants([String? name]) async { - try { - return (await feedService.getConsultants(name: name)).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.getConsultants(name: name)).data; + } Future viewPost(int id) async { - try { - return (await feedService.viewPost(id: id)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.viewPost(id: id)); + } Future viewRecipe(String slug) async { - try { - return (await feedService.viewRecipe(slug: slug)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.viewRecipe(slug: slug)); + } Future viewDiet(int id) async { - try { - return (await feedService.viewDiet(id: id)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.viewDiet(id: id)); + } Future viewPlan(String slug) async { - try { - return (await feedService.viewPlan(slug: slug)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await feedService.viewPlan(slug: slug)); + } Future viewContent({required ViewModel viewModel}) async { - try { - await feedService.viewContent(viewModel: viewModel); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await feedService.viewContent(viewModel: viewModel); + } } diff --git a/lib/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart b/lib/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart index a2f6daf0..8675bda1 100644 --- a/lib/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart +++ b/lib/features/finances_and_consultations_archive/cubit/archive/consultations_archive_cubit.dart @@ -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/period_enum.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultationsArchiveCubit extends Cubit { final ConsultationsRepository consultationsRepository; @@ -34,7 +35,8 @@ class ConsultationsArchiveCubit extends Cubit { : FiltersComparisonEnum.equal, ); emit(state.copyWith(consultations: consultations, isLoading: false)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(isLoading: false)); log(e.toString()); } @@ -55,7 +57,8 @@ class ConsultationsArchiveCubit extends Cubit { emit(state.copyWith(isLoading: true)); final statistic = await consultationsRepository.getStatistic(); emit(state.copyWith(statisticModel: statistic, isLoading: false)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(isLoading: false)); log(e.toString()); } diff --git a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart index 69d25052..9d2d57c6 100644 --- a/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart +++ b/lib/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart @@ -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/period_enum.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class ConsultantFinancesCubit extends Cubit { final ConsultationsRepository consultationsRepository; @@ -22,7 +23,8 @@ class ConsultantFinancesCubit extends Cubit { state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, ); emit(state.copyWith(financesModels: consultantFinances, isLoading: false)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(financesModels: [], isLoading: false)); log(e.toString()); } diff --git a/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart index 55fdbc68..981ec6d7 100644 --- a/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart +++ b/lib/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart @@ -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/period_enum.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class UserPurchasesCubit extends Cubit { final ConsultationsRepository consultationsRepository; @@ -23,7 +24,8 @@ class UserPurchasesCubit extends Cubit { ); emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(purchasesModels: [], isLoading: false)); log(e.toString()); } diff --git a/lib/features/glossary/repositories/glossary_repository.dart b/lib/features/glossary/repositories/glossary_repository.dart index 9702db8e..c23a7ba7 100644 --- a/lib/features/glossary/repositories/glossary_repository.dart +++ b/lib/features/glossary/repositories/glossary_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/glossary/models/glossary_model.dart'; import 'package:eatvio_app/features/glossary/services/glossary_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class GlossaryRepository { final GlossaryService glossaryService; @@ -10,11 +9,7 @@ class GlossaryRepository { }); Future> getGlossaryListByQuery(String query) async { - try { - return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await glossaryService.getGlossaryByQuery(query: query)).glossariesList; + } } diff --git a/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart b/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart index bb60d139..b4ab6b41 100644 --- a/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart +++ b/lib/features/menu/presentation/bloc/menu_cubit/menu_cubit.dart @@ -3,6 +3,7 @@ import 'dart:developer'; 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:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class MenuCubit extends Cubit { final ProfileRepository profileRepository; @@ -12,7 +13,8 @@ class MenuCubit extends Cubit { try { final passKey = await profileRepository.getPassKey(routeName: 'product_nutritional_value.index', routeParams: {}); return passKey; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); } return null; diff --git a/lib/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart b/lib/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart index 6c5c567c..60b0986d 100644 --- a/lib/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart +++ b/lib/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart @@ -1,5 +1,6 @@ import 'dart:developer'; 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/repositories/notification_settings_repository.dart'; @@ -16,6 +17,7 @@ class NotificationSettingsCubit extends Cubit { final notificationSettings = await _notificationSettingsRepository.getNotificationSettings(); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log('Error getting notification settings: $e', stackTrace: stackTrace); emit(NotificationSettingsStateError(errorMessage: e.toString())); } @@ -26,6 +28,7 @@ class NotificationSettingsCubit extends Cubit { final notificationSettings = await _notificationSettingsRepository.updateNotificationSettings(data); emit(NotificationSettingsStateLoaded(notificationSettings: notificationSettings)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log('Error updating notification settings: $e', stackTrace: stackTrace); emit(NotificationSettingsStateError(errorMessage: e.toString())); } diff --git a/lib/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart b/lib/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart index 3954e248..3a8b07ef 100644 --- a/lib/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart +++ b/lib/features/menu/presentation/blocked_user_cubit/blocked_users_cubit.dart @@ -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/repositories/blocked_users_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class BlockedUsersCubit extends Cubit { final BlockedUsersRepository blockedUsersRepository; @@ -13,17 +14,26 @@ class BlockedUsersCubit extends Cubit { try { emit(BlockedUsersState.loaded(profilesList: await blockedUsersRepository.getBlockedUsers())); } catch (e, st) { + await reportFrom(e, st, this); emit(BlockedUsersState.error(errorMessage: '$e $st')); } } Future blockUser(String slug) async { - await blockedUsersRepository.blockUserBySlug(slug); - await getBlockedUsers(); + try { + await blockedUsersRepository.blockUserBySlug(slug); + await getBlockedUsers(); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future unblockUser(String slug) async { - await blockedUsersRepository.unblockUserBySlug(slug); - await getBlockedUsers(); + try { + await blockedUsersRepository.unblockUserBySlug(slug); + await getBlockedUsers(); + } catch (e, st) { + await reportFrom(e, st, this); + } } } diff --git a/lib/features/menu/presentation/pages/menu_screen.dart b/lib/features/menu/presentation/pages/menu_screen.dart index dc4e3d82..859b1b6f 100644 --- a/lib/features/menu/presentation/pages/menu_screen.dart +++ b/lib/features/menu/presentation/pages/menu_screen.dart @@ -1,6 +1,6 @@ import 'package:dio/dio.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/theme/custom_text_style.dart'; import 'package:eatvio_app/core/token_storage.dart'; @@ -144,7 +144,7 @@ class MenuScreen extends StatelessWidget { try { await sl().post('${sl().baseUrl}/api/profile/logout'); } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); + await sl().report(e, s, origin: 'MenuScreen'); } finally { await sl().clear(); await TokenStorage.removeTokenInStorage(); diff --git a/lib/features/menu/presentation/repositories/blocked_users_repository.dart b/lib/features/menu/presentation/repositories/blocked_users_repository.dart index 2620252f..fe76c0d8 100644 --- a/lib/features/menu/presentation/repositories/blocked_users_repository.dart +++ b/lib/features/menu/presentation/repositories/blocked_users_repository.dart @@ -1,7 +1,5 @@ -import 'dart:developer'; import 'package:eatvio_app/features/menu/services/blocked_users_service.dart'; import 'package:eatvio_app/features/profile/models/profile_model.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class BlockedUsersRepository { final BlockedUserService blockedUserService; @@ -11,40 +9,25 @@ class BlockedUsersRepository { }); Future> getBlockedUsers() async { - try { - return (await blockedUserService.getBlockedUsers()).profilesList; - } catch (e, st) { - log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); - rethrow; - } + return (await blockedUserService.getBlockedUsers()).profilesList; + } Future blockUserBySlug(String slug) async { - try { - return await blockedUserService.blockUser( - data: { - 'slug': slug, - }, - ); - } catch (e, st) { - log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); - rethrow; - } + return await blockedUserService.blockUser( + data: { + 'slug': slug, + }, + ); + } Future unblockUserBySlug(String slug) async { - try { - return await blockedUserService.unblockUser( - data: { - 'slug': slug, - }, - ); - } catch (e, st) { - log(e.toString(), stackTrace: st); - await Sentry.captureException(e, stackTrace: st); - rethrow; - } + return await blockedUserService.unblockUser( + data: { + 'slug': slug, + }, + ); + } } diff --git a/lib/features/menu/repositories/notification_settings_repository.dart b/lib/features/menu/repositories/notification_settings_repository.dart index 81d782ef..3b46554e 100644 --- a/lib/features/menu/repositories/notification_settings_repository.dart +++ b/lib/features/menu/repositories/notification_settings_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/menu/models/notification_settings_model.dart'; import 'package:eatvio_app/features/menu/services/notification_settings_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class NotificationSettingsRepository { final NotificationSettingsService _notificationSettingsService; @@ -8,20 +7,12 @@ class NotificationSettingsRepository { NotificationSettingsRepository(this._notificationSettingsService); Future getNotificationSettings() async { - try { - return await _notificationSettingsService.getNotificationSettings(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await _notificationSettingsService.getNotificationSettings(); + } Future updateNotificationSettings(Map data) async { - try { - return await _notificationSettingsService.updateNotificationSettings(data: data); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await _notificationSettingsService.updateNotificationSettings(data: data); + } } diff --git a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart index 398fdb63..be5fcca9 100644 --- a/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart +++ b/lib/features/my_diets/presentation/cubit/my_diets_cubit.dart @@ -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/repositories/my_diets_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class MyDietsCubit extends Cubit { final MyDietsRepository myDietsRepository; @@ -212,6 +213,7 @@ class MyDietsCubit extends Cubit { ), ); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit( state.copyWith(activeDietsError: e.toString()), @@ -242,7 +244,8 @@ class MyDietsCubit extends Cubit { error: null, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(draftDietsError: e.toString()), ); @@ -272,7 +275,8 @@ class MyDietsCubit extends Cubit { error: null, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(personalDietsError: e.toString()), ); @@ -283,7 +287,8 @@ class MyDietsCubit extends Cubit { try { await myDietsRepository.deleteDiet(dietId); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(error: e.toString()), ); @@ -299,6 +304,7 @@ class MyDietsCubit extends Cubit { ); return statusCode; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return 0; } @@ -312,6 +318,7 @@ class MyDietsCubit extends Cubit { ); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } diff --git a/lib/features/my_diets/repositories/my_diets_repository.dart b/lib/features/my_diets/repositories/my_diets_repository.dart index df90c157..b579eebb 100644 --- a/lib/features/my_diets/repositories/my_diets_repository.dart +++ b/lib/features/my_diets/repositories/my_diets_repository.dart @@ -1,6 +1,5 @@ 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:sentry_flutter/sentry_flutter.dart'; class MyDietsRepository { final MyDietsService myDietsService; @@ -8,12 +7,8 @@ class MyDietsRepository { MyDietsRepository({required this.myDietsService}); Future deleteDiet(int dietId) async { - try { - await myDietsService.deleteDiet(dietId: dietId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await myDietsService.deleteDiet(dietId: dietId); + } Future getDiets({ @@ -24,18 +19,14 @@ class MyDietsRepository { String? sorting, String? state, }) async { - try { - return (await myDietsService.getDiets( - page: page, - query: query, - types: types, - duration: duration, - sorting: sorting, - state: state, - )); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await myDietsService.getDiets( + page: page, + query: query, + types: types, + duration: duration, + sorting: sorting, + state: state, + )); + } } diff --git a/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart index 184cd44f..802391cb 100644 --- a/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart +++ b/lib/features/my_finance/presentation/cubit/user_cards_cubit/user_cards_cubit.dart @@ -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/repositories/cards_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class UserCardsCubit extends Cubit { final CardsRepository cardsRepository; @@ -15,6 +16,7 @@ class UserCardsCubit extends Cubit { try { return await cardsRepository.addCard(); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } return null; @@ -27,6 +29,7 @@ class UserCardsCubit extends Cubit { final cards = await cardsRepository.getCards(); emit(state.copyWith(cards: cards, state: BaseState.success)); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(state: BaseState.error, error: e.toString())); } @@ -47,6 +50,7 @@ class UserCardsCubit extends Cubit { try { await cardsRepository.markAsDefault(id: id); } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(cards: oldCards)); log(e.toString(), stackTrace: st); } @@ -61,6 +65,7 @@ class UserCardsCubit extends Cubit { emit(state.copyWith(cards: cards)); await cardsRepository.deleteCard(id: id); } catch (e, st) { + await reportFrom(e, st, this); emit(state.copyWith(cards: oldCards, error: e.toString(), state: BaseState.error)); log(e.toString(), stackTrace: st); } @@ -71,6 +76,7 @@ class UserCardsCubit extends Cubit { await cardsRepository.collectCharge(chosenPlaId: chosenPlaId); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } return false; diff --git a/lib/features/my_finance/repositories/cards_repository.dart b/lib/features/my_finance/repositories/cards_repository.dart index 9c77a917..8f887d00 100644 --- a/lib/features/my_finance/repositories/cards_repository.dart +++ b/lib/features/my_finance/repositories/cards_repository.dart @@ -1,53 +1,32 @@ 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/services/cards_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class CardsRepository { final CardsService cardsService; CardsRepository({required this.cardsService}); Future addCard() async { - try { - return await cardsService.addCard(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await cardsService.addCard(); + } Future> getCards() async { - try { - return await cardsService.getCards(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await cardsService.getCards(); + } Future markAsDefault({required int id}) async { - try { - await cardsService.markAsDefault(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await cardsService.markAsDefault(id: id); + } Future deleteCard({required int id}) async { - try { - await cardsService.deleteCard(id: id); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await cardsService.deleteCard(id: id); + } Future collectCharge({required int chosenPlaId}) async { - try { - await cardsService.collectCharge(chosenPlanId: chosenPlaId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await cardsService.collectCharge(chosenPlanId: chosenPlaId); + } } diff --git a/lib/features/my_plans/presentation/cubit/my_plans_cubit.dart b/lib/features/my_plans/presentation/cubit/my_plans_cubit.dart index 33462563..129bfc9d 100644 --- a/lib/features/my_plans/presentation/cubit/my_plans_cubit.dart +++ b/lib/features/my_plans/presentation/cubit/my_plans_cubit.dart @@ -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/repositories/my_diets_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class MyPlansCubit extends Cubit { final MyPlansRepository myPlansRepository; @@ -145,7 +146,8 @@ class MyPlansCubit extends Cubit { maxActivePlanPrice: maxPlanPrice, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(error: e.toString()), ); @@ -185,7 +187,8 @@ class MyPlansCubit extends Cubit { maxDraftPlanPrice: maxPlanPrice, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(error: e.toString()), ); @@ -197,7 +200,8 @@ class MyPlansCubit extends Cubit { await myPlansRepository.deletePlan(slug); await loadAllPlans(); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit( state.copyWith(error: e.toString()), ); @@ -209,7 +213,8 @@ class MyPlansCubit extends Cubit { try { await myPlansRepository.unpublish(slug); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); return false; } } diff --git a/lib/features/my_plans/repositories/my_diets_repository.dart b/lib/features/my_plans/repositories/my_diets_repository.dart index a378bc4f..177023d5 100644 --- a/lib/features/my_plans/repositories/my_diets_repository.dart +++ b/lib/features/my_plans/repositories/my_diets_repository.dart @@ -1,8 +1,5 @@ -import 'dart:developer'; - 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:sentry_flutter/sentry_flutter.dart'; class MyPlansRepository { final MyPlansService myPlansService; @@ -10,30 +7,17 @@ class MyPlansRepository { MyPlansRepository({required this.myPlansService}); Future deletePlan(String slug) async { - try { - await myPlansService.deletePlan(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await myPlansService.deletePlan(slug: slug); + } Future 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)); - } catch (e, st) { - log(e.toString(), error: e, stackTrace: st); - await Sentry.captureException(e, stackTrace: st); - rethrow; - } + return (await myPlansService.getPlans(page: page, query: query, types: types, duration: duration, minPrice: minPrice, maxPrice: maxPrice)); + } Future unpublish(String slug) async { - try { - return await myPlansService.unpublish(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await myPlansService.unpublish(slug: slug); + } } diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart index 51771c24..a3c5827d 100644 --- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -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/repository/notifications_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class NotificationsCubit extends Cubit { final List _notifications = []; @@ -16,10 +17,14 @@ class NotificationsCubit extends Cubit { NotificationsCubit({required this.wsManager, required this.notificationsRepository}) : super(const NotificationsState.empty()); Future connectToWebSocket() async { - await _newNotificationsSubscription?.cancel(); - _newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) { - _handleNotification(event.data); - }); + try { + await _newNotificationsSubscription?.cancel(); + _newNotificationsSubscription = wsManager.newNotificationsStream?.listen((event) { + _handleNotification(event.data); + }); + } catch (e, st) { + await reportFrom(e, st, this); + } } BaseNotificationModel _parseNotification(Map notificationJson, NotificationType type) { @@ -341,12 +346,21 @@ class NotificationsCubit extends Cubit { ), 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()); } } 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; // Улучшенная логика определения типа уведомления (порядок: database_type → type → notification_class_name) @@ -420,6 +434,7 @@ class NotificationsCubit extends Cubit { ); } } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } } @@ -456,6 +471,7 @@ class NotificationsCubit extends Cubit { emit(currentState.copyWith(isLoadingMore: false)); } } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(currentState.copyWith(isLoadingMore: false)); } @@ -471,6 +487,7 @@ class NotificationsCubit extends Cubit { await getNotifications(); emit(currentState.copyWith(isLoading: false, hasUnreadNotifications: false)); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(currentState.copyWith(isLoading: false)); } diff --git a/lib/features/notifications/repository/notifications_repository.dart b/lib/features/notifications/repository/notifications_repository.dart index 6508ee2e..449154b0 100644 --- a/lib/features/notifications/repository/notifications_repository.dart +++ b/lib/features/notifications/repository/notifications_repository.dart @@ -1,27 +1,16 @@ -import 'dart:developer'; import 'package:eatvio_app/features/notifications/service/notifications_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class NotificationsRepository { final NotificationsService notificationsService; NotificationsRepository({required this.notificationsService}); Future?> getNotifications({required int page}) async { - try { - return await notificationsService.getNotifications(page: page); - } catch (e, s) { - log(e.toString()); - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await notificationsService.getNotifications(page: page); + } Future markNotificationsAsRead() async { - try { - await notificationsService.markNotificationsAsRead(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await notificationsService.markNotificationsAsRead(); + } } diff --git a/lib/features/order_info/cubit/order_info_cubit.dart b/lib/features/order_info/cubit/order_info_cubit.dart index af934fab..ff331c01 100644 --- a/lib/features/order_info/cubit/order_info_cubit.dart +++ b/lib/features/order_info/cubit/order_info_cubit.dart @@ -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/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class OrderInfoCubit extends Cubit { OrderInfoCubit({required this.chatRepository, required this.profileRepository}) : super(OrderInfoState()); @@ -15,22 +16,32 @@ class OrderInfoCubit extends Cubit { if (plan == null) return null; final paymentModel = await chatRepository.getPaymentLink(id: plan.id); return paymentModel.paymentUrl; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); return null; } } - Future getChosenPlan({required int id, String? customerSlug}) async { + Future getChosenPlan({required int id, String? customerSlug, bool isConsultantView = false}) async { try { emit(state.copyWith(isLoading: true)); final chosenPlan = await chatRepository.getChosenPlan(id: id); + if (customerSlug != null) { 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)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(state.copyWith(isLoading: false)); } diff --git a/lib/features/order_info/presentation/info_by_order_screen.dart b/lib/features/order_info/presentation/info_by_order_screen.dart index adce3286..15ad28dd 100644 --- a/lib/features/order_info/presentation/info_by_order_screen.dart +++ b/lib/features/order_info/presentation/info_by_order_screen.dart @@ -1,4 +1,5 @@ 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/theme_helper.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 { static const name = 'infoByOrderScreen'; 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 int planId; final String? customerSlug; + final bool isConsultantView; @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => sl()..getChosenPlan(id: planId, customerSlug: customerSlug), + create: (context) => sl()..getChosenPlan(id: planId, customerSlug: customerSlug, isConsultantView: isConsultantView), 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}); final int planId; final bool? successfulPayment; @@ -61,7 +63,11 @@ class _InfoByOrderScreenContentState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back), 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 { leading: IconButton( icon: const Icon(Icons.arrow_back), 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 { leading: IconButton( icon: const Icon(Icons.arrow_back), 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 { const SizedBox(height: 8), OptionWidget(option: chosenOption, disableDuration: true, duration: plan.plan.duration), const SizedBox(height: 8), - PlanState(state: plan.chosenPlanState, hasPersonalDiet: chosenOption?.hasPersonalDiet ?? false,), + PlanState(state: plan.chosenPlanState, hasPersonalDiet: chosenOption?.hasPersonalDiet ?? false), const SizedBox(height: 20), if (widget.successfulPayment == null) CustomOutlinedButton( diff --git a/lib/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart b/lib/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart index cde49e7e..0afb7d3e 100644 --- a/lib/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart +++ b/lib/features/personal_diet_suggestion/cubit/personal_diet_suggestion_cubit.dart @@ -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/repository/personal_diet_suggestion_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class PersonalDietSuggestionCubit extends Cubit { final DietRepository dietRepository; @@ -17,6 +18,7 @@ class PersonalDietSuggestionCubit extends Cubit { final diet = await dietRepository.getDiet(dietId); emit(state.copyWith(isLoading: false, dietName: diet.name)); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(isLoading: false, error: e.toString())); } @@ -36,6 +38,7 @@ class PersonalDietSuggestionCubit extends Cubit { consultationsWithoutDiet: consultationWithoutDiet, )); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); emit(state.copyWith(isLoading: false, error: e.toString())); } @@ -48,6 +51,7 @@ class PersonalDietSuggestionCubit extends Cubit { await personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } @@ -60,6 +64,7 @@ class PersonalDietSuggestionCubit extends Cubit { await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId); return true; } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); return false; } diff --git a/lib/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart b/lib/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart index 784d9857..b966dafd 100644 --- a/lib/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart +++ b/lib/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository.dart @@ -1,6 +1,5 @@ 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:sentry_flutter/sentry_flutter.dart'; class PersonalDietSuggestionRepository { final PersonalDietSuggestionService personalDietSuggestionService; @@ -8,29 +7,17 @@ class PersonalDietSuggestionRepository { PersonalDietSuggestionRepository({required this.personalDietSuggestionService}); Future> getConsultationWithPersonalDiets() async{ - try { - return (await personalDietSuggestionService.getConsultationWithPersonalDiets()).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await personalDietSuggestionService.getConsultationWithPersonalDiets()).data; + } Future attachPersonalDiet({required int consultationId, required int dietId}) async { - try { - return personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return personalDietSuggestionService.attachPersonalDiet(consultationId: consultationId, dietId: dietId); + } Future reattachPersonalDiet({required int consultationId, required int dietId}) async { - try { - return await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await personalDietSuggestionService.reattachPersonalDiet(consultationId: consultationId, dietId: dietId); + } } diff --git a/lib/features/plans/presentation/cubit/about_plan_cubit.dart b/lib/features/plans/presentation/cubit/about_plan_cubit.dart index 85d26b0f..6d43871d 100644 --- a/lib/features/plans/presentation/cubit/about_plan_cubit.dart +++ b/lib/features/plans/presentation/cubit/about_plan_cubit.dart @@ -8,6 +8,7 @@ import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/presentation/cubit/about_plan_state.dart'; import 'package:eatvio_app/features/plans/repositories/plan_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class AboutPlanCubit extends Cubit { final PlanRepository planRepository; @@ -21,6 +22,7 @@ class AboutPlanCubit extends Cubit { } emit(state.copyWith(planModel: plan, error: null)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); emit(state.copyWith(error: e.toString(), isLoading: false)); } @@ -32,6 +34,7 @@ class AboutPlanCubit extends Cubit { final plan = await planRepository.getPlan(slug); emit(state.copyWith(planModel: plan, error: null, isLoading: false)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); emit(state.copyWith(error: e.toString(), isLoading: false)); } @@ -59,7 +62,8 @@ class AboutPlanCubit extends Cubit { EasyDebounce.debounce('like_plan_$slug', const Duration(seconds: 1), () async { try { await planRepository.likePlan(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); if (index != null) { planRepository.emitLikesStateToFeed(LikesState(wasLikedByMe: currentPlan.wasLikedByMe, totalLikes: currentPlan.totalLikes, index: index)); @@ -80,7 +84,8 @@ class AboutPlanCubit extends Cubit { EasyDebounce.debounce('add_or_remove_plan_to_favorites', const Duration(seconds: 1), () async { try { await planRepository.addToFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (index != null) { @@ -102,7 +107,8 @@ class AboutPlanCubit extends Cubit { EasyDebounce.debounce('add_or_remove_plan_to_favorites', const Duration(seconds: 1), () async { try { await planRepository.removeFromFavorite(slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (index != null) { @@ -123,11 +129,13 @@ class AboutPlanCubit extends Cubit { return (await planRepository.chosePlan(slug, body), null); } } on DioException catch (e, st) { + await reportFrom(e, st, this); if (e.response?.statusCode == 400) { return (null, 'hasPlan'); } log(e.toString(), stackTrace: st); } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); } return (null, ''); diff --git a/lib/features/plans/repositories/plan_repository.dart b/lib/features/plans/repositories/plan_repository.dart index fcc942f9..67ea6003 100644 --- a/lib/features/plans/repositories/plan_repository.dart +++ b/lib/features/plans/repositories/plan_repository.dart @@ -3,7 +3,6 @@ import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart'; import 'package:eatvio_app/features/plans/services/plan_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class PlanRepository { final PlanService planService; @@ -18,48 +17,28 @@ class PlanRepository { Stream get planCommentsState => _planCommentsStateStreamController.stream; Future getPlan(String slug) async { - try { - return planService.getPlan(slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return planService.getPlan(slug); + } Future likePlan(String slug) async { - try { - await planService.likePlan(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await planService.likePlan(slug: slug); + } Future addToFavorite(String slug) async { - try { - await planService.addToFavorite(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await planService.addToFavorite(slug: slug); + } Future removeFromFavorite(String slug) async { - try { - await planService.removeFromFavorite(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await planService.removeFromFavorite(slug: slug); + } Future chosePlan(String slug, Map option ) async { - try { - return await planService.chosePlan(slug: slug, option: option); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await planService.chosePlan(slug: slug, option: option); + } void emitLikesStateToFeed(LikesState state) => _planLikeStateStreamController.add(state); diff --git a/lib/features/plans/repositories/plans_repository.dart b/lib/features/plans/repositories/plans_repository.dart index 74aec6b1..10386890 100644 --- a/lib/features/plans/repositories/plans_repository.dart +++ b/lib/features/plans/repositories/plans_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/plans/services/plans_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class PlansRepository { final PlansService plansService; @@ -8,12 +7,8 @@ class PlansRepository { PlansRepository({required this.plansService}); Future> getPlansBySlug(String slug) async { - try { - final plans = (await plansService.getPlansBySlug(author: slug)).data; - return plans.where((plan) => plan.state == 'active').toList(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final plans = (await plansService.getPlansBySlug(author: slug)).data; + return plans.where((plan) => plan.state == 'active').toList(); + } } diff --git a/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart b/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart index 4b5d86b6..a6370e48 100644 --- a/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart +++ b/lib/features/profile/presentation/bloc/plans_cubit/plans_cubit.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:eatvio_app/features/plans/repositories/plans_repository.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/plans_cubit/plans_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class PlansCubit extends Cubit { final PlansRepository plansRepository; @@ -15,7 +16,8 @@ class PlansCubit extends Cubit { emit(const PlansState.loading()); try { emit(PlansState.loaded(plansList: await plansRepository.getPlansBySlug(slug))); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(PlansState.error(errorMessage: e.toString())); } diff --git a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart index 9538de53..db3bf384 100644 --- a/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart +++ b/lib/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart @@ -10,6 +10,7 @@ import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart'; import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_state.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.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'; class ProfileCubit extends Cubit { @@ -41,9 +42,9 @@ class ProfileCubit extends Cubit { certPhotos: certPhotos, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(ProfileState.error(errorMessage: e.toString())); - rethrow; } } @@ -67,6 +68,7 @@ class ProfileCubit extends Cubit { ), ); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); emit(ProfileState.error(errorMessage: e.toString())); } @@ -101,7 +103,8 @@ class ProfileCubit extends Cubit { hipCircumference: hipCircumference, ); await getMyProfile(period: _currentPeriod); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(ProfileState.error(errorMessage: e.toString())); } } @@ -110,7 +113,8 @@ class ProfileCubit extends Cubit { try { await profileRepository.addWeight(weight: weight); await getMyProfile(period: _currentPeriod); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(ProfileState.error(errorMessage: e.toString())); } } @@ -121,27 +125,37 @@ class ProfileCubit extends Cubit { final userProfile = await profileRepository.getProfileBySlug(slug); if (chatIdOrError == '404') { AppRouter.navigatorKey.currentContext?.pushNamed('emptyChatScreen', extra: userProfile); - } else if (chatIdOrError == 'error') { - showErrorToast('Ошибка создания чата'); } else { AppRouter.navigatorKey.currentContext?.pushNamed('chatScreen', extra: {'uuid': chatIdOrError}); return chatIdOrError; } } catch (e, st) { + await reportFrom(e, st, this); log(e.toString(), stackTrace: st); + showErrorToast('Ошибка создания чата'); return ''; } return null; } Future uploadPhoto(File file) async { - await profileRepository.uploadPhoto(await ImageCompressUtility.compressFile(file)); - return await getMyProfile(); + try { + await profileRepository.uploadPhoto(await ImageCompressUtility.compressFile(file)); + await getMyProfile(); + } catch (e, st) { + await reportFrom(e, st, this); + emit(ProfileState.error(errorMessage: e.toString())); + } } Future deletePhoto() async { - await profileRepository.deletePhoto(); - return await getMyProfile(); + try { + await profileRepository.deletePhoto(); + await getMyProfile(); + } catch (e, st) { + await reportFrom(e, st, this); + emit(ProfileState.error(errorMessage: e.toString())); + } } Future hideCertPhoto(int id) async { @@ -153,7 +167,8 @@ class ProfileCubit extends Cubit { if (currentState is ProfileStateLoaded) { emit(currentState.copyWith(certPhotos: certPhotos)); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); } } @@ -167,7 +182,8 @@ class ProfileCubit extends Cubit { if (currentState is ProfileStateLoaded) { emit(currentState.copyWith(certPhotos: certPhotos)); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); } } diff --git a/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart b/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart index 693fe8d5..c54d73bf 100644 --- a/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart +++ b/lib/features/profile/presentation/bloc/sub_cubit/sub_cubit.dart @@ -1,6 +1,7 @@ import 'package:eatvio_app/features/profile/presentation/bloc/sub_cubit/sub_state.dart'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class SubCubit extends Cubit { final ProfileRepository profileRepository; @@ -16,7 +17,8 @@ class SubCubit extends Cubit { return; } emit(SubState.loaded(subList: await profileRepository.getSubscriptionsBySlug(slug, query))); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(SubState.error(errorMessage: e.toString())); } } @@ -30,16 +32,25 @@ class SubCubit extends Cubit { return; } emit(SubState.loaded(subList: await profileRepository.getSubscribersBySlug(slug, query))); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(SubState.error(errorMessage: e.toString())); } } Future subscribe(String slug) async { - return await profileRepository.subscribe(slug); + try { + await profileRepository.subscribe(slug); + } catch (e, st) { + await reportFrom(e, st, this); + } } Future unsubscribe(String slug) async { - return await profileRepository.unsubscribe(slug); + try { + await profileRepository.unsubscribe(slug); + } catch (e, st) { + await reportFrom(e, st, this); + } } } diff --git a/lib/features/profile/repositories/profile_repository.dart b/lib/features/profile/repositories/profile_repository.dart index 766e03c1..44ad5060 100644 --- a/lib/features/profile/repositories/profile_repository.dart +++ b/lib/features/profile/repositories/profile_repository.dart @@ -9,7 +9,6 @@ import 'package:eatvio_app/features/profile/models/profile_model.dart'; import 'package:eatvio_app/features/profile/models/sub_model.dart'; import 'package:eatvio_app/features/profile/models/weight_model.dart'; import 'package:eatvio_app/features/profile/services/profile_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class ProfileRepository { final ProfileService profileService; @@ -17,210 +16,101 @@ class ProfileRepository { ProfileRepository({required this.profileService}); Future getProfileBySlug(String slug) async { - try { - return await profileService.getProfileBySlug(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.getProfileBySlug(slug: slug); } Future getProfileDetailsBySlug(String slug) async { - try { - return await profileService.getProfileDetailsBySlug(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.getProfileDetailsBySlug(slug: slug); } Future getMyProfile() async { - try { - return await profileService.getMyProfile(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.getMyProfile(); } Future getMyProfileDetails() async { - try { - return await profileService.getMyProfileDetails(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.getMyProfileDetails(); } Future addCircumference({required int chestCircumference, required int waistCircumference, required int hipCircumference}) async { - try { - return (await profileService.addCircumference( - chestCircumference: chestCircumference, - waistCircumference: waistCircumference, - hipCircumference: hipCircumference, - )); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.addCircumference( + chestCircumference: chestCircumference, + waistCircumference: waistCircumference, + hipCircumference: hipCircumference, + ); } Future addWeight({required double weight}) async { - try { - return (await profileService.addWeight(weight: weight)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.addWeight(weight: weight); } Future> getBlogPostsByAuthorSlug({required String slug}) async { - try { - return (await profileService.getBlogPostByAuthorSlug(slug: slug)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.getBlogPostByAuthorSlug(slug: slug); } Future> getWeight({required String period, String? slug}) async { - try { - if (slug != null) { - return (await profileService.getWeightBySlug(period: period, slug: slug)); - } else { - return (await profileService.getWeight(period: period)); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + if (slug != null) { + return profileService.getWeightBySlug(period: period, slug: slug); } + return profileService.getWeight(period: period); } Future> getCircumferences({required String period, String? slug}) async { - try { - if (slug != null) { - return (await profileService.getCircumferencesBySlug(period: period, slug: slug)); - } else { - return (await profileService.getCircumferences(period: period)); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + if (slug != null) { + return profileService.getCircumferencesBySlug(period: period, slug: slug); } + return profileService.getCircumferences(period: period); } Future> getSubscribersBySlug(String slug, [String? query]) async { - try { - return (await profileService.getSubscribers(slug: slug, query: query)).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await profileService.getSubscribers(slug: slug, query: query)).data; } Future> getSubscriptionsBySlug(String slug, [String? query]) async { - try { - return (await profileService.getSubscriptions(slug: slug, query: query)).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await profileService.getSubscriptions(slug: slug, query: query)).data; } Future subscribe(String slug) async { - try { - return await profileService.subscribe(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.subscribe(slug: slug); } Future unsubscribe(String slug) async { - try { - return await profileService.unsubscribe(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.unsubscribe(slug: slug); } Future uploadPhoto(File file) async { - try { - return await profileService.uploadPhoto(photo: file); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.uploadPhoto(photo: file); } Future deletePhoto() async { - try { - return await profileService.deletePhoto(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return profileService.deletePhoto(); } Future> getChosenDiets() async { - try { - return (await profileService.getChosenDiets()).diets; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await profileService.getChosenDiets()).diets; } Future getPassKey({required String routeName, Map? routeParams}) async { - try { - return (await profileService.getPassKey( - json: PassKeyParams(routeName: routeName, routeParams: routeParams), - )).data['key_is']!; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await profileService.getPassKey( + json: PassKeyParams(routeName: routeName, routeParams: routeParams), + )).data['key_is']!; } Future getMyPhone() async { - try { - return (jsonDecode(await profileService.getMyPhone())['phone']); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (jsonDecode(await profileService.getMyPhone())['phone']); } Future sendEmailCode({String? newEmail}) async { - try { - if (newEmail != null) { - return await profileService.sendEmailCode(data: {'new_email': newEmail}); - } else { - return await profileService.sendEmailCode(); - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + if (newEmail != null) { + return profileService.sendEmailCode(data: {'new_email': newEmail}); } + return profileService.sendEmailCode(); } - Future tryVerifyEmailByCode(String code) async { - try { - await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}"); - return true; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - return false; - } + Future tryVerifyEmailByCode(String code) async { + await profileService.tryVerifyEmailByCode(code: "{\"code\": \"$code\"}"); } Future deleteAccount() async { - try { - await profileService.deleteAccount(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await profileService.deleteAccount(); } } diff --git a/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart b/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart index 9e00eda9..9f8339d6 100644 --- a/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart +++ b/lib/features/recipe/presentation/cubit/about_recipe_cubit.dart @@ -7,6 +7,7 @@ import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository. import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/recipe/presentation/cubit/about_recipe_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class AboutRecipeCubit extends Cubit { AboutRecipeCubit({required this.recipeRepository, required this.profileRepository}) : super(const AboutRecipeState.initial()); @@ -23,7 +24,8 @@ class AboutRecipeCubit extends Cubit { recipeRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: recipe.commentsCount)); } emit(AboutRecipeState.loaded(recipe: recipe)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(AboutRecipeState.error(message: e.toString())); } @@ -37,7 +39,8 @@ class AboutRecipeCubit extends Cubit { recipeRepository.emitCommentsStateToFeed(CommentsState(index: index, totalComments: recipe.commentsCount)); } emit(AboutRecipeState.loaded(recipe: recipe)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(AboutRecipeState.error(message: e.toString())); } @@ -76,7 +79,8 @@ class AboutRecipeCubit extends Cubit { () async { try { await recipeRepository.likeRecipe(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); if (recipeIndexInFeed != null) { recipeRepository @@ -107,7 +111,8 @@ class AboutRecipeCubit extends Cubit { () async { try { await recipeRepository.addToFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (recipeIndex != null) { @@ -138,7 +143,8 @@ class AboutRecipeCubit extends Cubit { () async { try { await recipeRepository.removeFromFavorite(id); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); showErrorToast('Произошла ошибка. Попробуйте ещё раз через некоторое время'); if (recipeIndex != null) { diff --git a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart index db46b7b4..715eefaa 100644 --- a/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart +++ b/lib/features/recipe/presentation/cubit/my_recipes_cubit/my_recipes_cubit.dart @@ -8,7 +8,7 @@ import 'package:eatvio_app/features/recipe/presentation/cubit/my_recipes_cubit/m import 'package:eatvio_app/features/recipe/repositories/about_recipe_repository.dart'; import 'package:eatvio_app/features/recipe/repositories/my_recipes_repository.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'; class MyRecipesCubit extends Cubit { final MyRecipesRepository myRecipesRepository; @@ -31,7 +31,8 @@ class MyRecipesCubit extends Cubit { final myRecipes = await myRecipesRepository.getMyRecipes(); final List activeRecipes = myRecipes.where((recipe) => recipe.state == active).toList(); emit(MyRecipesState.loaded(recipes: activeRecipes, selectedState: active)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const MyRecipesState.error()); } } @@ -42,7 +43,8 @@ class MyRecipesCubit extends Cubit { final myRecipes = await myRecipesRepository.getMyRecipes(); final List activeRecipes = myRecipes.where((recipe) => recipe.state == active).toList(); emit(MyRecipesState.loaded(recipes: activeRecipes, selectedState: active)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const MyRecipesState.error()); } } @@ -53,7 +55,8 @@ class MyRecipesCubit extends Cubit { final myRecipes = await myRecipesRepository.getMyRecipes(); final List archivedRecipes = myRecipes.where((recipe) => recipe.state == archived).toList(); emit(MyRecipesState.loaded(recipes: archivedRecipes, selectedState: archived)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const MyRecipesState.error()); } } @@ -64,13 +67,19 @@ class MyRecipesCubit extends Cubit { final myRecipes = await myRecipesRepository.getMyRecipes(); final List draftedRecipes = myRecipes.where((recipe) => recipe.state == drafted).toList(); emit(MyRecipesState.loaded(recipes: draftedRecipes, selectedState: drafted)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const MyRecipesState.error()); } } - Future getRecipeById(String slug) async { - return await aboutRecipeRepository.getRecipe(slug); + Future getRecipeById(String slug) async { + try { + return await aboutRecipeRepository.getRecipe(slug); + } catch (e, st) { + await reportFrom(e, st, this); + return null; + } } /// Attempts to publish a drafted recipe @@ -81,10 +90,9 @@ class MyRecipesCubit extends Cubit { await recipeRepository.publishRecipe(recipe, true, slug); return true; } catch (e, st) { + await reportFrom(e, st, this); if (e is DioException && e.response?.statusCode == 422) { - log('Validation failed for publishing draft: \${e.response?.data}'); - } else { - await Sentry.captureException(e, stackTrace: st); + log('Validation failed for publishing draft: ${e.response?.data}'); } return false; } @@ -96,8 +104,8 @@ class MyRecipesCubit extends Cubit { await recipeRepository.draftExistingRecipe(id); return true; } catch (e, st) { - log('Failed to draft recipe: \$e'); - await Sentry.captureException(e, stackTrace: st); + await reportFrom(e, st, this); + log('Failed to draft recipe: $e'); return false; } } diff --git a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart index 9d0269de..ee6aa4ab 100644 --- a/lib/features/recipe/presentation/widgets/my_recipe_widget.dart +++ b/lib/features/recipe/presentation/widgets/my_recipe_widget.dart @@ -155,6 +155,10 @@ class _MyRecipeWidgetState extends State { final fullRecipe = await context.read().getRecipeById(widget.recipe.slug); if (context.mounted) { Navigator.pop(context); // close dialog + if (fullRecipe == null) { + showErrorToast('Не удалось загрузить рецепт'); + return; + } final result = await context.push(RecipeCreateScreen.path, extra: fullRecipe); if (!context.mounted) return; if (result == MyRecipesCubit.drafted) { @@ -226,11 +230,16 @@ class _MyRecipeWidgetState extends State { builder: (context) => const Center(child: CircularProgressIndicator()), ); final fullRecipe = await context.read().getRecipeById(widget.recipe.slug); + if (!context.mounted) return; + Navigator.pop(context); + if (fullRecipe == null) { + showErrorToast('Не удалось загрузить рецепт'); + return; + } final createCubit = sl(); await createCubit.recipeToEntity(fullRecipe); if (context.mounted) { - Navigator.pop(context); if (createCubit.validateAll()) { final success = await createCubit.publishRecipe(true, widget.recipe.slug); if (success && context.mounted) { diff --git a/lib/features/recipe/repositories/about_recipe_repository.dart b/lib/features/recipe/repositories/about_recipe_repository.dart index 4b6de461..8708a805 100644 --- a/lib/features/recipe/repositories/about_recipe_repository.dart +++ b/lib/features/recipe/repositories/about_recipe_repository.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:eatvio_app/features/diets/repositories/diet_repository.dart'; import 'package:eatvio_app/features/feed/models/recipes_model.dart'; import 'package:eatvio_app/features/recipe/services/about_recipe_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class AboutRecipeRepository { final AboutRecipeService recipeService; @@ -17,48 +16,28 @@ class AboutRecipeRepository { AboutRecipeRepository({required this.recipeService}); Future getRecipe(String recipeID) async { - try { - return await recipeService.getRecipe(recipeID); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await recipeService.getRecipe(recipeID); + } Future likeRecipe(String slug) async { - try { - await recipeService.likeRecipe(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await recipeService.likeRecipe(slug: slug); + } Future addToFavorite(String slug) async { - try { - await recipeService.addToFavorite(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await recipeService.addToFavorite(slug: slug); + } Future removeFromFavorite(String slug) async { - try { - await recipeService.removeFromFavorite(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await recipeService.removeFromFavorite(slug: slug); + } Future deleteRecipe(String slug) async { - try { - await recipeService.deleteRecipe(slug: slug); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + await recipeService.deleteRecipe(slug: slug); + } void emitLikesStateToFeed(LikesState state) => _recipeLikeStateStreamController.add(state); diff --git a/lib/features/recipe/repositories/my_recipes_repository.dart b/lib/features/recipe/repositories/my_recipes_repository.dart index fb67f395..4b2b91e3 100644 --- a/lib/features/recipe/repositories/my_recipes_repository.dart +++ b/lib/features/recipe/repositories/my_recipes_repository.dart @@ -1,6 +1,5 @@ import 'package:eatvio_app/features/recipe/models/my_recipe_model.dart'; import 'package:eatvio_app/features/recipe/services/my_recipes_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class MyRecipesRepository { final MyRecipesService myRecipesService; @@ -8,11 +7,7 @@ class MyRecipesRepository { MyRecipesRepository({required this.myRecipesService}); Future> getMyRecipes() async { - try { - return (await myRecipesService.getMyRecipes()).data; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return (await myRecipesService.getMyRecipes()).data; + } } \ No newline at end of file diff --git a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart index 3bd0a6a1..e9260cfd 100644 --- a/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart +++ b/lib/features/registration/presentation/bloc/registration_cubit/registration_cubit.dart @@ -9,9 +9,11 @@ import 'package:eatvio_app/core/prefs.dart'; import 'package:eatvio_app/core/token_storage.dart'; import 'package:eatvio_app/features/registration/models/user_registration_model.dart'; import 'package:eatvio_app/features/registration/presentation/bloc/registration_cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration/registration_error_message.dart'; import 'package:eatvio_app/features/registration/repositories/registration_repository.dart'; import 'package:eatvio_app/features/registration/services/registration_analytics_service.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'; class RegistrationCubit extends Cubit with RegistrationDateCacheMixin { @@ -65,9 +67,11 @@ class RegistrationCubit extends Cubit with RegistrationDateCa cacheRegistrationDate(DateTime.now()); AppRouter.navigatorKey.currentContext?.replaceNamed('verifyEmailScreen'); } catch (e, st) { - log(e.toString(), stackTrace: st); - RegistrationAnalyticsService.reportRegistrationError(e.toString()); - emit(RegistrationState(sheet: state.sheet, error: e.toString())); + await reportFrom(e, st, this); + final message = registrationErrorMessage(e); + log(message, stackTrace: st); + RegistrationAnalyticsService.reportRegistrationError(message); + emit(RegistrationState(sheet: state.sheet, error: message)); } } @@ -76,8 +80,9 @@ class RegistrationCubit extends Cubit with RegistrationDateCa final location = (await registrationRepository.getLocationsListByQuery(query)); emit(RegistrationState(sheet: state.sheet.copyWith(responseBody: location.responseBody))); return location.locations; - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); + return []; } } @@ -86,8 +91,9 @@ class RegistrationCubit extends Cubit with RegistrationDateCa final location = (await registrationRepository.getLocationsListByCoordinates(latitude, longitude)); emit(RegistrationState(sheet: state.sheet.copyWith(responseBody: location.responseBody))); return location.locations; - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); + return []; } } } diff --git a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart index 7c9468d8..304af8e3 100644 --- a/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart +++ b/lib/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit.dart @@ -4,6 +4,7 @@ import 'package:eatvio_app/features/profile/repositories/profile_repository.dart import 'package:eatvio_app/features/registration/presentation/bloc/verify_email_cubit/verify_email_state.dart'; import 'package:eatvio_app/features/registration/services/registration_analytics_service.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class VerifyEmailCubit extends Cubit { final ProfileRepository profileRepository; @@ -16,7 +17,8 @@ class VerifyEmailCubit extends Cubit { final profile = await profileRepository.getMyProfile(); await profileRepository.sendEmailCode(); emit(VerifyEmailState.loaded(email: profile.email)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const VerifyEmailState.error(errorMessage: 'Ошибка отправки кода.\nПовторите попытку позже')); } } @@ -24,18 +26,15 @@ class VerifyEmailCubit extends Cubit { Future tryVerify(String code) async { try { emit(const VerifyEmailState.verifyLoading()); - final result = await profileRepository.tryVerifyEmailByCode(code); - if (result == true) { - emit(const VerifyEmailState.verifySuccess()); - try { - RegistrationAnalyticsService.reportEmailVerification(); - } catch (e) { - // Игнорируем ошибки аналитики, верификация уже успешна - } - } else { - emit(const VerifyEmailState.verifyError()); + await profileRepository.tryVerifyEmailByCode(code); + emit(const VerifyEmailState.verifySuccess()); + try { + RegistrationAnalyticsService.reportEmailVerification(); + } catch (e, st) { + await reportFrom(e, st, this); } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const VerifyEmailState.verifyError()); } } @@ -47,7 +46,8 @@ class VerifyEmailCubit extends Cubit { emit(const VerifyEmailState.changeEmailSuccess()); // Обновляем email в состоянии после успешной смены emit(VerifyEmailState.loaded(email: newEmail)); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(const VerifyEmailState.changeEmailError()); } } diff --git a/lib/features/registration/registration_error_message.dart b/lib/features/registration/registration_error_message.dart new file mode 100644 index 00000000..dfe1159b --- /dev/null +++ b/lib/features/registration/registration_error_message.dart @@ -0,0 +1,35 @@ +import 'package:dio/dio.dart'; + +/// Сообщение для UI / аналитики при ошибке регистрации (вызывать из кубита). +String registrationErrorMessage(Object error) { + if (error is String) { + return error; + } + if (error is DioException && error.response?.statusCode == 422) { + final data = error.response?.data; + if (data is Map) { + var errorMsg = ''; + if (data.containsKey('message')) { + errorMsg += '${data['message']}\n'; + } + if (data.containsKey('errors')) { + final errors = data['errors']; + if (errors is Map) { + errors.forEach((key, value) { + if (value is List && value.isNotEmpty) { + errorMsg += '${value.join('\n')}\n'; + } + }); + } + } + if (errorMsg.isNotEmpty) { + return errorMsg.trim(); + } + return 'Ошибка регистрации: $data'; + } + } + if (error is DioException) { + return error.message ?? error.toString(); + } + return error.toString(); +} diff --git a/lib/features/registration/repositories/registration_repository.dart b/lib/features/registration/repositories/registration_repository.dart index 7ab8c43a..fe02eea5 100644 --- a/lib/features/registration/repositories/registration_repository.dart +++ b/lib/features/registration/repositories/registration_repository.dart @@ -1,10 +1,9 @@ import 'package:dio/dio.dart'; -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/features/registration/models/user_registration_model.dart'; -import 'package:eatvio_app/features/registration/models/location_model.dart'; -import 'package:eatvio_app/features/registration/services/registration_service.dart'; import 'package:eatvio_app/core/env_config.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/features/registration/models/location_model.dart'; +import 'package:eatvio_app/features/registration/models/user_registration_model.dart'; +import 'package:eatvio_app/features/registration/services/registration_service.dart'; class RegistrationRepository { final RegistrationService registrationService; @@ -12,94 +11,36 @@ class RegistrationRepository { RegistrationRepository({required this.registrationService}); Future tryRegister(UserRegistrationSheet sheet) async { - try { - final response = await registrationService.register(sheet: sheet.toJson()); - if (response.containsKey('token')) { - return response['token']!; - } else { - // Если нет токена, но есть message или errors - String errorMsg = ''; - if (response.containsKey('message')) { - errorMsg += '${response['message']!}\n'; - } - if (response.containsKey('errors')) { - final errors = response['errors']; - if (errors != null && errors is Map) { - (errors as Map).forEach((key, value) { - if (value is List && value.isNotEmpty) { - errorMsg += '${value.join('\n')}\n'; - } - }); - } - } - if (errorMsg.isNotEmpty) { - throw errorMsg.trim(); - } else { - throw 'Ошибка регистрации: $response'; - } - } - } on DioException catch (e, s) { - if (e.response != null && e.response?.statusCode == 422) { - final data = e.response?.data; - String errorMsg = ''; - if (data is Map) { - if (data.containsKey('message')) { - errorMsg += data['message'] + '\n'; - } - if (data.containsKey('errors')) { - final errors = data['errors']; - if (errors != null && errors is Map) { - (errors).forEach((key, value) { - if (value is List && value.isNotEmpty) { - errorMsg += '${value.join('\n')}\n'; - } - }); - } - } - } - if (errorMsg.isNotEmpty) { - throw errorMsg.trim(); - } else { - throw 'Ошибка регистрации: $data'; - } - } - await Sentry.captureException(e, stackTrace: s); - rethrow; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; + final response = await registrationService.register(sheet: sheet.toJson()); + if (response.containsKey('token')) { + return response['token']!; } + var errorMsg = ''; + if (response.containsKey('message')) { + errorMsg += '${response['message']}\n'; + } + if (response.containsKey('errors')) { + errorMsg += '${response['errors']}\n'; + } + if (errorMsg.isNotEmpty) { + throw errorMsg.trim(); + } + throw 'Ошибка регистрации: $response'; } Future getLocationsListByQuery(String query) async { - try { - return (await registrationService.getLocationsByAddress(query: query)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return registrationService.getLocationsByAddress(query: query); } Future getLocationsListByCoordinates(String latitude, String longitude) async { - try { - return (await registrationService.getLocationsByCoordinate(latitude: latitude, longitude: longitude)); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return registrationService.getLocationsByCoordinate(latitude: latitude, longitude: longitude); } Future emailIsReachable(String email) async { - try { - final response = await sl().getUri(Uri.parse('${EnvConfig.emailValidationApiUrl}?email=$email')); - if (response.data['result']['reachable'] != 'invalid') { - return true; - } else { - return false; - } - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - return false; + final response = await sl().getUri(Uri.parse('${EnvConfig.emailValidationApiUrl}?email=$email')); + if (response.data['result']['reachable'] != 'invalid') { + return true; } + return false; } } diff --git a/lib/features/registration_v2/cubit/registration_cubit.dart b/lib/features/registration_v2/cubit/registration_cubit.dart index cfc72b4b..585d1c41 100644 --- a/lib/features/registration_v2/cubit/registration_cubit.dart +++ b/lib/features/registration_v2/cubit/registration_cubit.dart @@ -1,267 +1,270 @@ -import 'dart:developer'; - -import 'package:dio/dio.dart'; -import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/core/utils/date_time_utils.dart'; -import 'package:eatvio_app/core/utils/validation_utils.dart'; -import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; -import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; -import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:gpassword/gpassword.dart'; - -class RegistrationCubitV2 extends Cubit { - final RegistrationRepositoryV2 registrationRepository; - RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2()); - - void setFirstName(String firstName) { - emit(state.copyWith(firstName: firstName)); - validateFirstName(); - } - - void setLastName(String lastName) { - emit(state.copyWith(lastName: lastName)); - validateLastName(); - } - - void setEmail(String email) { - emit(state.copyWith(email: email)); - validateEmail(); - } - - void setPhone(String phone) { - emit(state.copyWith(phone: phone)); - validatePhone(); - } - - void setBirthday(DateTime birthday) { - emit(state.copyWith(birthday: birthday)); - validateBirthday(); - } - - void setGender(String gender) { - emit(state.copyWith(gender: gender)); - validateGender(); - } - - void setWeight(int weight) { - emit(state.copyWith(weight: weight)); - validateWeight(); - } - - void setHeight(int height) { - emit(state.copyWith(height: height)); - validateHeight(); - } - - void setActivityCoefficient(double activityCoefficient) { - emit(state.copyWith(activityCoefficient: activityCoefficient)); - validateActivityCoefficient(); - } - - void setPassword(String password) { - emit(state.copyWith(password: password)); - } - - bool validateFirstName() { - if (state.firstName.isEmpty) { - emit(state.copyWith(isFirstNameValid: false)); - return false; - } - emit(state.copyWith(isFirstNameValid: true)); - return true; - } - - bool validateLastName() { - if (state.lastName.isEmpty) { - emit(state.copyWith(isLastNameValid: false)); - return false; - } - emit(state.copyWith(isLastNameValid: true)); - return true; - } - - bool validateEmail() { - if (state.email.isEmpty) { - emit(state.copyWith(isEmailValid: false)); - return false; - } - emit(state.copyWith(isEmailValid: true)); - return true; - } - - bool validatePhone() { - if (state.phone.isEmpty) { - emit(state.copyWith(isPhoneValid: false)); - return false; - } - emit(state.copyWith(isPhoneValid: true)); - return true; - } - - bool validateGender() { - if (state.gender.isEmpty) { - emit(state.copyWith(isGenderValid: false)); - return false; - } - emit(state.copyWith(isGenderValid: true)); - return true; - } - - bool validateWeight() { - if (state.weight == 0) { - emit(state.copyWith(isWeightValid: false)); - return false; - } - emit(state.copyWith(isWeightValid: true)); - return true; - } - - bool validateHeight() { - if (state.height == 0) { - emit(state.copyWith(isHeightValid: false)); - return false; - } - emit(state.copyWith(isHeightValid: true)); - return true; - } - - bool validateActivityCoefficient() { - if (state.activityCoefficient == 0) { - emit(state.copyWith(isActivityCoefficientValid: false)); - return false; - } - emit(state.copyWith(isActivityCoefficientValid: true)); - return true; - } - - bool validateBirthday() { - if (state.birthday == null) { - emit(state.copyWith(isBirthdayValid: false)); - return false; - } - emit(state.copyWith(isBirthdayValid: true)); - return true; - } - - void nextStep() { - emit(state.copyWith(step: 1)); - } - - void prevStep() { - emit(state.copyWith(step: 0)); - } - - bool validateAllFields() { - if (!validateFirstName() || - !validateLastName() || - !validateEmail() || - !validatePhone() || - !validateGender() || - !validateWeight() || - !validateHeight() || - !validateActivityCoefficient() || - !validateBirthday()) { - return false; - } - return true; - } - - Future<(bool, bool)> register() async { - if (!validateAllFields()) { - return (false, true); - } - final birthday = state.birthday?.format('yyyy-MM-dd'); - if (birthday == null) { - validateBirthday(); - return (false, true); - } - final password = await _generatePassword(); - final registrationModel = RegistrationModel( - firstName: state.firstName, - lastName: state.lastName, - email: state.email, - phone: ValidationUtils.normalizePhone(state.phone), - gender: state.gender, - password: password, - confirmPassword: password, - weight: state.weight, - height: state.height, - activityCoefficient: state.activityCoefficient, - birthday: birthday, - ); - bool didApiComplainOnValidation = false; - String errorMessage = ''; - try { - final token = await registrationRepository.register(registrationModel: registrationModel); - if (token != null) { - TokenStorage.saveTokenInStorage(token); - emit(state.copyWith(registrationError: null)); - return (true, false); - } - } on DioException catch (e) { - final errors = e.response?.data['errors'] as Map?; - final emailErrors = errors?['email'] as List?; - final phoneErrors = errors?['phone'] as List?; - final dateOfBirth = errors?['date_of_birth'] as List?; - - // Собираем все ошибки для отображения в диалоге - List allErrors = []; - - if (emailErrors != null && emailErrors.isNotEmpty) { - for (var error in emailErrors) { - allErrors.add(error.toString()); - if (error.toString() == 'Некорректный формат email') { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email')); - didApiComplainOnValidation = true; - } - if (error.toString() == 'Такой Эл. почта уже существует.') { - emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.')); - didApiComplainOnValidation = true; - } - } - } - if (phoneErrors != null && phoneErrors.isNotEmpty) { - for (var error in phoneErrors) { - allErrors.add(error.toString()); - if (error.toString() == 'Такой phone уже существует.') { - emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.')); - didApiComplainOnValidation = true; - } - } - } - if (dateOfBirth != null && dateOfBirth.isNotEmpty) { - for (var error in dateOfBirth) { - allErrors.add(error.toString()); - if (error.toString() == 'Некорректный формат даты рождения') { - emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения')); - didApiComplainOnValidation = true; - } - } - } - - // Сохраняем все ошибки для отображения в диалоге - if (allErrors.isNotEmpty) { - errorMessage = allErrors.join('\n'); - emit(state.copyWith(registrationError: errorMessage)); - } else { - // Если нет конкретных ошибок, берем общее сообщение - final message = e.response?.data['message'] as String?; - errorMessage = message ?? 'Ошибка при регистрации'; - emit(state.copyWith(registrationError: errorMessage)); - } - } catch (e) { - log(e.toString()); - errorMessage = e.toString(); - emit(state.copyWith(registrationError: errorMessage)); - } - return (false, didApiComplainOnValidation); - } - - Future _generatePassword() async { - final generator = GPassword(); - final password = generator.generate(passwordLength: 10); - emit(state.copyWith(password: password)); - return password; - } -} +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/core/utils/validation_utils.dart'; +import 'package:eatvio_app/features/registration_v2/cubit/registration_state.dart'; +import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; +import 'package:eatvio_app/features/registration_v2/repositories/registration_repository_v2.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; +import 'package:gpassword/gpassword.dart'; + +class RegistrationCubitV2 extends Cubit { + final RegistrationRepositoryV2 registrationRepository; + RegistrationCubitV2({required this.registrationRepository}) : super(const RegistrationStateV2()); + + void setFirstName(String firstName) { + emit(state.copyWith(firstName: firstName)); + validateFirstName(); + } + + void setLastName(String lastName) { + emit(state.copyWith(lastName: lastName)); + validateLastName(); + } + + void setEmail(String email) { + emit(state.copyWith(email: email)); + validateEmail(); + } + + void setPhone(String phone) { + emit(state.copyWith(phone: phone)); + validatePhone(); + } + + void setBirthday(DateTime birthday) { + emit(state.copyWith(birthday: birthday)); + validateBirthday(); + } + + void setGender(String gender) { + emit(state.copyWith(gender: gender)); + validateGender(); + } + + void setWeight(int weight) { + emit(state.copyWith(weight: weight)); + validateWeight(); + } + + void setHeight(int height) { + emit(state.copyWith(height: height)); + validateHeight(); + } + + void setActivityCoefficient(double activityCoefficient) { + emit(state.copyWith(activityCoefficient: activityCoefficient)); + validateActivityCoefficient(); + } + + void setPassword(String password) { + emit(state.copyWith(password: password)); + } + + bool validateFirstName() { + if (state.firstName.isEmpty) { + emit(state.copyWith(isFirstNameValid: false)); + return false; + } + emit(state.copyWith(isFirstNameValid: true)); + return true; + } + + bool validateLastName() { + if (state.lastName.isEmpty) { + emit(state.copyWith(isLastNameValid: false)); + return false; + } + emit(state.copyWith(isLastNameValid: true)); + return true; + } + + bool validateEmail() { + if (state.email.isEmpty) { + emit(state.copyWith(isEmailValid: false)); + return false; + } + emit(state.copyWith(isEmailValid: true)); + return true; + } + + bool validatePhone() { + if (state.phone.isEmpty) { + emit(state.copyWith(isPhoneValid: false)); + return false; + } + emit(state.copyWith(isPhoneValid: true)); + return true; + } + + bool validateGender() { + if (state.gender.isEmpty) { + emit(state.copyWith(isGenderValid: false)); + return false; + } + emit(state.copyWith(isGenderValid: true)); + return true; + } + + bool validateWeight() { + if (state.weight == 0) { + emit(state.copyWith(isWeightValid: false)); + return false; + } + emit(state.copyWith(isWeightValid: true)); + return true; + } + + bool validateHeight() { + if (state.height == 0) { + emit(state.copyWith(isHeightValid: false)); + return false; + } + emit(state.copyWith(isHeightValid: true)); + return true; + } + + bool validateActivityCoefficient() { + if (state.activityCoefficient == 0) { + emit(state.copyWith(isActivityCoefficientValid: false)); + return false; + } + emit(state.copyWith(isActivityCoefficientValid: true)); + return true; + } + + bool validateBirthday() { + if (state.birthday == null) { + emit(state.copyWith(isBirthdayValid: false)); + return false; + } + emit(state.copyWith(isBirthdayValid: true)); + return true; + } + + void nextStep() { + emit(state.copyWith(step: 1)); + } + + void prevStep() { + emit(state.copyWith(step: 0)); + } + + bool validateAllFields() { + if (!validateFirstName() || + !validateLastName() || + !validateEmail() || + !validatePhone() || + !validateGender() || + !validateWeight() || + !validateHeight() || + !validateActivityCoefficient() || + !validateBirthday()) { + return false; + } + return true; + } + + Future<(bool, bool)> register() async { + if (!validateAllFields()) { + return (false, true); + } + final birthday = state.birthday?.format('yyyy-MM-dd'); + if (birthday == null) { + validateBirthday(); + return (false, true); + } + final password = await _generatePassword(); + final registrationModel = RegistrationModel( + firstName: state.firstName, + lastName: state.lastName, + email: state.email, + phone: ValidationUtils.normalizePhone(state.phone), + gender: state.gender, + password: password, + confirmPassword: password, + weight: state.weight, + height: state.height, + activityCoefficient: state.activityCoefficient, + birthday: birthday, + ); + bool didApiComplainOnValidation = false; + String errorMessage = ''; + try { + final token = await registrationRepository.register(registrationModel: registrationModel); + if (token != null) { + TokenStorage.saveTokenInStorage(token); + emit(state.copyWith(registrationError: null)); + return (true, false); + } + } on DioException catch (e, st) { + await reportFrom(e, st, this); + final errors = e.response?.data['errors'] as Map?; + final emailErrors = errors?['email'] as List?; + final phoneErrors = errors?['phone'] as List?; + final dateOfBirth = errors?['date_of_birth'] as List?; + + // Собираем все ошибки для отображения в диалоге + List allErrors = []; + + if (emailErrors != null && emailErrors.isNotEmpty) { + for (var error in emailErrors) { + allErrors.add(error.toString()); + if (error.toString() == 'Некорректный формат email') { + emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Некорректный формат email')); + didApiComplainOnValidation = true; + } + if (error.toString() == 'Такой Эл. почта уже существует.') { + emit(state.copyWith(isEmailValid: false, emailDisplayError: 'Такая Эл. почта уже существует.')); + didApiComplainOnValidation = true; + } + } + } + if (phoneErrors != null && phoneErrors.isNotEmpty) { + for (var error in phoneErrors) { + allErrors.add(error.toString()); + if (error.toString() == 'Такой phone уже существует.') { + emit(state.copyWith(isPhoneValid: false, phoneDisplayError: 'Такой номер телефона уже существует.')); + didApiComplainOnValidation = true; + } + } + } + if (dateOfBirth != null && dateOfBirth.isNotEmpty) { + for (var error in dateOfBirth) { + allErrors.add(error.toString()); + if (error.toString() == 'Некорректный формат даты рождения') { + emit(state.copyWith(isBirthdayValid: false, dateOfBirthDisplayError: 'Некорректный формат даты рождения')); + didApiComplainOnValidation = true; + } + } + } + + // Сохраняем все ошибки для отображения в диалоге + if (allErrors.isNotEmpty) { + errorMessage = allErrors.join('\n'); + emit(state.copyWith(registrationError: errorMessage)); + } else { + // Если нет конкретных ошибок, берем общее сообщение + final message = e.response?.data['message'] as String?; + errorMessage = message ?? 'Ошибка при регистрации'; + emit(state.copyWith(registrationError: errorMessage)); + } + } catch (e, st) { + await reportFrom(e, st, this); + log(e.toString()); + errorMessage = e.toString(); + emit(state.copyWith(registrationError: errorMessage)); + } + return (false, didApiComplainOnValidation); + } + + Future _generatePassword() async { + final generator = GPassword(); + final password = generator.generate(passwordLength: 10); + emit(state.copyWith(password: password)); + return password; + } +} diff --git a/lib/features/registration_v2/repositories/registration_repository_v2.dart b/lib/features/registration_v2/repositories/registration_repository_v2.dart index 7640ec30..38c173e7 100644 --- a/lib/features/registration_v2/repositories/registration_repository_v2.dart +++ b/lib/features/registration_v2/repositories/registration_repository_v2.dart @@ -1,18 +1,13 @@ import 'package:eatvio_app/features/registration_v2/models/registration_model.dart'; import 'package:eatvio_app/features/registration_v2/services/registration_service_v2.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class RegistrationRepositoryV2 { final RegistrationServiceV2 registrationService; RegistrationRepositoryV2({required this.registrationService}); Future register({required RegistrationModel registrationModel}) async { - try { - final response = await registrationService.register(model: registrationModel.toJson()); - return response['token']; - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + final response = await registrationService.register(model: registrationModel.toJson()); + return response['token']; + } } diff --git a/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart b/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart index ed123203..f468a1ce 100644 --- a/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart +++ b/lib/features/settings/presentation/bloc/settings_cubit/settings_cubit.dart @@ -10,6 +10,7 @@ import 'package:eatvio_app/features/settings/models/profile_settings_model.dart' import 'package:eatvio_app/features/settings/presentation/bloc/settings_cubit/settings_state.dart'; import 'package:eatvio_app/features/settings/repositories/service_repository.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class SettingsCubit extends Cubit { final ProfileRepository profileRepository; @@ -36,20 +37,23 @@ class SettingsCubit extends Cubit { } else { consultantInfo = null; } - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); consultantInfo = null; } try { hasActiveConsultation = await _checkActiveConsultation(); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); hasActiveConsultation = false; } try { final oauthResponse = await settingsRepository.getOAuthAccounts(); oauthAccounts = oauthResponse.data; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); oauthAccounts = null; } @@ -64,7 +68,8 @@ class SettingsCubit extends Cubit { oauthAccounts: oauthAccounts, ), ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); emit(SettingsState.error(errorMessage: e.toString())); } } @@ -72,8 +77,8 @@ class SettingsCubit extends Cubit { Future updateProfileDetails({required ProfileDetailsModel profileDetailsModel}) async { try { await settingsRepository.updateProfileDetails(profileDetailsModel: profileDetailsModel); - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); } finally { await init(); } @@ -82,8 +87,8 @@ class SettingsCubit extends Cubit { Future updateProfileSettings({required ProfileSettingsModel profileSettingsModel}) async { try { await settingsRepository.updateProfileSettings(profileSettingsModel: profileSettingsModel); - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); } finally { await init(); } @@ -93,7 +98,8 @@ class SettingsCubit extends Cubit { try { await profileRepository.deleteAccount(); return true; - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); return false; } } @@ -113,8 +119,8 @@ class SettingsCubit extends Cubit { ); await settingsRepository.linkOAuthAccount(request: request); await init(); - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); } } @@ -122,8 +128,8 @@ class SettingsCubit extends Cubit { try { await settingsRepository.unlinkOAuthAccount(provider: provider); await init(); - } catch (e) { - rethrow; + } catch (e, st) { + await reportFrom(e, st, this); } } @@ -135,7 +141,8 @@ class SettingsCubit extends Cubit { return userPurchases.any((purchase) => activeStates.contains(purchase.chosenPlanState) ); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); return false; } } diff --git a/lib/features/settings/repositories/service_repository.dart b/lib/features/settings/repositories/service_repository.dart index aa86ec57..efbd117a 100644 --- a/lib/features/settings/repositories/service_repository.dart +++ b/lib/features/settings/repositories/service_repository.dart @@ -3,7 +3,6 @@ import 'package:eatvio_app/features/settings/models/oauth_account_model.dart'; import 'package:eatvio_app/features/settings/models/oauth_attach_request.dart'; import 'package:eatvio_app/features/settings/models/profile_settings_model.dart'; import 'package:eatvio_app/features/settings/services/settings_service.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; class SettingsRepository { final SettingsService settingsService; @@ -11,56 +10,32 @@ class SettingsRepository { const SettingsRepository({required this.settingsService}); Future getProfileSettings() async { - try { - return await settingsService.getProfileSettings(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.getProfileSettings(); + } Future updateProfileSettings({required ProfileSettingsModel profileSettingsModel}) async { - try { - return await settingsService.updateProfileSettings(data: profileSettingsModel.toJson()); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.updateProfileSettings(data: profileSettingsModel.toJson()); + } Future updateProfileDetails({required ProfileDetailsModel profileDetailsModel}) async { - try { - return await settingsService.updateProfileDetails(data: profileDetailsModel.toJson()); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.updateProfileDetails(data: profileDetailsModel.toJson()); + } Future getOAuthAccounts() async { - try { - return await settingsService.getOAuthAccounts(); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.getOAuthAccounts(); + } Future linkOAuthAccount({required OAuthAttachRequest request}) async { - try { - return await settingsService.linkOAuthAccount(request); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.linkOAuthAccount(request); + } Future unlinkOAuthAccount({required String provider}) async { - try { - return await settingsService.unlinkOAuthAccount(provider); - } catch (e, s) { - await Sentry.captureException(e, stackTrace: s); - rethrow; - } + return await settingsService.unlinkOAuthAccount(provider); + } } diff --git a/lib/features/user_details/presentation/cubit/user_details_cubit.dart b/lib/features/user_details/presentation/cubit/user_details_cubit.dart index 0ce0bd93..6735043b 100644 --- a/lib/features/user_details/presentation/cubit/user_details_cubit.dart +++ b/lib/features/user_details/presentation/cubit/user_details_cubit.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:eatvio_app/features/profile/repositories/profile_repository.dart'; import 'package:eatvio_app/features/user_details/presentation/cubit/user_details_state.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:eatvio_app/core/error_reporting/report_from.dart'; class UserDetailsCubit extends Cubit { final ProfileRepository profileRepository; @@ -14,7 +15,8 @@ class UserDetailsCubit extends Cubit { final profileDetails = await profileRepository.getProfileDetailsBySlug(slug); emit(state.copyWith(profileDetails: profileDetails, isLoading: false)); getChartData(slug: slug); - } catch (e) { + } catch (e, st) { + await reportFrom(e, st, this); log(e.toString()); emit(state.copyWith(error: e.toString(), isLoading: false)); } @@ -26,6 +28,7 @@ class UserDetailsCubit extends Cubit { final circumferencePoints = await profileRepository.getCircumferences(period: period, slug: slug); emit(state.copyWith(weightPoints: weightPoints, circumferencePoints: circumferencePoints)); } catch (e, stackTrace) { + await reportFrom(e, stackTrace, this); log(e.toString(), stackTrace: stackTrace); emit(state.copyWith(error: e.toString(), isLoading: false)); } diff --git a/test/core/cubit/marketing/marketing_cubit_test.dart b/test/core/cubit/marketing/marketing_cubit_test.dart index 9fb33df0..77344fa9 100644 --- a/test/core/cubit/marketing/marketing_cubit_test.dart +++ b/test/core/cubit/marketing/marketing_cubit_test.dart @@ -103,11 +103,12 @@ void main() { .called(1); }); - test('rethrows exception when error occurs', () async { + test('returns false when prefs throws (error is reported)', () async { when(mockPrefs.getInt(PrefsKeys.registrationDate)) .thenThrow(Exception('Test error')); - expect(() => cubit.checkRegistrationDate(), throwsException); + final result = await cubit.checkRegistrationDate(); + expect(result, false); }); }); } diff --git a/test/core/error_reporting/error_report_policy_test.dart b/test/core/error_reporting/error_report_policy_test.dart new file mode 100644 index 00000000..52bbc6e5 --- /dev/null +++ b/test/core/error_reporting/error_report_policy_test.dart @@ -0,0 +1,114 @@ +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/error_reporting/error_report_policy.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +void main() { + group('ErrorReportPolicy.shouldReport', () { + test('returns false for null', () { + expect(ErrorReportPolicy.shouldReport(null), false); + }); + + test('returns false for String', () { + expect(ErrorReportPolicy.shouldReport('user message'), false); + }); + + test('returns false for SocketException', () { + expect(ErrorReportPolicy.shouldReport(const SocketException('offline')), false); + }); + + test('returns false for cancelled Dio request', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + type: DioExceptionType.cancel, + ); + expect(ErrorReportPolicy.shouldReport(e), false); + }); + + test('returns false for Dio timeouts', () { + for (final type in [ + DioExceptionType.connectionTimeout, + DioExceptionType.sendTimeout, + DioExceptionType.receiveTimeout, + ]) { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + type: type, + ); + expect(ErrorReportPolicy.shouldReport(e), false, reason: '$type'); + } + }); + + test('returns false for HTTP 4xx with response', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + response: Response(statusCode: 422, requestOptions: RequestOptions(path: '/x')), + type: DioExceptionType.badResponse, + ); + expect(ErrorReportPolicy.shouldReport(e), false); + }); + + test('returns false for HTTP 5xx (handled by sentry_dio failed-request)', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + response: Response(statusCode: 503, requestOptions: RequestOptions(path: '/x')), + type: DioExceptionType.badResponse, + ); + expect(ErrorReportPolicy.shouldReport(e), false); + }); + + test('returns false for Dio connectionError / offline-style', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + type: DioExceptionType.connectionError, + error: const SocketException('offline'), + ); + expect(ErrorReportPolicy.shouldReport(e), false); + }); + + test('returns true for arbitrary exceptions', () { + expect(ErrorReportPolicy.shouldReport(StateError('x')), true); + }); + }); + + group('ErrorReportPolicy.allowSentryEvent', () { + final hint = Hint(); + + test('allows 5xx Dio so sentry_dio events are not dropped in beforeSend', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + response: Response(statusCode: 503, requestOptions: RequestOptions(path: '/x')), + type: DioExceptionType.badResponse, + ); + final event = SentryEvent(throwable: e); + expect(ErrorReportPolicy.allowSentryEvent(event, hint), true); + }); + + test('drops 4xx Dio from SDK', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + response: Response(statusCode: 422, requestOptions: RequestOptions(path: '/x')), + type: DioExceptionType.badResponse, + ); + final event = SentryEvent(throwable: e); + expect(ErrorReportPolicy.allowSentryEvent(event, hint), false); + }); + + test('drops transient Dio from SDK (connectionError)', () { + final e = DioException( + requestOptions: RequestOptions(path: '/x'), + type: DioExceptionType.connectionError, + error: const SocketException('reset'), + ); + final event = SentryEvent(throwable: e); + expect(ErrorReportPolicy.allowSentryEvent(event, hint), false); + }); + + test('drops SocketException from SDK when not wrapped in Dio', () { + final event = SentryEvent(throwable: const SocketException('x')); + expect(ErrorReportPolicy.allowSentryEvent(event, hint), false); + }); + }); +} diff --git a/test/core/firebase_api_test.dart b/test/core/firebase_api_test.dart index 00423672..e4948f6e 100644 --- a/test/core/firebase_api_test.dart +++ b/test/core/firebase_api_test.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/error_reporting/no_op_error_reporter.dart'; import 'package:eatvio_app/core/firebase_api.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,7 +8,7 @@ void main() { // В тестах Firebase не может быть инициализирован // Проверяем, что попытка создания приводит к ожидаемой ошибке expect( - () => FirebaseApi(), + () => FirebaseApi(errorReporter: const NoOpErrorReporter()), throwsA(isA()), ); }); @@ -16,7 +17,7 @@ void main() { // Метод getToken требует инициализированный Firebase // В тестах мы проверяем, что он выбрасывает ожидаемую ошибку try { - final api = FirebaseApi(); + final api = FirebaseApi(errorReporter: const NoOpErrorReporter()); expect(() => api.getToken(), throwsA(isA())); } catch (e) { expect(e.toString(), contains('No Firebase App')); @@ -26,7 +27,7 @@ void main() { test('registerAnonymousPushToken возвращает Future', () { // Метод registerAnonymousPushToken требует инициализированный Firebase try { - final api = FirebaseApi(); + final api = FirebaseApi(errorReporter: const NoOpErrorReporter()); expect(() => api.registerAnonymousPushToken(), throwsA(isA())); } catch (e) { expect(e.toString(), contains('No Firebase App')); @@ -36,7 +37,7 @@ void main() { test('registerPushToken возвращает Future', () { // Метод registerPushToken требует инициализированный Firebase try { - final api = FirebaseApi(); + final api = FirebaseApi(errorReporter: const NoOpErrorReporter()); expect(() => api.registerPushToken(), throwsA(isA())); } catch (e) { expect(e.toString(), contains('No Firebase App')); diff --git a/test/core/websockets/ws_manager_test.dart b/test/core/websockets/ws_manager_test.dart index 5c17c758..d0a584bb 100644 --- a/test/core/websockets/ws_manager_test.dart +++ b/test/core/websockets/ws_manager_test.dart @@ -1,3 +1,4 @@ +import 'package:eatvio_app/core/error_reporting/no_op_error_reporter.dart'; import 'package:eatvio_app/core/app_config.dart'; import 'package:eatvio_app/core/app_initializer.dart'; import 'package:eatvio_app/core/locator_service.dart'; @@ -29,7 +30,7 @@ void main() { test('initialize returns early when client is not null', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); await manager.initialize(); @@ -38,35 +39,35 @@ void main() { test('newMessagesStream returns null when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.newMessagesStream, isNull); }); test('newNotificationsStream returns null when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.newNotificationsStream, isNull); }); test('directChatStream returns null when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.directChatStream, isNull); }); test('clientTypingStream returns null when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.clientTypingStream, isNull); }); test('subscribeDirectChatChannel does nothing when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Не должно быть исключений manager.subscribeDirectChatChannel(); @@ -74,7 +75,7 @@ void main() { test('unsubscribeDirectChatChannel does nothing when channel is null', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Не должно быть исключений manager.unsubscribeDirectChatChannel(); @@ -82,7 +83,7 @@ void main() { test('clear sets client and userId to null', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); manager.userId = 1; @@ -94,7 +95,7 @@ void main() { test('initPrivateDirectChatChannel - создает канал для прямого чата', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); const chatId = 'chat-123'; const token = 'test-token'; @@ -109,7 +110,7 @@ void main() { test('initialize - обрабатывает ошибку при отсутствии токена', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Убеждаемся, что токена нет await TokenStorage.removeTokenInStorage(); @@ -122,7 +123,7 @@ void main() { test('initialize - обрабатывает ошибку при ошибке getWebsocket', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); const token = 'test-token'; await TokenStorage.saveTokenInStorage(token); @@ -136,7 +137,7 @@ void main() { test('initialize - устанавливает userId из WebsocketInfoModel', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); const token = 'test-token'; const userId = 42; @@ -158,7 +159,7 @@ void main() { test('clear - отписывается от всех каналов', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); manager.userId = 1; @@ -171,7 +172,7 @@ void main() { test('initialize - не инициализирует повторно если client уже существует', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); const token = 'test-token'; await TokenStorage.saveTokenInStorage(token); @@ -193,7 +194,7 @@ void main() { test('subscribeDirectChatChannel - подписывается на канал когда он существует', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Когда канал null, метод не должен выбрасывать исключение manager.subscribeDirectChatChannel(); @@ -203,7 +204,7 @@ void main() { test('unsubscribeDirectChatChannel - отписывается от канала когда он существует', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Когда канал null, метод не должен выбрасывать исключение manager.unsubscribeDirectChatChannel(); @@ -213,35 +214,35 @@ void main() { test('newMessagesStream - возвращает null когда канал не инициализирован', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.newMessagesStream, isNull); }); test('newNotificationsStream - возвращает null когда канал не инициализирован', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.newNotificationsStream, isNull); }); test('directChatStream - возвращает null когда канал не инициализирован', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.directChatStream, isNull); }); test('clientTypingStream - возвращает null когда канал не инициализирован', () { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); expect(manager.clientTypingStream, isNull); }); test('initialize - обрабатывает случай когда TokenStorage.haveTokenInStorage возвращает false', () async { final wsService = MockWsService(); - final manager = WsManager(wsService: wsService); + final manager = WsManager(wsService: wsService, errorReporter: const NoOpErrorReporter()); // Убеждаемся, что токена нет await TokenStorage.removeTokenInStorage(); diff --git a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart index 8bf682e2..94401422 100644 --- a/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart +++ b/test/features/auth/presentation/bloc/oauth_cubit/oauth_cubit_test.dart @@ -379,25 +379,22 @@ void main() { ), ).thenThrow(Exception('Ошибка подтверждения')); - expect( - () => oauthCubit!.confirmOAuthVerification( - provider: 'google', - accessToken: 'access_token', - refreshToken: 'refresh_token', - clientId: 'client_id', - id: 'id', - email: 'test@example.com', - phone: null, - code: '123456', - verificationType: 'email', - firstName: 'Test', - lastName: 'User', - dateOfBirth: '2000-01-01', - gender: 'male', - weight: 70, - height: 175, - ), - throwsException, + await oauthCubit!.confirmOAuthVerification( + provider: 'google', + accessToken: 'access_token', + refreshToken: 'refresh_token', + clientId: 'client_id', + id: 'id', + email: 'test@example.com', + phone: null, + code: '123456', + verificationType: 'email', + firstName: 'Test', + lastName: 'User', + dateOfBirth: '2000-01-01', + gender: 'male', + weight: 70, + height: 175, ); await Future.delayed(const Duration(milliseconds: 100)); diff --git a/test/features/auth/repositories/reset_password_repository_test.dart b/test/features/auth/repositories/reset_password_repository_test.dart index f4c702da..05593cb7 100644 --- a/test/features/auth/repositories/reset_password_repository_test.dart +++ b/test/features/auth/repositories/reset_password_repository_test.dart @@ -21,7 +21,7 @@ void main() { mockResetPasswordService = MockResetPasswordService(); resetPasswordRepository = ResetPasswordRepository( resetPasswordService: mockResetPasswordService, - ); + ); }); test('sendEmailCodeForPasswordChange - успешная отправка кода (200)', () async { diff --git a/test/features/barcode/repositories/barcode_repository_test.dart b/test/features/barcode/repositories/barcode_repository_test.dart index 23509eab..feaabb43 100644 --- a/test/features/barcode/repositories/barcode_repository_test.dart +++ b/test/features/barcode/repositories/barcode_repository_test.dart @@ -24,74 +24,54 @@ void main() { test('getProductByBarcode - возвращает успешный результат (200)', () async { const barcode = '1234567890123'; final productData = {'GTIN': barcode, 'Описание (ru)': 'Test Product'}; - final response = Response( + final response = Response>( data: productData, statusCode: 200, requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), ); - when(mockDio.get('/api/gsone/barcode/$barcode')).thenAnswer((_) async => response); + when(mockDio.get>(any, options: anyNamed('options'))).thenAnswer((_) async => response); final result = await repository.getProductByBarcode(barcode); expect(result.type, BarcodeResultType.success); expect(result.product, isNotNull); expect(result.product!.description, 'Test Product'); - verify(mockDio.get('/api/gsone/barcode/$barcode')).called(1); + verify(mockDio.get>(any, options: anyNamed('options'))).called(1); }); test('getProductByBarcode - возвращает статус обработки (202)', () async { const barcode = '1234567890123'; final taskData = {'task_id': 'task-123', 'status': 'processing'}; - final response = Response( + final response = Response>( data: taskData, statusCode: 202, requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), ); - when(mockDio.get('/api/gsone/barcode/$barcode')).thenAnswer((_) async => response); + when(mockDio.get>(any, options: anyNamed('options'))).thenAnswer((_) async => response); final result = await repository.getProductByBarcode(barcode); expect(result.type, BarcodeResultType.processing); expect(result.taskId, equals('task-123')); - verify(mockDio.get('/api/gsone/barcode/$barcode')).called(1); + verify(mockDio.get>(any, options: anyNamed('options'))).called(1); }); - test('getProductByBarcode - обрабатывает DioException с 202 статусом', () async { + test('getProductByBarcode - пробрасывает DioException при нестандартном ответе', () async { const barcode = '1234567890123'; - final taskData = {'task_id': 'task-456', 'status': 'processing'}; - final dioException = DioException( + final response = Response>( + data: {'error': 'Not found'}, + statusCode: 404, requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), - response: Response( - data: taskData, - statusCode: 202, - requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), - ), ); - when(mockDio.get('/api/gsone/barcode/$barcode')).thenThrow(dioException); + when(mockDio.get>(any, options: anyNamed('options'))).thenAnswer((_) async => response); - final result = await repository.getProductByBarcode(barcode); - - expect(result.type, BarcodeResultType.processing); - expect(result.taskId, equals('task-456')); - }); - - test('getProductByBarcode - пробрасывает исключение при другой ошибке', () async { - const barcode = '1234567890123'; - final dioException = DioException( - requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), - response: Response( - data: {'error': 'Not found'}, - statusCode: 404, - requestOptions: RequestOptions(path: '/api/gsone/barcode/$barcode'), - ), + await expectLater( + repository.getProductByBarcode(barcode), + throwsA(isA()), ); - - when(mockDio.get('/api/gsone/barcode/$barcode')).thenThrow(dioException); - - expect(() => repository.getProductByBarcode(barcode), throwsException); }); test('getTaskStatus - возвращает completed статус', () async { diff --git a/test/features/chat/repositories/chat_repository_test.dart b/test/features/chat/repositories/chat_repository_test.dart index ea0bda09..53f8a9bd 100644 --- a/test/features/chat/repositories/chat_repository_test.dart +++ b/test/features/chat/repositories/chat_repository_test.dart @@ -357,15 +357,16 @@ void main() { verify(mockChatService.getChatByUserSlug(slug: slug)).called(1); }); - test('getChatByUserSlug - возвращает error при ошибке', () async { + test('getChatByUserSlug - пробрасывает исключение при ошибке сети', () async { const slug = 'user-slug'; when(mockChatService.getChatByUserSlug(slug: slug)) .thenThrow(Exception('Network error')); - final result = await chatRepository.getChatByUserSlug(slug); - - expect(result, 'error'); + await expectLater( + chatRepository.getChatByUserSlug(slug), + throwsA(isA()), + ); }); }); } diff --git a/test/features/glossary/repositories/glossary_repository_test.dart b/test/features/glossary/repositories/glossary_repository_test.dart index a852e69a..588d2c14 100644 --- a/test/features/glossary/repositories/glossary_repository_test.dart +++ b/test/features/glossary/repositories/glossary_repository_test.dart @@ -1,75 +1,75 @@ -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/services/glossary_service.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockGlossaryService extends Mock implements GlossaryService {} - -void main() { - group('GlossaryRepository', () { - late GlossaryRepository repository; - late MockGlossaryService mockService; - - setUp(() { - mockService = MockGlossaryService(); - repository = GlossaryRepository(glossaryService: mockService); - }); - - test('getGlossaryListByQuery - успешный запрос', () async { - const query = 'test'; - final mockGlossaries = []; - final mockPagination = GlossaryPaginationModel(glossariesList: mockGlossaries); - - when(() => mockService.getGlossaryByQuery(query: query)) - .thenAnswer((_) async => mockPagination); - - final result = await repository.getGlossaryListByQuery(query); - - expect(result, mockGlossaries); - verify(() => mockService.getGlossaryByQuery(query: query)).called(1); - }); - - test('getGlossaryListByQuery - пробрасывает ошибку', () async { - const query = 'test'; - - when(() => mockService.getGlossaryByQuery(query: query)) - .thenThrow(Exception('Error')); - - expect(() => repository.getGlossaryListByQuery(query), throwsException); - }); - }); -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +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/services/glossary_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockGlossaryService extends Mock implements GlossaryService {} + +void main() { + group('GlossaryRepository', () { + late GlossaryRepository repository; + late MockGlossaryService mockService; + + setUp(() { + mockService = MockGlossaryService(); + repository = GlossaryRepository(glossaryService: mockService); + }); + + test('getGlossaryListByQuery - успешный запрос', () async { + const query = 'test'; + final mockGlossaries = []; + final mockPagination = GlossaryPaginationModel(glossariesList: mockGlossaries); + + when(() => mockService.getGlossaryByQuery(query: query)) + .thenAnswer((_) async => mockPagination); + + final result = await repository.getGlossaryListByQuery(query); + + expect(result, mockGlossaries); + verify(() => mockService.getGlossaryByQuery(query: query)).called(1); + }); + + test('getGlossaryListByQuery - пробрасывает ошибку', () async { + const query = 'test'; + + when(() => mockService.getGlossaryByQuery(query: query)) + .thenThrow(Exception('Error')); + + expect(() => repository.getGlossaryListByQuery(query), throwsException); + }); + }); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/features/menu/repositories/blocked_users_repository_test.dart b/test/features/menu/repositories/blocked_users_repository_test.dart index 6fba2382..b844ca43 100644 --- a/test/features/menu/repositories/blocked_users_repository_test.dart +++ b/test/features/menu/repositories/blocked_users_repository_test.dart @@ -17,7 +17,7 @@ void main() { mockBlockedUserService = MockBlockedUserService(); blockedUsersRepository = BlockedUsersRepository( blockedUserService: mockBlockedUserService, - ); + ); }); test('getBlockedUsers - возвращает список заблокированных пользователей', () async { diff --git a/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart b/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart index 914732d4..76b292aa 100644 --- a/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart +++ b/test/features/personal_diet_suggestion/repository/personal_diet_suggestion_repository_test.dart @@ -18,7 +18,7 @@ void main() { mockService = MockPersonalDietSuggestionService(); repository = PersonalDietSuggestionRepository( personalDietSuggestionService: mockService, - ); + ); }); test('getConsultationWithPersonalDiets - успешная загрузка консультаций', () async { diff --git a/test/features/plans/presentation/cubit/plans_cubit_test.dart b/test/features/plans/presentation/cubit/plans_cubit_test.dart index c1c46b39..ad63a960 100644 --- a/test/features/plans/presentation/cubit/plans_cubit_test.dart +++ b/test/features/plans/presentation/cubit/plans_cubit_test.dart @@ -1,3 +1,4 @@ +import 'package:easy_debounce/easy_debounce.dart'; import 'package:eatvio_app/core/models/pagination_model.dart'; import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart'; import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart'; @@ -59,9 +60,12 @@ void main() { }); tearDown(() async { + EasyDebounce.cancel('loadPlans'); + EasyDebounce.cancel('like_plan'); + EasyDebounce.cancel('add_or_remove_plan_from_favorite'); if (plansCubit != null && !plansCubit!.isClosed) { await Future.delayed(const Duration(milliseconds: 100)); - plansCubit!.close(); + await plansCubit!.close(); plansCubit = null; } }); diff --git a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart index c76ace2e..9b109014 100644 --- a/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart +++ b/test/features/profile/presentation/bloc/profile_cubit/profile_cubit_test.dart @@ -143,7 +143,7 @@ void main() { when(mockProfileRepository.getProfileBySlug('test-user')).thenThrow(Exception('Ошибка загрузки')); - expect(() => profileCubit!.getProfileBySlug('test-user'), throwsException); + await profileCubit!.getProfileBySlug('test-user'); final state = profileCubit!.state; expect(state, isA()); diff --git a/test/features/profile/repositories/profile_repository_test.dart b/test/features/profile/repositories/profile_repository_test.dart index 8c29c465..bd693991 100644 --- a/test/features/profile/repositories/profile_repository_test.dart +++ b/test/features/profile/repositories/profile_repository_test.dart @@ -280,9 +280,8 @@ void main() { when(mockService.tryVerifyEmailByCode(code: '{"code": "$code"}')).thenAnswer((_) async => Future.value()); - final result = await repository.tryVerifyEmailByCode(code); + await repository.tryVerifyEmailByCode(code); - expect(result, isTrue); verify(mockService.tryVerifyEmailByCode(code: '{"code": "$code"}')).called(1); }); @@ -291,9 +290,10 @@ void main() { when(mockService.tryVerifyEmailByCode(code: '{"code": "$code"}')).thenThrow(Exception('Invalid code')); - final result = await repository.tryVerifyEmailByCode(code); - - expect(result, isFalse); + await expectLater( + repository.tryVerifyEmailByCode(code), + throwsA(isA()), + ); }); test('deleteAccount - удаляет аккаунт', () async { diff --git a/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart b/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart index 4afde5ec..63216626 100644 --- a/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart +++ b/test/features/recipes/presentation/cubit/my_recipes_cubit_test.dart @@ -228,7 +228,8 @@ void main() { final result = await myRecipesCubit!.getRecipeById('test-recipe'); expect(result, isA()); - expect(result.id, 1); + expect(result, isNotNull); + expect(result!.id, 1); expect(result.slug, 'test-recipe'); verify(mockAboutRecipeRepository.getRecipe('test-recipe')).called(1); }); diff --git a/test/features/registration/presentation/bloc/registration_cubit/registration_cubit_test.dart b/test/features/registration/presentation/bloc/registration_cubit/registration_cubit_test.dart index 58aa0ffc..11bfe5d6 100644 --- a/test/features/registration/presentation/bloc/registration_cubit/registration_cubit_test.dart +++ b/test/features/registration/presentation/bloc/registration_cubit/registration_cubit_test.dart @@ -139,13 +139,14 @@ void main() { expect(cubit.state.sheet.responseBody, '{"locations": ["London, UK"]}'); }); - test('getLocationsByQuery rethrows exception on error', () async { + test('getLocationsByQuery returns empty list on error', () async { const query = 'Invalid'; final exception = Exception('Network error'); when(() => mockRepository.getLocationsListByQuery(query)).thenThrow(exception); - expect(() => cubit.getLocationsByQuery(query), throwsException); + final result = await cubit.getLocationsByQuery(query); + expect(result, isEmpty); }); test('getLocationsByCoordinates returns list of locations', () async { @@ -176,14 +177,15 @@ void main() { expect(cubit.state.sheet.responseBody, '{"locations": ["London, UK"]}'); }); - test('getLocationsByCoordinates rethrows exception on error', () async { + test('getLocationsByCoordinates returns empty list on error', () async { const latitude = 'invalid'; const longitude = 'invalid'; final exception = Exception('Invalid coordinates'); when(() => mockRepository.getLocationsListByCoordinates(latitude, longitude)).thenThrow(exception); - expect(() => cubit.getLocationsByCoordinates(latitude, longitude), throwsException); + final result = await cubit.getLocationsByCoordinates(latitude, longitude); + expect(result, isEmpty); }); test('getLocationsByQuery handles empty query', () async { diff --git a/test/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit_test.dart b/test/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit_test.dart index 2a78ce73..cd39b8ed 100644 --- a/test/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit_test.dart +++ b/test/features/registration/presentation/bloc/verify_email_cubit/verify_email_cubit_test.dart @@ -140,7 +140,7 @@ void main() { when(mockRepository.sendEmailCode()) .thenAnswer((_) async => {}); when(mockRepository.tryVerifyEmailByCode('wrong')) - .thenAnswer((_) async => false); + .thenThrow(Exception('Неверный код')); await cubit!.sendEmailCode(); await cubit!.tryVerify('wrong'); diff --git a/test/features/registration/repositories/registration_repository_test.dart b/test/features/registration/repositories/registration_repository_test.dart index 07c06bec..aeee031a 100644 --- a/test/features/registration/repositories/registration_repository_test.dart +++ b/test/features/registration/repositories/registration_repository_test.dart @@ -145,7 +145,10 @@ void main() { when(() => mockService.register(sheet: any(named: 'sheet'))) .thenThrow(dioException); - expect(() => repository.tryRegister(sheet), throwsA(isA())); + await expectLater( + repository.tryRegister(sheet), + throwsA(isA()), + ); }); test('getLocationsListByQuery - возвращает LocationModel', () async { diff --git a/test/fixtures/doubles/auth_doubles.dart b/test/fixtures/doubles/auth_doubles.dart index 8dbfaedf..3e1eecdd 100644 --- a/test/fixtures/doubles/auth_doubles.dart +++ b/test/fixtures/doubles/auth_doubles.dart @@ -14,7 +14,8 @@ import 'package:eatvio_app/features/auth/services/auth_service.dart'; /// - Returns data loaded from fixture files so QA/backenders can see /// exactly what is exchanged. class FixtureAuthRepository extends AuthRepository { - FixtureAuthRepository({required this.tokenFixture}) : super(authService: _NoopAuthService()); + FixtureAuthRepository({required this.tokenFixture}) + : super(authService: _NoopAuthService()); final TokenModel tokenFixture; diff --git a/test/fixtures/doubles/blog_post_doubles.dart b/test/fixtures/doubles/blog_post_doubles.dart index bb1df7c6..d79ca54c 100644 --- a/test/fixtures/doubles/blog_post_doubles.dart +++ b/test/fixtures/doubles/blog_post_doubles.dart @@ -4,7 +4,8 @@ import 'package:eatvio_app/features/blog_post_create/services/blog_post_service. import 'package:flutter_image_compress/flutter_image_compress.dart'; class FixtureBlogPostRepository extends BlogPostRepository { - FixtureBlogPostRepository() : super(blogPostService: _NoopBlogPostService()); + FixtureBlogPostRepository() + : super(blogPostService: _NoopBlogPostService()); final List createPayloads = []; final List updatePayloads = []; diff --git a/test/fixtures/doubles/create_diet_doubles.dart b/test/fixtures/doubles/create_diet_doubles.dart index 52645ae5..c5addea6 100644 --- a/test/fixtures/doubles/create_diet_doubles.dart +++ b/test/fixtures/doubles/create_diet_doubles.dart @@ -12,7 +12,8 @@ import 'package:retrofit/dio.dart' show HttpResponse; import '../../fixtures/fixture_reader.dart'; class FixtureCreateDietRepository extends CreateDietRepository { - FixtureCreateDietRepository() : super(createDietService: _NoopCreateDietService()); + FixtureCreateDietRepository() + : super(createDietService: _NoopCreateDietService()); final List> createRequests = >[]; final List> updateRequests = >[]; @@ -136,7 +137,8 @@ class FixtureCreateDietRepository extends CreateDietRepository { } class FixtureGlossaryRepository extends GlossaryRepository { - FixtureGlossaryRepository() : super(glossaryService: _NoopGlossaryService()); + FixtureGlossaryRepository() + : super(glossaryService: _NoopGlossaryService()); @override Future> getGlossaryListByQuery(String query) async { diff --git a/test/fixtures/doubles/create_plan_doubles.dart b/test/fixtures/doubles/create_plan_doubles.dart index 6c32d0bb..75fd6668 100644 --- a/test/fixtures/doubles/create_plan_doubles.dart +++ b/test/fixtures/doubles/create_plan_doubles.dart @@ -3,7 +3,8 @@ import 'package:eatvio_app/features/create_plan/repositories/create_plan_reposit import 'package:eatvio_app/features/create_plan/services/create_plan_service.dart'; class FixtureCreatePlanRepository extends CreatePlanRepository { - FixtureCreatePlanRepository() : super(createPlanService: _NoopCreatePlanService()); + FixtureCreatePlanRepository() + : super(createPlanService: _NoopCreatePlanService()); final List> createPlanRequests = >[]; diff --git a/test/fixtures/doubles/profile_doubles.dart b/test/fixtures/doubles/profile_doubles.dart index d3b98791..dfc8e935 100644 --- a/test/fixtures/doubles/profile_doubles.dart +++ b/test/fixtures/doubles/profile_doubles.dart @@ -13,7 +13,8 @@ import 'package:eatvio_app/features/profile/services/profile_service.dart'; import '../fixture_reader.dart'; class FixtureProfileRepository extends ProfileRepository { - FixtureProfileRepository() : super(profileService: _NoopProfileService()); + FixtureProfileRepository() + : super(profileService: _NoopProfileService()); final List> addWeightRequests = >[]; final List> addCircumferenceRequests = >[]; @@ -64,7 +65,8 @@ class _NoopProfileService implements ProfileService { } class FixtureChatRepository extends ChatRepository { - FixtureChatRepository() : super(chatService: _NoopChatService()); + FixtureChatRepository() + : super(chatService: _NoopChatService()); @override Future getChatByUserSlug(String slug) async { @@ -78,7 +80,8 @@ class _NoopChatService implements ChatService { } class FixtureConsultantRepository extends ConsultantRepository { - FixtureConsultantRepository() : super(consultantService: _NoopConsultantService()); + FixtureConsultantRepository() + : super(consultantService: _NoopConsultantService()); @override Future getConsultantRequestStatus(String slug) async {